Double-Entry Accounting
A digital wallet is a ledger, and the correct foundation for any ledger is double-entry accounting. Every financial event creates exactly two ledger entries: a debit on one account and a credit on another. A transfer of $50 from Alice to Bob creates a debit entry on Alice’s account and a credit entry on Bob’s account. The two entries always sum to zero; money is conserved by construction.
Ledger entries are immutable. You never update or delete an entry; you create a compensating entry to reverse a prior transaction. This gives you a complete, auditable history of every financial event in the system. Balance at any point in time is computable as the sum of all credits minus the sum of all debits for an account up to that timestamp — useful for disputes, regulatory audits, and reconciliation.
The ledger table schema: entry_id, account_id, transaction_id, amount, type (debit|credit), created_at. The transaction table links paired entries: transaction_id, from_account, to_account, amount, currency, status, idempotency_key, created_at. Every financial operation in the system writes through this model, including fee collection, currency conversion spread, and interest accrual.
Balance Management
Computing balance by summing the full ledger on every read is too slow for accounts with years of history. Instead, maintain a materialized balance in a balance_cache table: account_id, currency, available_balance, pending_balance, updated_at. The cache is updated atomically within the same database transaction that writes the ledger entries, so it never diverges from ground truth under normal operation.
For the hot path — balance checks before authorizing a transaction — read the cached balance from Redis with sub-millisecond latency. Write-through caching: the DB transaction updates both the database balance_cache row and the Redis key. Cache TTL is set long (hours) since the authoritative update always goes through the cache-busting write path.
Periodic reconciliation (every few hours) recomputes balance from ledger and compares against balance_cache. Any discrepancy pages on-call immediately. In practice, discrepancies indicate a bug in the transaction write path — a ledger write succeeded but the balance update was skipped, or vice versa. Reconciliation also catches any data corruption from hardware failures or bugs in compensating transaction logic.
Transaction Atomicity
A money transfer must be atomic: both the debit and the credit succeed, or neither does. Implement this with a database transaction wrapping all ledger writes for a given transfer. The sequence: BEGIN; validate sender balance; INSERT debit entry; INSERT credit entry; UPDATE balance_cache for both accounts; COMMIT. If any step fails, the database rolls back all writes and the balances are unchanged.
Idempotency keys prevent duplicate processing. Each transfer request from a client includes a client-generated idempotency_key (UUID). Before executing the transfer, check if a transaction with this key already exists. If it does, return the existing result without re-executing. Idempotency keys are stored with the transaction record and indexed for fast lookup. Keys expire after 24 hours to bound storage.
Distributed scenarios — where the wallet service calls an external payment network — require a two-phase approach: write a PENDING transaction record, execute the external call, then mark the transaction COMPLETED or FAILED based on the response. A background job scans for stuck PENDING transactions older than a timeout and reconciles them against the external network’s status API. This handles the case where the response is lost after a successful external operation.
Transfer Flow
A P2P transfer between two wallet accounts: (1) Client sends transfer request with idempotency key. (2) Service validates: sender account exists and is active, sufficient available balance, recipient account exists and is active, transfer amount within daily/monthly limits. (3) Service creates a PENDING transaction record. (4) Database transaction: debit sender, credit receiver, update balance cache for both, mark transaction COMPLETED. (5) Async: enqueue push notification to sender and recipient, enqueue email receipt, publish transaction event to event bus for downstream consumers (fraud monitoring, reporting, loyalty).
Cross-wallet transfers — to a different wallet provider or bank account — go through an external payment rail (ACH, wire, instant payment network). The transfer is PENDING until the external network confirms settlement. Funds are debited from the sender immediately and held in a suspense account; the recipient is credited when settlement confirms. If the external transfer fails, the suspense account is debited and the sender is credited back.
Transfer limits are enforced at multiple layers: per-transaction limit, daily limit, monthly limit, and velocity limit (count of transfers per hour). Limits are configurable per account tier based on KYC status. Limit checks read from Redis for performance and are enforced under an account-level advisory lock to prevent concurrent transfers from bypassing the daily limit check.
Currency Conversion
Exchange rates are fetched from a provider (e.g., Open Exchange Rates, Wise, or a bank feed) and cached in Redis with a 30-second TTL. On a cross-currency transfer, the rate at time of transaction is locked in and recorded on the transaction record for auditability. The sender sees the exact exchange rate and converted amount before confirming — regulatory requirement in most jurisdictions.
A spread is applied to the mid-market rate as revenue: e.g., 1.5% above mid-market for retail conversions. The spread is recorded as a separate fee ledger entry, so revenue from currency conversion is tracked separately from transfer volume. Large conversions (above a configurable threshold) are flagged for rate desk review — the system may need to hedge the position before executing at scale.
Disputed conversions require storing the rate at time of transaction indefinitely (or for the regulatory retention period). The rate history table stores: currency pair, rate, spread, source, timestamp. This table is append-only and never updated. Disputes are resolved by looking up the rate that was in effect at the transaction timestamp.
KYC Compliance
Know Your Customer (KYC) is a regulatory requirement for financial services. On account creation, collect: legal name, date of birth, residential address, government ID type and number. A third-party identity verification service (Jumio, Onfido, Persona) performs: document scan, OCR extraction of ID fields, liveness check via selfie, and match of selfie to ID photo. Results are returned via webhook and stored on the account record.
KYC status gates transaction limits. Unverified accounts: $1,000/month total transaction volume, no international transfers. Basic verified (ID confirmed): $10,000/month. Enhanced due diligence (EDD, for high-value customers or high-risk jurisdictions): unlimited with ongoing monitoring. EDD requires additional source-of-funds documentation and ongoing periodic re-verification.
Document retention follows regulatory requirements: identity documents retained for 5 years after account closure in most jurisdictions, 7 years for AML-relevant records. Documents are stored encrypted in object storage with access logged. Access to raw identity documents requires elevated permissions and is audited. De-identified attributes (name hash, DOB, ID number hash) are used for analytics and matching without exposing raw PII.
Regulatory Reporting
Anti-Money Laundering (AML) transaction monitoring runs as a stream processor over the transaction event bus. Patterns detected: structuring (multiple transactions just below the $10,000 CTR reporting threshold within a short window — a deliberate pattern to avoid reporting); layering (rapid movement of funds through multiple accounts); unusual cash-out after a period of dormancy; transactions with high-risk jurisdictions on OFAC sanctions lists.
When suspicious activity is detected, a SAR (Suspicious Activity Report) is filed with FinCEN (US) or equivalent authority within 30 days of detection. The filing is handled by the compliance team; the system generates a draft SAR pre-populated with transaction details, account history, and the specific AML pattern that triggered it. SAR filings are confidential — the account holder must not be notified that a SAR was filed.
1099-K reporting to the IRS is required for US accounts that receive over $600/year in payments (post-2022 threshold). The reporting pipeline computes annual totals per account from the ledger, generates 1099-K records, files electronically with the IRS via the FIRE system, and delivers copies to account holders by January 31. Data retention for all regulatory submissions is 7 years minimum.
Dispute and Chargeback
A user disputing a transaction triggers a case in the dispute management system. The disputed transaction is placed on hold (funds reserved, not accessible to recipient). The case record captures: transaction details, user’s dispute reason, any evidence submitted by the user. Dispute reasons are standardized: unauthorized transaction, item not received, item not as described, duplicate charge, processing error.
For disputes on wallet-to-wallet transfers, the merchant (recipient) is notified and given 7 days to submit rebuttal evidence. The disputes team reviews evidence from both parties and makes a decision within 10 business days (regulatory maximum in many jurisdictions). If the dispute is upheld, the transfer is reversed: debit the recipient’s account (or suspense if insufficient funds), credit the sender’s account, insert compensating ledger entries. A chargeback fee is charged to the losing party.
Chargeback rate per merchant is monitored. A merchant exceeding a 1% chargeback rate in a rolling 30-day window triggers a risk review and potential account suspension. Merchants with persistent high chargeback rates are terminated from the platform — payment networks impose fines on wallet providers who allow high-chargeback merchants to operate unchecked.
See also: Stripe Interview Guide 2026: Process, Bug Bash Round, and Payment Systems
See also: Coinbase Interview Guide