Design International Money Transfers

[!NOTE] This is the “Full System Design” version of Wise. It connects every pattern we’ve learned: Idempotency, Ledgers, Async Queues, and Reconciliation.

1. The Problem: Moving Value across Borders

Unlike a local bank transfer, an international transfer involves currency conversion, compliance screening (KYC/AML), and external payout rails (SWIFT, ACH, SEPA).

The Interview Challenge:

“Design a system where a user can send EUR to a friend in India receiving INR. The system must handle the rate lock, funding, conversion, and final delivery while ensuring the accounting is perfectly accurate even if the network fails.”


2. Requirements & Goals

Functional Requirements

  1. Transfer Lifecycle: Create, Fund, Convert, and Deliver funds.
  2. Status Tracking: Real-time visibility for the user.
  3. Audit Trail: Immutable record of every balance change.

Non-Functional Requirements

  1. Financial Correctness: Never credit the recipient without debiting the sender.
  2. Resilience: Rails (external partners) will fail. Handle retries gracefully.
  3. Consistency: Use Double-Entry Bookkeeping to ensure the system is audit-ready.

3. System APIs

Initiate Transfer

POST /v1/transfers
Idempotency-Key: "uuid"
{
  "source_currency": "EUR",
  "target_currency": "INR",
  "source_amount": 10000,
  "recipient_id": "r_99"
}

Response:

{
  "transfer_id": "t_1",
  "status": "CREATED",
  "quoted_rate": 88.54,
  "expires_at": "..."
}

4. High-Level Architecture: The Orchestrator Path

We use a Microservices architecture to separate concerns.

flowchart TD
  U[User App] --> API[Transfer API]
  API --> ORCH[Transfer Orchestrator]
  ORCH -->|1. Lock Rate| FX[FX Service]
  ORCH -->|2. Record Intent| L[Ledger Service]
  ORCH -->|3. Accept Funds| P[Payment Ingestor]
  ORCH -->|4. Dispatch Payout| RA[Rail Adapters]
  RA -->|Webhook/Poll| ORCH
  ORCH -->|5. Update Status| L

5. Detailed Design: The Transfer State Machine

Handling a transfer is a long-running process. It must be modeled as a Finite State Machine (FSM).

State Action Financial Event
CREATED User initiates. None.
FUNDED User pays via card/bank. Debit User Wallet, Credit Wise Purgatory Account.
CONVERTED FX conversion applied. Debit EUR Purgatory, Credit INR Purgatory.
OUTGOING Sent to Indian bank rail. Debit Wise INR Account, Credit Recipient Bank.
SETTLED Delivery confirmed. Finalize audit log.

6. Database Design: Double-Entry Ledger

CRITICAL: Do not just have a balance column in a users table. You need a Ledger.

Table: ledger_entries

id tx_id account_id amount type status
1 tx_001 user_alice_eur -100.00 DEBIT POSTED
2 tx_001 wise_eur_holding +100.00 CREDIT POSTED
3 tx_002 wise_inr_payout -8854.00 DEBIT PENDING
4 tx_002 user_bob_inr +8854.00 CREDIT PENDING

Atomicity (Postgres)

The move from wise_eur_holding to recipient_inr must be Atomic. Use a database transaction. If one part fails, the whole thing rolls back.


7. Deep Dive: Three-Way Reconciliation

The biggest risk for Wise is “Lost Money” in the network. We solve this with Reconciliation.

flowchart LR
  L[Internal Ledger] --- MATCH[Match Processor]
  RA[Rail Reports] --- MATCH
  BS[Bank Statements] --- MATCH
  MATCH -->|Gaps Found| ALARM[Engineering Alarm]
  1. Level 1 (Internal): Do our Credits match our Debits at the end of every hour? SUM(amount) == 0.
  2. Level 2 (Rail): We told the Indian bank to pay $100. Did their nightly CSV confirm a $100 payout?
  3. Level 3 (Bank): Did our actual bank balance decrease by $100?

8. Summary: The Senior Interview Checklist

  1. Idempotency: “What if the user clicks ‘Pay’ twice?” (Use the Idempotency-Key and talk about the PENDING state).
  2. Rail Failure: “What if the Indian bank rail is down for 6 hours?” (Talk about an Outgoing Queue with exponential backoff).
  3. Compensating Transactions: “What if the transfer fails at the last second?” (Explain how you’d ‘Reverse’ the ledger entries).
  4. Auditability: “Finance team wants to know why we paid 8854 INR instead of 8800.” (Show how the rate_snapshot_id links the FX service to the Ledger).