Interview Setup
Interview Prompt
Design a multi-currency payment system that accepts payments in 150+ currencies, locks FX rates during checkout, and settles to merchants in their preferred currency at 10M cross-currency transactions/day.
Clarifying Questions (ask before designing)
| Question | Why it matters |
|---|---|
| Does the platform hold FX risk between charge and settlement, or pass it to the merchant? | Determines rate-lock TTL, treasury hedging, and markup strategy, which is core to the business model. |
| Authorize-only vs capture later? Refunds and chargebacks in scope? |
|
| Who sets rounding rules across buyer display, charge amount, and merchant settlement? |
|
| Single PSP or multi-PSP routing by currency and region? |
|
Scope
In scope
- FX rate service
- Settlement currency
- Rounding rules
- Multi-PSP routing
- Cross-border compliance
- Capacity estimation with shown math
Out of scope (state explicitly)
- Fraud ML model training (covered in Fraud Detection System): a rules engine is sufficient unless explicitly requested
- Merchant onboarding and KYC workflows
- Building a PSP or bank from scratch
Functional Requirements
Start by confirming buyer vs merchant currency flows with your interviewer. Ask whether FX rate locking during checkout and multi-currency ledger balances are in scope for this round.
- Accept payments in any currency: Buyer pays in their local currency (EUR, GBP, JPY, INR...)
- Settle in merchant's currency: Convert and settle to merchant's preferred currency
- Real-time exchange rates: Use live FX rates for conversion at payment time
- FX rate locking: Lock exchange rate for a window (15-30 min) during checkout
- Multi-currency wallets: Users and merchants hold balances in multiple currencies, coordinating with our Digital Wallet System architecture.
- Cross-border transfers: Send money internationally with transparent FX fees
- FX markup/fee: Configurable spread on exchange rates for revenue
- Currency display: Show prices in buyer's local currency across the platform
Non-Functional Requirements
Your interviewer will care most about FX accuracy and rate-lock consistency. A locked rate must hold through checkout even if the market moves, so explicitly clarify who absorbs FX risk during the lock window.
- Accuracy: FX rates accurate to 6 decimal places; no rounding errors
- Low Latency: Currency conversion decision in < 50 ms
- Consistency: Locked FX rate honored even if market moves during checkout
- Compliance: Adhere to currency regulations per country (capital controls, sanctioned currencies)
- Scale: 10M+ cross-currency transactions/day
- Availability: 99.99%
Capacity Estimations
Run this math before you size the FX rate cache. Cross-currency transactions per day and supported currency pairs tell you rate-ingestion frequency; lock TTL drives Redis key volume at checkout.
| Metric | Calculation | Value |
|---|---|---|
| Supported currencies | Given | 150+ |
| FX rate updates | Given | Every 30 seconds from providers |
| Cross-currency transactions / day | Given | 10M |
| FX rate lookups / sec | Derived from daily volume ÷ 86400 (+ peak factor) | 50K |
| Locked rate records | Given | 5M active at any time |
Architecture Diagram
In the room: FX rate lock at checkout means the platform holds exposure between charge and settlement; treasury hedging serves as a staff-level follow-up.
Walk your interviewer through the checkout FX path first. FX Rate Service ingests live rates from multiple providers, Payment Router selects PSP by currency, and a per-currency ledger records every debit/credit with rate locks bridging buyer and merchant currencies at checkout. Frame rate ingestion, locking, and settlement as three distinct architectural hops.
Component Deep Dives
Next we examine each component in the architecture, starting with FX rate management because every downstream conversion decision depends on fresh, multi-source rates.
FX Rate Management
Multi-provider rate ingestion with staleness detection is worth stating before your interviewer asks what happens when an upstream rate feed goes stale.
Rate ingestion:
Multiple FX rate providers (redundancy + best rate selection):
- Reuters/Bloomberg: institutional rates
- ECB (European Central Bank): reference rates
- Open Exchange Rates API: retail rates
Rate with markup:
Raw rate: 1.0845
Platform markup: 2% (revenue)
Buy rate (user buys USD with EUR): 1.0845 * 0.98 = 1.0628
Sell rate (user sells USD for EUR): 1.0845 * 1.02 = 1.1062
Rate triangulation:
No direct rate for NGN->BRL?
NGN -> USD -> BRL (via common intermediate currency)
rate = ngn_usd_rate * usd_brl_rateFX Rate Locking
Rate locking establishes the checkout user experience contract. Explain who absorbs FX risk during the 15-minute window before diving into the lock token flow.
Without locking, the user sees €92.17, takes 5 minutes to fill payment details, and the rate changes before completion. With locking, the platform absorbs FX risk for 15 minutes. At scale (10M transactions/day), average FX movement in 15 minutes remains < 0.05%.
Checkout flow with rate lock:
1. User sees price: "$100 USD" -> "Show in my currency" -> "€92.17 EUR"
2. User clicks "Pay €92.17":
FX Service locks rate:
INSERT INTO rate_locks (lock_id, from_ccy, to_ccy, rate, expires_at)
VALUES ('lock-uuid', 'EUR', 'USD', 1.0850, NOW() + '15 min');
3. User completes payment within 15 min:
Payment Service: validate lock_id is not expired
Charge: €92.17 EUR -> convert at locked rate 1.0850 -> $100.00 USD to merchant
4. If user takes > 15 min:
Lock expired -> re-quote with current rateMulti-Currency Ledger
Per-currency ledger accounts keep FX conversions auditable, aligning with the double-entry bookkeeping detailed in our Banking Ledger System, ensuring that every cross-border payment creates balanced entries in both currencies.
Transaction: Buyer pays €92.17, Merchant receives $100.00
EUR ledger:
Entry 1: { account: buyer_eur, DEBIT, €92.17 }
Entry 2: { account: platform_eur, CREDIT, €92.17 }
USD ledger:
Entry 3: { account: platform_usd, DEBIT, $100.00 }
Entry 4: { account: merchant_usd, CREDIT, $100.00 }
FX conversion record:
{ from: €92.17 EUR, to: $100.00 USD, rate: 1.0850, lock_id: "lock-uuid" }
Per-currency balancing:
SUM(EUR debits) = SUM(EUR credits) ?
SUM(USD debits) = SUM(USD credits) ?Smallest Currency Units
Different currencies have different decimal places: USD: 2 decimals ($1.00 = 100 cents) JPY: 0 decimals (¥100 = 100 yen, no fractional yen) BHD: 3 decimals (0.001 BHD = 1 fils) Best practice: store amounts as integers in smallest unit. $100.00 -> 10000 (cents) ¥100 -> 100 0.500 BHD -> 500 (fils) Avoids all decimal arithmetic issues. Integer math is exact.
API Design
GET /api/v1/fx/quote?from=EUR&to=USD&amount=100
-> { "from": "EUR", "to": "USD", "amount": 100.00,
"converted": 106.28, "rate": 1.0845, "markup": 0.02,
"effective_rate": 1.0628, "valid_for_seconds": 30 }
POST /api/v1/fx/lock
{ "from_currency": "EUR", "to_currency": "USD", "rate": 1.0850, "ttl_seconds": 900 }
-> { "lock_id": "lock-uuid", "expires_at": "2026-03-14T11:15:00Z" }
POST /api/v1/payments/cross-currency
{ "buyer_currency": "EUR", "buyer_amount": 92.17,
"merchant_currency": "USD", "merchant_amount": 100.00,
"fx_lock_id": "lock-uuid", "merchant_id": "m-uuid" }
-> { "payment_id": "pay-uuid", "status": "completed",
"fx_rate_used": 1.0850, "fx_fee": 1.84 }Common Error Responses
400 Bad Request: invalid input, missing required fields, or malformed JSON payload 401 Unauthorized: missing or invalid authentication token or API key 403 Forbidden: authenticated caller lacks required permissions for this resource 404 Not Found: requested resource ID does not exist 409 Conflict: duplicate write or version conflict, retry with a unique idempotency key 422 Unprocessable Entity: syntactically valid request failed semantic business validation 429 Too Many Requests: rate limit quota exceeded, client should honor Retry-After header 500 Internal Error: unexpected server failure, retry safely with an idempotency key 503 Service Unavailable: downstream dependency is unavailable or overloaded, retry with exponential backoff 402 Payment Required: account balance or payment method has insufficient funds 502 Bad Gateway: payment gateway provider timeout, poll transaction status endpoint
Data Model
Redis: Live Rates
fx_rate:{base}:{quote} -> Hash { rate, bid, ask, provider, updated_at }
TTL: 60 (stale after 1 min without update)PostgreSQL: Locks & Ledger
CREATE TABLE rate_locks (
lock_id UUID PRIMARY KEY, from_currency CHAR(3), to_currency CHAR(3),
locked_rate DECIMAL(12,6), expires_at TIMESTAMPTZ, used BOOLEAN DEFAULT FALSE,
payment_id UUID, created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE fx_conversions (
conversion_id UUID PRIMARY KEY, payment_id UUID,
from_currency CHAR(3), from_amount DECIMAL(18,2),
to_currency CHAR(3), to_amount DECIMAL(18,2),
rate_used DECIMAL(12,6), lock_id UUID,
platform_fee DECIMAL(18,2), created_at TIMESTAMPTZ
);Event Bus Design (Kafka)
Topic: payment-events
Partitions: 64
Partition key: payment_id
Retention: 7 years (FX audit trail)
Producer: Payment Service after multi-currency charge + ledger posting
Event: { payment_id, buyer_currency, seller_currency, fx_rate, fx_lock_id, amount_local, amount_settlement }
Consumer groups:
1. notification: receipt with FX breakdown
2. fx-risk-engine: monitor net exposure per currency; auto-hedge if > $1M
3. settlement: daily batch payouts in local currencies
Sync path: FX quote + lock in Redis; charge via PSP < 500ms
Async path: settlement and hedging never block payment authorization
DLQ: payment-events-dlqFault Tolerance
| Concern | Solution |
|---|---|
| FX provider down |
|
| Rate lock expired mid-payment |
|
| Rounding errors |
|
| FX risk |
|
| Sanctioned currencies | Country/currency blocklist enforced at API gateway level |
Rate Staleness and Provider Failover
Multi-provider strategy:
Priority order: Reuters (best quality) -> Bloomberg -> ECB -> OpenExchangeRates
Every 30 seconds:
1. Try primary provider (Reuters)
2. If fails: fall to Bloomberg
3. If both fail: use ECB (updated hourly)
4. If all fail: use last known rate with staleness flag
Staleness rules:
Rate < 1 min old: "live" (green)
Rate 1-5 min old: "recent" (yellow)
Rate 5-30 min old: "stale" (orange): show warning
Rate > 30 min old: "expired" (red)Additional Considerations
Interview Walkthrough
- 25-minute cut
Skip arch50/arch75 depth unless staff.
- FX quote → lock → charge → settle flow (8 min)
- Minor units as integer cents rather than floating point numbers (9 min)
- Rate lock at authorize protects buyer and merchant (8 min)
- Frame the problem as FX risk management rather than basic currency conversion, because the platform holds exposure between charge and settlement.
- Walk through checkout: lock FX rate at payment initiation (15-min window), charge in buyer currency, settle to merchant in their currency.
- Explain rate sourcing hierarchy: primary provider → secondary fallback → last-known-good with staleness flag and wider spread.
- Cover treasury hedging: net daily FX exposure per currency pair and execute forward contracts to limit platform P&L swings.
- Mention payment routing: route USD through Stripe US, EUR through Adyen EU, local methods (UPI, PIX) through regional acquirers.
- Discuss multi-currency ledger with separate balance accounts per currency and cross-currency transfers via FX conversion entries, coordinating with the Banking Ledger System and Digital Wallet System.
- Orchestrate multi-step cross-currency payments and merchant payouts using the saga pattern detailed in Distributed Transactions: 2PC vs Saga.
- Common pitfall: using live mid-market rates without locking at checkout, because a 2% currency move between cart and capture creates merchant disputes.
Settlement and Treasury
Platform accumulates various currencies throughout the day: EUR balance: +€500K, USD balance: -$542K, GBP balance: +£100K Daily settlement: 1. Calculate net position per currency 2. Execute FX trades in wholesale market (better rates than retail) 3. Transfer settled amounts to merchants' bank accounts 4. T+1 or T+2 settlement (1-2 business days) FX hedging: If platform knows it will receive €1M tomorrow (from European sales): Buy a EUR/USD forward contract today -> lock in today's rate Eliminates FX risk on the $1M settlement.
Payment Routing by Currency
Different PSPs have different strengths per currency/country: Stripe: excellent for USD, EUR, GBP Adyen: strong in EU and Asia, 150+ currencies Local PSPs: Razorpay (INR), iDEAL (NL), Alipay (CNY) Smart routing: For INR payments: route to Razorpay (lower fees, higher approval rates) For EUR payments: route to Adyen (native EUR processing) For USD payments: route to Stripe (lowest processing fee) Benefits: higher approval rates, lower fees, fewer chargebacks
Engineering Trade-offs
FX Risk Management: Platform Exposure
Multi-currency checkout balances FX spread revenue against buyer trust, governed by rate-lock windows and platform hedging policy.
Problem: Platform locks EUR/USD at 1.0850 for 15 min for buyer. If EUR/USD moves to 1.0750, platform loses on conversion. At 10M cross-currency txns/day x avg $50 = $500M daily volume Mitigation: 1. Markup covers most risk: 2% markup >> average 15-min FX movement (0.05%) 2. Auto-hedging: if net position in any currency exceeds $1M, execute FX hedge 3. Dynamic lock TTL: normal=15min, high volatility=5min, extreme=2min+larger markup
Review
How helpful was this walkthrough?
Click a star to rate. We actively use this feedback to refine and update our system design content.
Discussion
Share your thoughts, ask questions, or help others.