Revision sheet

Design a payment system like Stripe.

The six things worth holding in your head the night before. Everything else is on the full page.

The opening move — minute one

“100M a day is about 1,160 TPS — this isn’t a scale problem, it’s a correctness problem. I’m going to design a small ACID core that can’t lose or duplicate money, and keep everything that tolerates delay async.”

The state machine

CREATED → PROCESSING → AUTHORIZED → CAPTURED → SETTLED
FAILED (from PROCESSING: declined / timeout) · VOIDED (from AUTHORIZED: released, never captured) · REFUNDED (from CAPTURED or SETTLED: via a new refund transaction — never deletion)

Must-haves

  1. Treats idempotency as a first-class concern: client-supplied idempotency keys with 24h TTL, stored in Redis or DB, replay returns the original response.
  2. Designs a double-entry ledger (every transaction = 2 entries: debit + credit) and explains why a single ‘balance’ table is wrong.
  3. Names the payment state machine explicitly: CREATED → PROCESSING → AUTHORIZED → CAPTURED → SETTLED, and what triggers each transition.
  4. Sizes throughput (~1,160 TPS avg, ~3,500 TPS peak), ledger growth (~200M rows/day), 7-year retention (~256 TB).
  5. Addresses PCI DSS scope: tokenization at the ingress, raw card data never stored in our systems, all storage references tokens.

Red flags

  • Updating a single ‘account_balance’ column instead of using a ledger — corrupts under any concurrent write or partial failure.
  • No idempotency story — client retries result in duplicate charges, every time.
  • Storing raw card numbers anywhere in the application — PCI DSS violation, $1M+/yr compliance cost increase.
  • Treating the PSP (Stripe / Adyen) as ‘just call this API’ with no retry / state reconciliation — guarantees stuck transactions.

The ten trap claims — each one is wrong

  1. “payment is a single synchronous step”
  2. “just check if payment already exists”
  3. “the PSP handles idempotency”
  4. “eventual consistency is fine for payments”
  5. “refund just deletes the payment record”
  6. “encrypt the card numbers and store them”
  7. “update the balance column directly”
  8. “fire-and-forget webhooks”
  9. “publish to Kafka then write to DB”
  10. “Kafka guarantees global ordering”

The L4 lines — what depth sounds like

Idempotency & Duplicate Prevention

Quantifies the idempotency store size (100M keys × 256 bytes ≈ 25 GB), addresses key collision probability, cache vs DB tradeoff for lookup latency, and idempotency key scoping per merchant to prevent cross-tenant collisions

Double-Entry Ledger

Quantifies ledger write volume (1,160 TPS × 2 entries × 1 KB ≈ 200 GB/day), addresses ledger sharding by merchant_id, hot-merchant write contention mitigation via batching, and cross-currency ledger entries with conversion rate snapshotting

Saga / Distributed Transaction Orchestration

Addresses partial failure scenarios (capture succeeds at PSP but local DB write fails), stuck saga detection via heartbeat TTLs, compensation ordering constraints, and poison-pill saga isolation to prevent cascading failures

PCI Compliance & Tokenization

Addresses multi-PSP token portability (network tokens vs PSP-specific tokens), PCI DSS Level 1 audit requirements, key rotation schedules, and the tradeoff between in-house vault (control, full PCI scope) vs processor tokenization (smaller scope, vendor lock-in)

Settlement Reconciliation

Quantifies reconciliation load (100M transactions × 3 sources = 300M comparisons nightly), addresses multi-PSP reconciliation with different file formats, real-time vs batch tradeoffs, and regulatory reporting requirements for unresolved mismatches

Multi-Currency Support

Addresses cross-border payment routing (choose PSP based on currency pair for best FX rate), hedging strategies for FX exposure between authorization and settlement, and multi-currency ledger entries with realized vs unrealized gain/loss tracking