Remittance Flow Low-Level Design: Remittance Flow Version: 1.0 Date: 2026-02-21 Author: Banking Architecture Team Status: Approved Applies to: Drop — Cross-Border Money Transfer (PISP) 1. Overview Remittance is Drop's core feature — sending money from a Norwegian bank account to a recipient abroad. The flow has 4 user-facing steps: Select recipient (or add new) Enter amount (see FX rate + fee in real-time) Review (PSD2 Art. 45 pre-payment disclosure) Confirm (SCA via BankID at user's bank) Drop uses PISP (Payment Initiation Service) to initiate the transfer directly from the user's bank account. Drop never touches the money. API endpoint: POST /api/transactions/remittance Fee: 0.5% of send amount Amount range: 100 - 50,000 NOK KYC required: Yes ( kyc_status = 'approved' ) Supported corridors: Serbia (RSD), Bosnia (BAM), Poland (PLN), Pakistan (PKR), Turkey (TRY), EU (EUR) 2. End-to-End Remittance Flow sequenceDiagram participant U as User participant UI as Drop App
(/send) participant API as Drop API participant DB as Drop DB participant ASPSP as User's Bank participant RB as Recipient Bank Note over U,RB: Step 1 — Select Recipient U->>UI: Navigate to Send Money UI->>API: GET /api/recipients?page=1&limit=20 API->>DB: SELECT * FROM recipients
WHERE user_id = ? ORDER BY created_at DESC DB-->>API: [{id: "rec_1", name: "Marko Petrovic",
country: "Serbia", currency: "RSD"}] API-->>UI: Recipient list (bank accounts masked) U->>UI: Select "Marko Petrovic" Note over U,RB: Step 2 — Enter Amount UI->>API: GET /api/rates/RSD API->>DB: SELECT rate FROM exchange_rates
WHERE to_currency = 'RSD' DB-->>API: {rate: 10.17} API-->>UI: {rate: 10.17, fee: 0.005} U->>UI: Enter 2000 NOK UI->>UI: Live calculation:
Send: 2,000 NOK
Fee: 10 NOK (0.5%)
Rate: 1 NOK = 10.17 RSD
Receives: 20,340 RSD Note over U,RB: Step 3 — Review (PSD2 Art. 45 Disclosure) U->>UI: Tap "Neste" (Next) UI->>API: POST /api/transactions/disclosure
{type: "remittance", amount: 2000,
recipientId: "rec_1"} API->>DB: Lookup recipient currency, exchange rate API->>API: Calculate fee (2000 * 0.005 = 10)
Calculate receive (2000 * 10.17 = 20340)
Determine delivery (non-EEA: 2-4 days) API-->>UI: {amount: 2000, fee: 10, feePercentage: 0.5,
exchangeRate: 10.17, receiveAmount: 20340,
receiveCurrency: "RSD",
estimatedDelivery: "2-4 business days",
totalCost: 2010} UI->>UI: Display disclosure screen:
"Du sender 2 000 kr til Marko Petrovic
Gebyr: 10 kr (0,5%)
Vekslingskurs: 1 NOK = 10,17 RSD
Marko mottar: 20 340 RSD
Total kostnad: 2 010 kr
Estimert levering: 2-4 virkedager" Note over U,RB: Step 4 — Confirm & Payment Initiation U->>UI: Tap "Bekreft og send" (Confirm and send) UI->>API: POST /api/transactions/remittance
{recipientId: "rec_1", amount: 2000,
bankAccountId: "ba_1"} API->>DB: Verify KYC: kyc_status = 'approved' API->>DB: Verify recipient belongs to user API->>DB: Verify bank account exists + balance >= 2010 API->>DB: Lookup exchange rate for RSD API->>DB: Generate idempotency_key
BEGIN TRANSACTION
UPDATE bank_accounts SET balance = balance - 201000
INSERT INTO transactions (status: 'processing') API->>DB: COMMIT API->>ASPSP: POST /v1/payments/cross-border-credit-transfers
{debtorAccount: {iban: user_iban},
instructedAmount: {currency: "NOK", amount: "2010.00"},
creditorName: "Marko Petrovic",
creditorAccount: {bban: "265-1234567-89"},
remittanceInformationUnstructured: "Drop remittance tx_rem_xxx"} ASPSP-->>API: {paymentId: "pay_xyz",
transactionStatus: "RCVD",
scaRedirect: "https://dnb.no/sca/pay/..."} API-->>UI: {transactionId: "tx_rem_xxx",
scaRedirect: "https://dnb.no/sca/pay/..."} Note over U,RB: SCA at Bank (Dynamic Linking) UI->>U: Redirect to bank SCA page U->>ASPSP: BankID authentication
(sees: "2 010 NOK to Marko Petrovic") ASPSP-->>U: Redirect to Drop callback U->>API: GET /api/payments/callback?paymentId=pay_xyz API->>ASPSP: GET /v1/payments/pay_xyz/status ASPSP-->>API: {transactionStatus: "ACCP"} API->>DB: UPDATE transactions
SET status = 'completed',
completed_at = now
WHERE id = 'tx_rem_xxx' API->>DB: INSERT INTO audit_log
(action: 'payment.completed') API->>DB: INSERT INTO notifications
(title: 'Overfoering sendt',
body: '2 000 kr sendt til Marko Petrovic') API-->>UI: {status: "completed"} UI-->>U: Success screen:
"2 000 kr sendt til Marko Petrovic!
Estimert levering: 2-4 virkedager" Note over ASPSP,RB: Settlement (Drop is not involved) ASPSP->>RB: SWIFT gpi / correspondent banking
NOK converted to RSD RB->>RB: Credit Marko's account: 20,340 RSD 3. Transaction States stateDiagram-v2 [*] --> Draft: User on review screen
(disclosure shown, not yet confirmed) Draft --> Initiated: User taps "Bekreft og send"
Transaction record created
(status: processing) Draft --> Abandoned: User navigates away
(no record created) Initiated --> ScaPending: ASPSP returns scaRedirect
User redirected to bank Initiated --> Failed: ASPSP rejects initiation
(invalid IBAN, bank error) ScaPending --> Completed: User completes BankID SCA
ASPSP status: ACCP/ACSC ScaPending --> Failed: SCA timeout (5 min) ScaPending --> Failed: User cancels SCA ScaPending --> Failed: Bank rejects payment
(insufficient funds at bank) Completed --> Settled: Funds credited to recipient
(tracked via ASPSP status polling) Completed --> RefundPending: Settlement failed
(correspondent bank error) Failed --> [*]: User sees error,
can retry from Step 1 RefundPending --> Refunded: Refund processed
Balance restored Refunded --> [*] Settled --> [*] Abandoned --> [*] Database status values ( transactions.status CHECK constraint): processing — Transaction created, awaiting SCA or settlement completed — ASPSP accepted payment, settlement in progress or done failed — Payment rejected, SCA failed, or settlement error 4. Pre-Payment Disclosure (PSD2 Art. 45) Before the user confirms a remittance, Drop must show a complete cost breakdown . This is a legal requirement under PSD2 Art. 45 (Betalingstjenesteloven in Norwegian law). 4.1 Disclosure Checklist Item PSD2 Reference Drop Field Example Amount to be transferred Art. 45(1)(a) amount 2,000 NOK All fees/charges Art. 45(1)(b) fee , feePercentage 10 NOK (0.5%) Exchange rate used Art. 45(1)(c) exchangeRate 1 NOK = 10.17 RSD Amount after conversion Art. 45(1)(d) receiveAmount , receiveCurrency 20,340 RSD Total cost to payer Art. 45(1)(e) totalCost 2,010 NOK Estimated delivery time Art. 45(1)(f) estimatedDelivery 2-4 business days Currency of debit Implicit Send currency NOK Currency of credit Implicit Receive currency RSD 4.2 Disclosure Screen Content (Norwegian) Bekreft overfoering Til: Marko Petrovic Land: Serbia Bankkonto: *****567-89 (Banca Intesa) Du sender: 2 000,00 kr Gebyr (0,5%): 10,00 kr Totalt belop: 2 010,00 kr Vekslingskurs: 1 NOK = 10,17 RSD Marko mottar: 20 340,00 RSD Estimert levering: 2-4 virkedager Pengene trekkes fra: DNB Brukskonto [Bekreft og send] [Avbryt] 5. FX Rate Lock & Expiry 5.1 Rate Lock Window Event Time Action User views disclosure T+0 Rate displayed from exchange_rates table User confirms payment T+0 to T+15min Rate locked in transactions.exchange_rate SCA completes T+0 to T+15min Locked rate applies to settlement SCA not completed T+15min Rate expires, transaction fails, user must re-quote 5.2 Rate Lock Implementation When POST /api/transactions/remittance is called, the current rate is read from exchange_rates The rate is stored in transactions.exchange_rate at insert time If the SCA takes longer than 15 minutes, the reconciliation job detects the stale transaction and marks it failed User is notified to retry (with a new, current rate) 6. Validation Rules 6.1 Pre-Flight Checks (Before Transaction Creation) Check Source Error if Failed User authenticated JWT from cookie/header 401 unauthorized KYC approved users.kyc_status = 'approved' 403 kyc_required Recipient exists recipients.id WHERE user_id = ? 404 not_found Recipient belongs to user recipients.user_id = jwt.userId 404 not_found Bank account exists bank_accounts.id WHERE user_id = ? 400 no_bank_account Amount in range 100 to 50,000 NOK 422 validation_error Amount valid Number.isFinite() , max 2 decimals 422 validation_error Balance sufficient bank_accounts.balance >= amount + fee 402 insufficient_balance Currency corridor supported exchange_rates.to_currency exists 422 validation_error Not duplicate idempotency_key unique Return existing transaction Rate limit < 10 requests/min per IP 429 rate_limited 6.2 Amount Validation Minimum: 100 NOK (protect against micro-transaction abuse) Maximum: 50,000 NOK (regulatory limit for simplified CDD) Decimals: max 2 (validated by validateAmount()) Type: Number.isFinite() (prevents NaN, Infinity injection) 7. Error Scenarios & User Messages Scenario API Response User Message (Norwegian) Next Step KYC not approved 403 kyc_required "Du maa fullfoere identitetsverifisering for aa sende penger." Redirect to KYC flow No linked bank account 400 no_bank_account "Du har ingen tilkoblet bankkonto. Koble til en bank foerst." Redirect to /accounts Insufficient balance 402 insufficient_balance "Ikke nok penger paa kontoen. Saldo: 1 200 kr, totalt belop: 2 010 kr." Show balance, suggest lower amount Unsupported corridor 422 validation_error "Vi stoetter ikke overfoering til dette landet ennaa." Show supported countries Amount too low 422 validation_error "Minimumsbelopet er 100 kr." Adjust amount Amount too high 422 validation_error "Maksimumsbelopet er 50 000 kr." Adjust amount SCA timeout (callback timeout) "BankID-sesjonen utlop. Overforingen ble ikke gjennomfoert." Retry button SCA cancelled (callback cancelled) "Du avbrot betalingen. Ingen penger er trukket." Retry button Bank rejected ASPSP RJCT "Banken avviste overforingen. Kontakt banken din." Show bank support info Rate expired (rate > 15min old) "Vekslingskursen har utlopt. Vennligst bekreft ny kurs." Re-show disclosure with new rate Network error 502/503 "Teknisk feil. Proev igjen om noen minutter." Retry after 30s Duplicate detected 200 (existing tx) "Denne overforingen er allerede registrert." Show existing transaction 8. Post-Transaction 8.1 Confirmation Screen After successful SCA: Overfoering sendt! 2 000 kr sendt til Marko Petrovic Marko mottar 20 340 RSD Referanse: tx_rem_a1b2c3d4... Status: Under behandling Estimert levering: 2-4 virkedager [Se detaljer] [Send til en annen] 8.2 Transaction Tracking Users can track their remittance in the Transaction History ( /transactions ): Status Display Icon processing "Under behandling" Spinner completed "Fullfoert" Green checkmark failed "Feilet" Red X 8.3 Transaction Summary GET /api/transactions/summary returns aggregated transaction statistics for the authenticated user (total sent, total fees, transaction count, breakdown by corridor). 8.4 Receipt GET /api/transactions/{id}/receipt returns a detailed receipt: { "transactionId": "tx_rem_xxx", "date": "2026-02-21T14:30:00Z", "type": "remittance", "amount": 2000, "currency": "NOK", "fee": 10, "exchangeRate": 10.17, "receiveAmount": 20340, "receiveCurrency": "RSD", "recipient": {"name": "Marko Petrovic", "country": "RS"}, "reference": "tx_rem_xxx", "status": "completed", "completedAt": "2026-02-21T14:35:00Z" } 8.5 Notifications On completion/failure, a notification is created: Event Notification Title Notification Body Payment sent "Overfoering sendt" "2 000 kr sendt til Marko Petrovic" Payment completed "Overfoering fullfoert" "20 340 RSD mottatt av Marko Petrovic" Payment failed "Overfoering feilet" "Overfoering til Marko Petrovic ble avvist. Kontakt oss for hjelp." 9. Refund Handling If a remittance fails after funds were debited (e.g., correspondent bank rejects, recipient IBAN invalid): Step Action Timeline 1 ASPSP reports RJCT or CANC status 1-5 business days 2 Drop detects via reconciliation job Within 1 hour of status change 3 Drop creates refund record in audit_log Immediate 4 ASPSP reverses the debit (automatic for SEPA) 1-3 business days 5 Drop updates bank_accounts.balance on next AISP sync Next balance refresh 6 User notified via push notification Immediate Note: For SWIFT transfers, refund timing depends on correspondent banks and may take 5-10 business days. Drop sends a notification with estimated refund timeline. 10. AML/Compliance Checks Each remittance triggers compliance checks before PISP initiation: Check Implementation Action on Trigger Velocity limit > 5 remittances/hour or > 20/day aml_alerts record (medium severity), continue Structuring detection Multiple amounts just below 25,000 NOK aml_alerts record (high severity), review queue High-risk corridor FATF grey/black list country Enhanced due diligence flag Single large transfer > 25,000 NOK Enhanced monitoring Total daily volume > 100,000 NOK cumulative aml_alerts record, may require manual approval Sanctions screening Recipient name vs sanctions lists Block if match, screening_results record 11. Database Impact 11.1 Tables Written Table Operation When transactions INSERT Payment initiated (Step 4) transactions UPDATE Status change (processing to completed/failed) bank_accounts UPDATE (balance) Atomic debit during transaction creation audit_log INSERT Every payment action notifications INSERT Payment sent / completed / failed aml_alerts INSERT If AML rule triggered 11.2 Tables Read Table Operation When users SELECT (kyc_status) Pre-flight KYC check recipients SELECT Recipient lookup (Step 1) bank_accounts SELECT (balance) Balance check (Step 4) exchange_rates SELECT (rate) FX rate lookup (Step 2, 3, 4) transactions SELECT (idempotency_key) Duplicate detection 12. Cross-References Payment Processing: ../integration/payment-processing.md — SEPA/SWIFT settlement, FX, fees Open Banking AISP/PISP: ../integration/open-banking-aisp-pisp.md — PISP API details BankID OIDC: ../integration/bankid-oidc-integration.md — Drop authentication Security Architecture: ../hld/security-architecture.md — Fraud detection, AML pipeline Bank Account Linking: flow-bank-account-linking.md — Prerequisite: linked bank account Database Schema: ../../backend/DATABASE-SCHEMA.md — transactions , recipients , exchange_rates tables API Reference: ../../backend/API-REFERENCE.md — Remittance, disclosure, receipt, rate endpoints