Interview Case Studies
Design a Payment System
Moving money correctly matters more than moving it fast: idempotency end to end, a double-entry ledger, and reconciliation against the outside world.
By the end of this chapter
- Make every step of a payment safe to retry, including calls to an external processor
- Model money movements in an append-only double-entry ledger
- Avoid dual-write inconsistencies with a transactional outbox
- Design daily reconciliation that catches what real-time processing missed
Clarify the requirements
- Functional: a customer pays a merchant using a card, through an external payment processor.
- Functional: full and partial refunds.
- Functional: merchants see their balances and a history of payments and payouts.
- Functional: every movement of money is recorded and auditable.
- Out of scope: building card-network connectivity ourselves, fraud scoring, currency conversion. Note fraud checks as a step in the flow.
- Non-functional: never charge a customer twice for one purchase, and never lose a payment that succeeded.
- Non-functional: every balance is explainable from the records that produced it.
- Non-functional: card details never touch our systems in raw form; we hold processor tokens only.
Estimate
Sign in to keep a note here. It is free, and nobody else can see it.
Sign inYou’ve reached the end
What this chapter was for:
- In payments, correctness beats latency and availability. Throughput is rarely the hard part.
- A timeout from the processor means 'unknown', not 'failed'. Resolve it by asking, never by charging again under a new id.
- A double-entry ledger is append-only and always balances; corrections are new entries, never edits.
- Reconciliation is not optional. Your records, the processor's records and the bank's records are compared every day.