Architecture Document
Architecture Document: Drop
Version: 1.1 Date: 2026-02-08 Author: dev agent (qwen2.5-coder:32b) + John Status: Approved Approved by: John (AI Director)
1. Overview
1.1 System Purpose
Drop je fintech aplikacija za remittance i QR plaćanja za sve stanovnike Norveške i Skandinavije. Drop koristi PSD2 pass-through model — nikada ne drži novac korisnika. AISP čita stanje računa putem Open Banking-a, a PISP inicira plaćanja direktno sa bankovnog računa korisnika.
1.2 Architecture Style
Monolith — scope je 7 stranica + API rute. Microservices bi bio over-engineering za demo.
1.3 Key Design Decisions
| Decision | Choice | Rationale | Alternatives |
|---|---|---|---|
| Architecture | Monolith | Small scope, simple deploy | Microservices (overkill) |
| Frontend | Next.js 16 + React 19 | Already built, modern stack | Remix, SvelteKit |
| Styling | Tailwind v4 | Already in use | Styled Components |
| Database | PostgreSQL 16 (Drizzle ORM) | Full test/prod parity, PostgreSQL-native features, type-safe schema | SQLite (superseded by ADR-014) |
| Auth | JWT via jose | Lightweight, stateless | Session-based (needs Redis) |
| JWT Storage | httpOnly cookie | Prevents XSS token theft | localStorage (less secure) |
| Error Handling | Centralized middleware | Consistent responses, easy logging | Per-route try/catch |
1.4 User Requirements (ENFORCED — from vilkår.html)
These are legally binding requirements published in our Terms of Service. They MUST be enforced in code.
| Requirement | Value | Enforcement |
|---|---|---|
| Minimum age | 18 år | Registration: DOB field → reject if < 18. BankID returns DOB → double-check. |
| Residency | Bosatt i Norge | Registration: Norwegian phone (+47) + Norwegian BankID required. |
| Identity verification | Gyldig BankID | Onboarding: BankID verification mandatory before any transaction. |
| Accurate personal data | User obligation | BankID provides verified name/DOB. User confirms address. |
| No illegal use | User obligation | AML monitoring, transaction limits, suspicious activity detection. |
Source: landing/pages/vilkar.html section 3 — "Du må være minst 18 år og bosatt i Norge for å bruke Drop."
Implementation notes:
- BankID returns fødselsnummer (11-digit) which encodes DOB → extract and validate age >= 18
- In demo/MVP: mock BankID with DOB field, enforce 18+ check in
/api/auth/register - Pass-through model: Drop never holds money, uses Open Banking (PSD2) to read balance and initiate transfers
2. System Context
┌──────────┐ ┌─────────────────────────────┐
│ Mobile │ │ Drop App │
│ Browser │────▶│ Next.js 16 (App Router) │
└──────────┘ │ │
│ ┌─────────┐ ┌───────────┐ │
│ │Frontend │ │ API Routes │ │
│ │ React19 │─▶│ /api/* │ │
│ │ TW v4 │ │ JWT Auth │ │
│ └─────────┘ └─────┬─────┘ │
│ │ │
│ ┌─────▼─────┐ │
│ │PostgreSQL 16│ │
│ │ (Drizzle) │ │
│ └───────────┘ │
└─────────────────────────────┘
2.1 External Interfaces
Currently self-contained with local PostgreSQL (Docker). No external service integrations in MVP.
| System | Purpose | Status |
|---|---|---|
| Exchange rates | Remittance corridors (RSD, BAM, PLN, PKR, TRY, EUR) | Local DB table |
| Auth | JWT via jose | Built-in |
| QR payments | Merchant scanning | Built-in |
Post-MVP roadmap (FUTURE — not yet implemented, requires partners):
- Open Banking provider (PSD2 AISP/PISP) for production bank account access
- Card issuing provider (Stripe or similar) for physical/virtual cards — gated behind feature flags
- KYC provider (Sumsub/Onfido or partner's existing system)
3. Component Architecture
3.1 Frontend Pages
| Page | Route | Description | Status |
|---|---|---|---|
| Landing | / | Marketing page | Core |
| Register | /register | Phone + PIN registration | Core |
| Login | /login | Phone + PIN auth | Core |
| Dashboard | /dashboard | Account overview, last 5 transactions | Core |
| Send Money | /send | Remittance — PISP from user's bank account | Core |
| QR Payments | /scan | Scan & pay via QR code — PISP from user's bank account | Core |
| Bank Accounts | /accounts | View linked bank account balances via AISP | Core |
| Transaction History | /transactions | Full transaction list with filters | Core |
| Notifications | /notifications | Push notifications and transaction alerts | Core |
| Settings | /profile | User preferences and account management | Core |
| Cards | /cards | Virtual/physical card management | FUTURE (feature-flagged) |
3.2 API Layer
/api
/auth
/register/route.ts — POST register (phone + PIN)
/login/route.ts — POST login → JWT in httpOnly cookie
/account/route.ts — GET balance, account info
/transactions
/route.ts — GET list, POST send money
/simulate/route.ts — POST simulate incoming (demo)
/cards — FUTURE (feature-flagged, requires partner)
/route.ts — GET cards, POST create virtual
/[id]/route.ts — PATCH freeze/unfreeze
/[id]/physical/route.ts — POST order physical
middleware/
errorHandler.ts — Centralized error responses
authMiddleware.ts — JWT verification
4. Data Architecture
4.1 Database
- Engine: PostgreSQL 16 (all environments — development, CI, staging, production)
- ORM: Drizzle ORM (
src/shared/db/schema.ts— single source of truth) - Local dev: Docker (
docker compose up -d), port 5433 - Rationale: Full test/prod parity, type-safe schema, PostgreSQL-native features (ADR-014)
4.2 Schema
Total Tables: 19 (12 core + 7 compliance)
Core Tables (12)
| Table | Key Fields | Relationships |
|---|---|---|
| users | id, email, password_hash, first_name, last_name, phone, date_of_birth, kyc_status, role, risk_level, pep_status, sanctions_cleared, kyc_method, kyc_verified_at, national_id_hash, deleted_at, created_at | → bank_accounts, cards, recipients, transactions |
| bank_accounts | id, user_id, bank_name, account_number, iban, balance (cached AISP read), balance_synced_at, currency, is_primary, connected_at | → users, transactions |
| cards | id, user_id, type, last_four, token_ref, expiry, status, shipping_address, pin_hash, created_at | → users — FUTURE (feature-flagged) |
| transactions | id, user_id, type, status, amount, currency, fee, recipient_id, merchant_id, send_amount, send_currency, receive_amount, receive_currency, exchange_rate, purpose_code, created_at, completed_at | → users, recipients, merchants |
| recipients | id, user_id, name, country, currency, bank_account, bank_name, created_at | → users, transactions |
| merchants | id, user_id, business_name, org_number, address, bank_account, fee_rate, status, created_at | → users, transactions |
| exchange_rates | id, from_currency, to_currency, rate, updated_at | — |
| sessions | id, user_id, token_hash, created_at, expires_at, revoked | → users |
| notifications | id, user_id, type, title, body, read, created_at | → users |
| settings | user_id, currency, language, push_enabled, email_enabled, updated_at | → users |
| spending_limits | id, user_id, card_id, limit_type, amount, currency, created_at | → users, cards |
| rate_limits | key, count, reset_at | — |
Compliance Tables (7) — Added 2026-02-16
| Table | Key Fields | Purpose |
|---|---|---|
| audit_log | id, timestamp, user_id, action, resource_type, resource_id, details, ip_address, user_agent | Audit trail of all user actions |
| aml_alerts | id, user_id, alert_type, severity, transaction_id, details, status, reviewed_by, reviewed_at, created_at | Anti-money laundering alert tracking |
| str_reports | id, user_id, alert_id, report_type, status, filed_at, reference_number, details, created_at | Suspicious transaction reports (SAR/STR) |
| screening_results | id, user_id, screening_type, provider, result, match_details, screened_at | PEP, sanctions, adverse media screening |
| consents | id, user_id, consent_type, granted, granted_at, withdrawn_at, ip_address | GDPR consent tracking (PSD2, marketing, data processing) |
| data_access_requests | id, user_id, request_type, status, requested_at, completed_at, download_url, notes | GDPR right to access/erasure/rectification |
| complaints | id, user_id, category, subject, description, status, resolution, created_at, resolved_at | Customer complaint handling |
Pass-through model: Drop NEVER holds customer money. The
bank_accounts.balancefield is a cached AISP read from the user's actual bank account (read-only in production, synced via Open Banking). User funds remain in their bank at all times. PISP initiates payments directly from user's bank account.
No wallet, no top-up: Drop does not have a wallet feature or top-up functionality. Users do not maintain a balance with Drop.
4.3 PSD2 Pass-Through Model
Drop operates as a PSD2 Payment Initiation Service Provider (PISP) and Account Information Service Provider (AISP):
AISP (Account Information)
- Purpose: Read user's bank account balance and transaction history
- Method: Open Banking API via BankID consent
- Storage: Cached balance in
bank_accounts.balance(read-only, synced periodically) - Note: Drop never controls or holds this balance
PISP (Payment Initiation)
- Purpose: Initiate payments directly from user's bank account
- Use cases: Remittance transfers, QR merchant payments
- Method: Open Banking payment initiation with Strong Customer Authentication (SCA)
- Flow: User approves payment → PISP initiates → Bank debits user's account → Drop records transaction
Compliance Requirements (PSD2)
- User consent: Explicit BankID consent required for AISP + PISP access
- SCA (Strong Customer Authentication): Required for all payments
- Data minimization: Only store what's necessary for compliance
- Audit trail: All PISP/AISP operations logged in
audit_logtable - Right to withdraw consent: Tracked in
consentstable
Regulatory tables: audit_log, aml_alerts, str_reports, screening_results, consents, data_access_requests, complaints
5. Security Architecture (from security agent threat model)
5.1 Authentication
- Method: JWT (jose library)
- Storage: httpOnly cookie (NOT localStorage)
- Expiry: 1h access token
- PIN: bcrypt hashed, never stored plain
5.2 Threats & Mitigations
| Threat | Severity | Mitigation |
|---|---|---|
| Broken Access Control | HIGH | JWT middleware on all /api routes |
| SQL Injection | HIGH | Parameterized queries via Drizzle ORM |
| XSS | HIGH | React auto-escapes, CSP headers |
| Token Theft | HIGH | httpOnly cookie, HTTPS |
| CSRF | MEDIUM | SameSite cookie + CSRF token |
| Data in localStorage | HIGH | Move sensitive data to httpOnly cookies |
| Replay Attacks | MEDIUM | Token expiration + jti claim |
| Security Misconfiguration | HIGH | Security headers (HSTS, X-Frame, CSP) |
5.3 Data Protection
- In Transit: HTTPS/TLS (when deployed)
- At Rest: AWS RDS AES-256 encryption (TLS 1.3 in transit to DB)
- PII: Phone numbers hashed in logs
6. Infrastructure
| Environment | Purpose | URL |
|---|---|---|
| Development | Local dev | localhost:3000 |
| Staging | Pre-release | TBD (Vercel preview) |
| Production | Live demo | TBD (Vercel) |
CI/CD Pipeline
Push → Build (next build) → TypeScript Check → Lint → Test → Deploy Staging → Manual Approval → Deploy Prod
7. Technology Stack
| Layer | Technology | Version |
|---|---|---|
| Frontend | Next.js | 16 |
| UI | React | 19 |
| Styling | Tailwind CSS | 4 |
| Backend | Next.js API Routes | 16 |
| Database | PostgreSQL 16 + Drizzle ORM | 16 |
| Auth | JWT (jose) | latest |
| Hosting | Vercel | — |
8. Performance Targets
| Metric | Target |
|---|---|
| FCP | < 1.5s |
| LCP | < 2.5s |
| TTFB | < 200ms |
| API p95 | < 300ms |
| Lighthouse | > 90 |
| Build time | < 60s |
9. ADRs
ADR-001: SQLite over PostgreSQL (superseded)
- Date: 2026-02-08
- Status:
AcceptedSuperseded by ADR-014 - Context: Demo app needs simple DB setup
Decision:Decision (original): SQLite — zero config, file-based- Consequence:
CannotCould not handle concurrent writes well.FineSupersededforbeforedemo.production use. - Current state: PostgreSQL 16 in all environments (development, CI, staging, production). See ADR-014.
ADR-002: JWT in httpOnly Cookie
- Date: 2026-02-08
- Status: Accepted
- Context: Need secure token storage
- Decision: httpOnly cookie prevents XSS token theft
- Consequence: Slightly more complex CSRF handling needed.
ADR-003: Monolith Architecture
- Date: 2026-02-08
- Status: Accepted
- Context: 7 pages, simple API
- Decision: Single Next.js app handles everything
- Consequence: Easy to deploy and maintain. Refactor if scaling needed.
10. Approvals
| Role | Name | Date | Approved |
|---|---|---|---|
| Dev Agent | dev (qwen2.5-coder:32b) | 2026-02-08 | ✅ |
| Security Agent | security (qwen2.5-coder:32b) | 2026-02-08 | ✅ |
| John (AI Director) | John | 2026-02-08 | ✅ |