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
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 |
Layered architecture
Phase 1 of 4 · Client & edge
Start with the entry points
First, define who uses the system and where requests enter. This is the boundary of the design, not yet a complete request path.
- Merchant
- Use an idempotency key for each logical payment, including retries after network timeouts.
- API gateway
- Authenticate the merchant and bind an idempotency key to a request fingerprint.
This first layer defines the system boundary. The next chapters add application logic and data dependencies. After examining the request paths and tradeoffs, we will add operational safeguards and bring the complete architecture together.
API & contracts
Phase 2 of 4 · Application & compute
Add the application logic
Keep the entry points and add the services that do the work. Give each service a clear responsibility before defining its contracts. Storage and background dependencies come next.
- Orchestrator
- Track explicit payment states and resolve ambiguous provider outcomes before retrying a charge.
- Risk + token vault
- Use a token boundary for payment details and evaluate risk before authorization.
API Design
Create Payment Intent (Authorize)
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)
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
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
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
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).Data model
Phase 3 of 4 · Data & async work
Connect data and background work
The services now need their dependencies: durable state, fast lookups, or work that continues outside a request. Separate these responsibilities rather than treating every component as a database.
- Ledger
- Record financial movements as balanced entries with an auditable transaction history.
- Payment adapters
- Isolate provider protocols and normalize outcomes without assuming every provider behaves identically.
- Payment networks
- Treat timeouts as unknown outcomes; reconcile against external provider records.
Data Model
Payment Intent
Ledger Entry (Double-Entry Bookkeeping)
Payment State Machine
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.
Solution: Multi-Layer Idempotency
Decision 2: The Double-Entry Ledger
Decision 3: Smart Routing to Maximize Authorization Rate
Request flows
Detailed Flow Diagrams
Payment Authorization Flow
Refund Flow
Settlement / Payout Flow
Performance & caching
Payment status is correctness-sensitive, so caching cannot replace the authoritative operation record. Cache stable configuration and provider routing data instead. Move receipts and nonessential analytics off the charge path, while keeping durable recording and idempotency checks within the acceptance boundary.
Advanced design
Tokenization & PCI Compliance
Fraud Detection Engine
Subscription / Recurring Billing
Edge cases
Handling Edge Cases
Partial Capture / Over-Capture
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
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 neededNetwork Timeout (Ambiguous Response)
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 hoursDouble-Charge Prevention on Retry
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
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 entirelyTradeoffs
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 |
Reliability & fault tolerance
Phase 4 of 4 · Operations & resilience
Add operational safeguards
With the request paths and tradeoffs established, add the controls that help detect trouble and keep the system operating. The final diagram retains every layer introduced so far.
- Reconciliation
- Compare internal records with provider settlement data and deliver signed webhooks with retry.
Reliability & Fault Tolerance
Single Points of Failure & Mitigations
Disaster Recovery
Reconciliation — The Ultimate Safety Net
Production architecture
High-Level Architecture
Component Breakdown
Full System Architecture (Production-Grade)
Further exploration
A provider reports success through a webhook before the original API request returns. Trace how both paths converge on the same payment record. Then deliver that webhook again and out of order with a later status update. The state machine should reject regressions rather than trusting arrival order.
Interview playbook
Interview Tips
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.
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).
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.
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.
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.
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.
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.
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.
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.
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.