SDE Path

Payments, ledger & idempotency

Design a Payment System

Hard

Move billions of dollars a day — and never lose, invent, or double-charge a single cent.

A payment system is the money-movement backbone behind a checkout: a merchant charges a customer's card, funds flow through external card networks and banks, and the merchant is eventually paid out. Stripe, Adyen, and PayPal are the reference points.

What makes it a hard system-design problem is that it moves real money — and money has no undo. Two failure modes are unacceptable:

  • Double-charging — a network retry must never charge the customer twice.
  • Losing or inventing money — every cent debited from one account must land, exactly once, as a credit somewhere else.

So the whole design is organized around exactly-once semantics and a strongly consistent, double-entry ledger that is the single source of truth. Idempotency keys tame retries; the ledger guarantees conservation of money; asynchronous settlement and reconciliation keep our books in agreement with external banks. This is the canonical "correctness over everything" interview.

Start by scoping. State the money-conservation invariant up front: every charge is exactly-once and the ledger always balances. Then separate the write path (charge / refund — strongly consistent) from the read path (dashboards / reporting) early — that split drives every later decision. Keep the surface tiny: initiate a payment, process it through a PSP, record it in the ledger, and query/refund it.

Functional

  • Create & confirm a charge (idempotent) — a merchant charges a customer's payment method for an amount + currency, and every write carries an Idempotency-Key so a retry returns the original result.Modeled as a PaymentIntent the merchant confirms; amounts are integer minor units (cents); same key → same outcome, never a second charge.
  • Authorize & capture through an external PSP / card network, then mark the payment succeeded or failed.
  • Double-entry ledger — every money movement records balanced debits and credits; account balances are derived from it, and it is append-only.Refunds and chargebacks post reversing entries rather than editing history — the audit trail is never mutated.
  • Query status & refund — read a payment's current state and issue full or partial refunds.Merchants are notified of state changes via webhooks, and accumulated merchant balances are paid out to their bank on a schedule (settlement / payouts).

Non-functional

  • Exactly-once money movement — zero double-charges and zero lost/duplicated ledger postings. Correctness is non-negotiable.
  • Strong consistency & durability on the ledger — every committed entry survives failure with RPO = 0 (synchronous replication); no acknowledged write is ever lost.
  • Auditability — the ledger is append-only and retained 7+ years for compliance; any past balance is reconstructable.
  • Throughput — sustain ~5,000 charges/s peak (~1,000/s average) and ~20,000 ledger writes/s peak.
  • Availability99.99% on the charge API; degraded-but-correct always beats fast-but-wrong.
  • Latency — authorize < 500 ms p99 (bounded by the external PSP); the synchronous ledger commit < 50 ms p99.

Out of scope

  • Fraud / risk-scoring ML models
  • PCI card-data vaulting (assume a tokenization vault upstream)
  • FX / multi-currency conversion rates
  • Merchant onboarding & KYC

Check yourself· 3 questions

Question 1

Which single invariant is the crux of this system and shapes the whole design?

Question 2

Why require an Idempotency-Key on every charge write?

Question 3

Why favor strong consistency (CP) over raw availability on the charge path?