01

Requirements & scope

Problem Statement & Requirements

Design a payment processing platform that handles the full lifecycle of financial transactions — accepting payments from customers, routing to card networks, managing settlements, handling refunds, and preventing fraud — similar to Stripe, Square, Adyen, or PayPal.

Functional Requirements

  • Charge a payment method — credit/debit cards, bank transfers (ACH), digital wallets (Apple Pay, Google Pay)
  • Payment intents — two-phase: authorize (hold funds) → capture (collect funds), or single-phase direct charge
  • Refunds — full or partial refund of a completed payment
  • Payment methods — store, retrieve, and delete customer payment methods (tokenized)
  • Idempotency — every API call accepts an idempotency key; retries are safe and produce identical results
  • Webhooks — notify merchants of async events (payment succeeded, refund completed, dispute opened)
  • Multi-currency — accept payments in 135+ currencies; settle in merchant's preferred currency
  • Subscriptions / Recurring billing — charge customers on a schedule (monthly, annual)
  • Dispute / Chargeback handling — manage the lifecycle of customer-initiated disputes
  • Payout / Settlement — aggregate funds and transfer to merchant bank accounts on a schedule
  • Ledger — double-entry bookkeeping for every money movement; audit trail

Non-Functional Requirements

  • Correctness above all — money must never be created, lost, or double-counted
  • Exactly-once processing — no duplicate charges, no lost refunds
  • High availability — 99.999% for payment acceptance (< 5 min downtime/year)
  • Low latency — payment authorization in < 2 seconds end-to-end
  • PCI DSS compliance — Level 1; card data encrypted at rest and in transit; tokenization
  • Strong consistency — payment state transitions must be ACID
  • Auditability — every state change logged immutably for regulatory compliance
  • Scalability — handle Black Friday traffic (10x normal peak)

Out of Scope

  • Detailed card network protocol internals (ISO 8583 message format)
  • KYC/AML onboarding flow for merchants
  • Tax calculation engine
  • Full invoicing system
02

Scale estimations

Scale Estimations

Traffic

Metric Value
Payments processed / day 50M
Payments / second (avg) ~580 TPS
Payments / second (peak — Black Friday) ~6,000 TPS
Refunds / day 2.5M (5% refund rate)
Webhook deliveries / day 200M (avg 4 events per payment lifecycle)
API calls / second (all endpoints) ~10K RPS
Active merchants 5M
Stored payment methods (tokens) 2B

Storage

Metric Value
Payment record size ~2 KB (metadata, status, amounts, timestamps)
Payments per year 50M × 365 = ~18.25B
Payment storage (1 year) 18.25B × 2 KB = ~36.5 TB
Ledger entries (2 per payment + refunds + fees) ~50B/year
Ledger storage (1 year) 50B × 500 bytes = ~25 TB
Event log (webhooks, audits) ~20 TB/year
Total storage (1 year) ~80 TB
Retention 7+ years (regulatory)

Bandwidth

Metric Value
API ingress 10K RPS × 2 KB = ~20 MB/s
Webhook egress 200M/day × 1 KB = ~2.3 MB/s avg, 50 MB/s peak
Card network traffic ~580 TPS × 1 KB = ~0.6 MB/s

Hardware Estimate

Component Spec
API servers 20-50 (stateless, auto-scaling)
Payment processing workers 30-100 (orchestrate payment flow)
Database (primary) 10-20 shards (PostgreSQL, SSD-backed)
Ledger database 5-10 dedicated nodes (append-only, SSD)
Card network gateway 10-20 (connection pooling to Visa/MC)
Webhook delivery fleet 20-50 (async, retry-capable)
Redis (idempotency + rate limiting) 3-6 nodes
03

Layered architecture

High-Level Architecture

High-Level ArchitectureExcalidraw diagram · editable shapes · reveal step by stepExplore

Component Breakdown

Component BreakdownExcalidraw diagram · editable shapes · reveal step by stepExplore
04

API & contracts

API Design

Create Payment Intent (Authorize)

EXAMPLE
POST /v1/payment_intents
Idempotency-Key: pi_req_abc123
Authorization: Bearer sk_live_...

Request:
{
  "amount": 2999,                        // cents (avoid floating point!)
  "currency": "usd",
  "payment_method": "pm_card_visa_4242",
  "capture_method": "manual",            // "automatic" for single-phase
  "description": "Order #1234",
  "metadata": {"order_id": "ord-1234"},
  "customer": "cus_abc123",
  "receipt_email": "alice@example.com"
}

Response (200 OK):
{
  "id": "pi_3MtwBwLkdIwHu7ix",
  "object": "payment_intent",
  "amount": 2999,
  "currency": "usd",
  "status": "requires_confirmation",
  "payment_method": "pm_card_visa_4242",
  "client_secret": "pi_3MtwBw_secret_YkS2...", // for frontend confirmation
  "created": 1712150400
}

Confirm Payment (Execute Authorization)

EXAMPLE
POST /v1/payment_intents/{id}/confirm
Idempotency-Key: pi_confirm_abc123

Response (200 OK):
{
  "id": "pi_3MtwBwLkdIwHu7ix",
  "status": "requires_capture",     // if manual capture
  // OR
  "status": "succeeded",            // if automatic capture
  "charges": {
    "data": [{
      "id": "ch_abc123",
      "amount": 2999,
      "currency": "usd",
      "status": "succeeded",
      "authorization_code": "A12345",
      "network_transaction_id": "NT98765",
      "risk_score": 12,
      "outcome": {
        "network_status": "approved_by_network",
        "risk_level": "normal"
      }
    }]
  }
}

Capture Payment

EXAMPLE
POST /v1/payment_intents/{id}/capture
Idempotency-Key: pi_capture_abc123

Request:
{
  "amount_to_capture": 2999           // can be less than authorized (partial capture)
}

Response (200 OK):
{
  "id": "pi_3MtwBwLkdIwHu7ix",
  "status": "succeeded",
  "amount_captured": 2999
}

Create Refund

EXAMPLE
POST /v1/refunds
Idempotency-Key: ref_req_xyz789

Request:
{
  "payment_intent": "pi_3MtwBwLkdIwHu7ix",
  "amount": 1500,                     // partial refund (in cents)
  "reason": "customer_request"        // customer_request | duplicate | fraudulent
}

Response (200 OK):
{
  "id": "re_abc123",
  "amount": 1500,
  "currency": "usd",
  "status": "pending",               // pending → succeeded (async from network)
  "payment_intent": "pi_3MtwBwLkdIwHu7ix"
}

Webhook Event

EXAMPLE
POST https://merchant.example.com/webhooks/stripe
Stripe-Signature: t=1712150400,v1=sha256_hmac_signature

{
  "id": "evt_abc123",
  "type": "payment_intent.succeeded",
  "data": {
    "object": {
      "id": "pi_3MtwBwLkdIwHu7ix",
      "amount": 2999,
      "status": "succeeded"
    }
  },
  "created": 1712150400
}

Merchant must respond 2xx within 10 seconds.
No 2xx → retry with exponential backoff (up to 3 days, ~20 attempts).
05

Data model

Data Model

Payment Intent

Payment IntentExcalidraw diagram · editable shapes · reveal step by stepExplore

Ledger Entry (Double-Entry Bookkeeping)

Ledger Entry (Double-Entry Bookkeeping)Excalidraw diagram · editable shapes · reveal step by stepExplore

Payment State Machine

Payment State MachineExcalidraw diagram · editable shapes · reveal step by stepExplore
06

Core design decisions

Core Design Decisions

Decision 1: How to Guarantee Exactly-Once Payment Processing

This is the single most important design problem. Charging a customer twice is catastrophic.

Decision 1: How to Guarantee Exactly-Once Payment ProcessingExcalidraw diagram · editable shapes · reveal step by stepExplore
Solution: Multi-Layer Idempotency
Solution: Multi-Layer IdempotencyExcalidraw diagram · editable shapes · reveal step by stepExplore

Decision 2: The Double-Entry Ledger

Decision 2: The Double-Entry LedgerExcalidraw diagram · editable shapes · reveal step by stepExplore

Decision 3: Smart Routing to Maximize Authorization Rate

Decision 3: Smart Routing to Maximize Authorization RateExcalidraw diagram · editable shapes · reveal step by stepExplore
07

Request flows

Detailed Flow Diagrams

Payment Authorization Flow

Payment Authorization FlowExcalidraw diagram · editable shapes · reveal step by stepExplore

Refund Flow

Refund FlowExcalidraw diagram · editable shapes · reveal step by stepExplore

Settlement / Payout Flow

Settlement / Payout FlowExcalidraw diagram · editable shapes · reveal step by stepExplore
08

Performance & caching

Workshop note · added for the website’s common reading format

Start with the dominant access pattern of Payment System. Ledger is one place to inspect capacity and tail latency. Measure before introducing a cache: define the cache key, invalidation policy, stale-data budget, and cold-start behavior.

Batch independent work where the latency budget allows it. Bound queues and concurrency, and verify that an optimization does not move the bottleneck to a dependency.

09

Advanced design

Tokenization & PCI Compliance

Tokenization & PCI ComplianceExcalidraw diagram · editable shapes · reveal step by stepExplore

Fraud Detection Engine

Fraud Detection EngineExcalidraw diagram · editable shapes · reveal step by stepExplore

Subscription / Recurring Billing

Subscription / Recurring BillingExcalidraw diagram · editable shapes · reveal step by stepExplore
10

Edge cases

Handling Edge Cases

Partial Capture / Over-Capture

EXAMPLE
Authorized: $100.00

Case 1: Partial capture (customer removed items from cart)
  → Capture $75.00. Remaining $25.00 auth released to cardholder.
  → Ledger: DEBIT network $75, CREDIT merchant $72.82, CREDIT fees $2.18

Case 2: Multi-capture (ship items separately)
  → Capture $40.00 (shipment 1)
  → Capture $35.00 (shipment 2)
  → Total captured: $75.00. Remaining $25.00 released.
  → Each capture is a separate ledger transaction.

Authorization Expiry

EXAMPLE
Problem: Merchant authorizes $100 but never captures

  Card network rules:
    Visa: auth expires in 7 days
    Mastercard: auth expires in 7-30 days (varies)
    If not captured → funds released to cardholder automatically

  Our handling:
    Background job scans for old uncaptured auths
    After 6 days: send webhook warning merchant
    After 7 days: mark as expired, release ledger hold
    Merchant can re-authorize if needed

Network Timeout (Ambiguous Response)

EXAMPLE
Problem: We send auth request to Visa → timeout after 30s
         Did Visa approve it? Decline it? Never receive it?

  This is the HARDEST edge case in payments.

  Solution: Status Inquiry + Reconciliation

  1. Immediately: mark payment as "processing" (not succeeded/failed)
  2. After 30s: send "status inquiry" to Visa
     → "I sent request REQ-123, what happened?"
     → Visa responds: approved / declined / not found
  3. If "not found": safe to retry with new request ID
  4. If approved/declined: update our state accordingly
  5. If status inquiry also times out:
     → Mark as "requires_manual_review"
     → Nightly reconciliation batch compares our records
       with network settlement files
     → Catches ALL discrepancies within 24 hours

Double-Charge Prevention on Retry

EXAMPLE
Scenario:
  1. Merchant calls POST /confirm (Idempotency-Key: IK-123)
  2. We auth with Visa → approved → our server crashes BEFORE responding
  3. Merchant times out → retries with same IK-123
  4. We see IK-123 exists with status="processing" (not completed)

  Handling:
  → Check: is there a network_txn_id for this payment?
  → Yes: we already sent to Visa and it was approved
  → Return the original approval result (don't send to Visa again)
  → Update idempotency cache with final result

  If network_txn_id is NULL:
  → We crashed before sending to Visa
  → Safe to send authorization now (first attempt from network's POV)

Dispute / Chargeback Handling

EXAMPLE
Customer calls bank: "I didn't make this purchase!"

Timeline:
  Day 0:   Issuer initiates dispute, debits merchant's account
  Day 1:   We receive dispute notification from network
           → Webhook: "charge.dispute.created"
           → Ledger: DEBIT merchant, CREDIT dispute_reserve
  Day 1-21: Merchant submits evidence (receipt, tracking, logs)
           → We forward evidence to network (representment)
  Day 30-75: Issuer reviews evidence
           → Won: funds returned to merchant (CREDIT merchant)
           → Lost: funds stay with customer (dispute_reserve cleared)
           → Webhook: "charge.dispute.closed" with outcome

  Financial impact tracking:
  dispute_rate = disputes / total_charges (by merchant)
  If > 1%: warn merchant (Visa/MC programs penalize high dispute rates)
  If > 2%: risk of losing processing privileges entirely
11

Tradeoffs

Tradeoffs & Design Decisions Summary

Decision Option A Option B Chosen Why
Idempotency DB-based (check before every write) Redis SETNX + DB Redis + DB Redis for fast dedup (< 1ms); DB as durable backup
Ledger Single-entry (simple balance tracking) Double-entry bookkeeping Double-entry Provable correctness; audit trail; catches bugs via balance invariant
Money representation Float/decimal Integer cents (BIGINT) Integer cents Floats have rounding errors; $29.99 = 2999 cents; zero ambiguity
Card storage Encrypt in main DB Isolated Token Vault + HSM Token Vault Minimizes PCI scope from 100+ servers to < 5; massive compliance savings
Network routing Single acquirer Smart routing with fallback Smart routing 2-5% auth rate improvement = millions in recovered revenue
State transitions Eventual consistency ACID transactions ACID Money requires absolute correctness; payment + ledger + event = atomic
Webhook delivery Synchronous (block on merchant) Async with retry queue Async + retry Merchant downtime shouldn't block our pipeline; retry up to 3 days
Settlement Real-time (per-transaction) Daily batch Daily batch Reduces ACH costs; provides fraud review window; industry standard
Fraud scoring Rules-only ML model + rules ML + rules ML catches patterns rules can't; rules handle explicit policies
Consistency Optimistic (retry on conflict) Pessimistic (lock on payment) Pessimistic Payment state transitions must be serialized; no lost updates
12

Reliability & fault tolerance

Reliability & Fault Tolerance

Single Points of Failure & Mitigations

Single Points of Failure & MitigationsExcalidraw diagram · editable shapes · reveal step by stepExplore

Disaster Recovery

Disaster RecoveryExcalidraw diagram · editable shapes · reveal step by stepExplore

Reconciliation — The Ultimate Safety Net

Reconciliation — The Ultimate Safety NetExcalidraw diagram · editable shapes · reveal step by stepExplore
13

Production architecture

Full System Architecture (Production-Grade)

Full System Architecture (Production-Grade)Excalidraw diagram · editable shapes · reveal step by stepExplore
14

Further exploration

Workshop note · added for the website’s common reading format

Rebuild Payment System from memory, then change one assumption: ten times more traffic, a new region, or a stricter consistency requirement. Which component must change first—and which does not?

Compare Orchestrator with the same boundary in a related design. Write down one alternative you rejected, what it would simplify, and when you would choose it instead. Follow the source link at the end of this article to explore the original document.

15

Interview playbook

Interview Tips

  1. Lead with correctness, not performance — "The #1 requirement is money never gets lost or double-counted." Explain idempotency keys, ACID transactions, double-entry ledger. Performance is secondary to correctness for payments.

  2. Draw the payment lifecycle state machine — requires_payment_method → requires_confirmation → requires_capture → succeeded → refunded. Show you understand the two-phase (auth + capture) model and why it exists (hotel holds, preorders).

  3. Explain idempotency concretely — Walk through the retry scenario: merchant sends charge, times out, retries with same key. Show how SETNX in Redis prevents duplicate processing, and how stored results return the original response.

  4. Double-entry ledger is the showstopper answer — Most candidates say "store balances in a column." Explain: every money movement creates debit + credit entries; sum must always be zero; corrections are reversing entries, never updates. This invariant catches bugs, fraud, and data corruption.

  5. Use integers for money, never floats — $29.99 = 2999 cents as BIGINT. Floating point: 0.1 + 0.2 = 0.30000000000000004. In payments, one cent off in a billion transactions = audit nightmare.

  6. Tokenization minimizes PCI scope — Card numbers never touch your main infrastructure. Token Vault (< 5 servers in PCI scope) vs entire platform (100+ servers). Dramatically reduces audit cost and security risk.

  7. Network timeout is the hardest edge case — Don't handwave "just retry." Explain: timeout is ambiguous (approved? declined? lost?). Solution: status inquiry to network + nightly reconciliation as safety net.

  8. Smart routing improves auth rates — Not all acquirers are equal. Routing based on BIN, country, card type, historical performance can lift auth rates 2-5%. At scale, this is millions in recovered revenue.

  9. Reconciliation is the last line of defense — Three-way nightly comparison: our records vs network settlement files vs ledger balances. Any mismatch = alert. This catches everything that all other safeguards missed.

  10. End with fraud and disputes — Mention ML scoring (< 10ms real-time), 3DS challenges for medium-risk, and the dispute lifecycle. Show you understand the business impact: > 1% dispute rate = card network penalties.