System design · must-tier · asked at Amazon, Apple, Google, Meta, Microsoft, Netflix

Design a Payment System

hardhldidempotencyledgerconsistency

Prompt

"Design the backend that lets a user pay a merchant. Money must never be lost or charged twice. Assume we work with external card networks and banks that we do not control."

Clarifying questions

  • Which payment methods? Cards through an external processor, and a stored balance (wallet) that we own. Bank transfers are a follow-up.
  • What operations? Charge, refund (full or partial), and a merchant payout at the end of the day.
  • What does the external processor guarantee? It is idempotent if we pass a request key, may take seconds to respond, and may time out with the charge having succeeded on its side.
  • Consistency versus availability? Correctness first. A payment that fails cleanly is fine; a payment that succeeds twice, or succeeds without being recorded, is not.
  • Do we need real-time balances? For the wallet, yes. For merchant balances, an eventually consistent view with a daily settled figure is acceptable.
  • Compliance scope? Assume card data is tokenised by a vault we do not design, and all money movements must be auditable and reconstructible.

Requirements

Functional

  • charge(payer, merchant, amount, currency, idempotency_key) producing a durable outcome.
  • Refunds referencing an original charge, never exceeding it.
  • A double-entry ledger from which any balance can be recomputed.
  • Daily reconciliation against the processor's settlement report and merchant payouts.

Non-functional

  • No lost or duplicated money movements under crashes, retries, and processor timeouts.
  • Charge latency dominated by the processor (seconds); our own overhead under 100 ms.
  • Full audit trail; ledger entries are immutable.
  • Availability target 99.99% for the charge path; degrade by refusing new charges rather than by guessing.

Back-of-envelope

  • Assume 10 million charges per day. Average: 10 M / 86,400 = ~115 charges/s; peak (sales events) 20x that, ~2,300/s. This is low; say so and note that it means we can afford synchronous, strongly consistent writes on the hot path.
  • Ledger entries: each charge produces at least 2 entries (debit payer, credit merchant) plus fee entries, say 4 on average, at ~200 bytes each. 10 M x 4 x 200 B = 8 GB/day, ~3 TB/year. Kept forever, so a decade is 30 TB. Modest for a replicated relational store.
  • Processor calls: 10 M/day outbound, each holding a connection for 1-5 s. At peak 2,300/s x 3 s average = ~7,000 concurrent outstanding calls. Connection pool and thread model must handle that; an async worker pool, not a thread per request.
  • Wallet balance reads: assume 10 reads per charge (UI, fraud checks): ~1,200/s average, trivial.
  • Reconciliation: one settlement file per day with 10 M lines, processed in batch; a few GB.

Last reviewed 8 September 2026