System DesignMarch 3, 2026·14 min read
Design a Payment System: System Design Interview Guide
Design reliable payment processing with idempotency, ledgers, and fraud detection
Why Design a Payment System?
Payment system design is asked at Stripe, Square, PayPal, and any company that processes money. It tests fundamentally different concerns from social media systems: correctness over performance.
- Exactly-once processing: A payment must never be charged twice
- Consistency: Money must never be created or destroyed
- Auditability: Every transaction must have a complete paper trail
- Compliance: PCI-DSS, SOX, anti-money-laundering regulations
Step 1: Requirements
Functional Requirements
Core features:
- Process card payments (charge, authorize, capture)
- Handle refunds (full and partial)
- Manage payment methods (store cards securely)
- Webhook notifications for payment events
- Transaction history and reporting
Out of scope:
- Multi-currency / forex
- Subscription billing (higher-level concern)
- Marketplace payouts (adds complexity)
- Physical card issuanceNon-Functional Requirements
Scale:
- 10M transactions per day
- Peak: 1,000 transactions/second (Black Friday)
- 99.999% durability (never lose a transaction)
Performance:
- Payment processing < 3 seconds end-to-end
- 99.99% availability
- Zero double-charges (idempotency is non-negotiable)
Compliance:
- PCI-DSS Level 1 (card data handling)
- SOX compliance (financial reporting)
- AML (anti-money-laundering) screening
Key insight: Correctness > speed. A slow payment is acceptable.
A wrong payment (double charge, lost money) is catastrophic.Step 2: High-Level Architecture
┌─────────────────────────────────────────────────────────────┐
│ MERCHANT API │
│ (REST API with idempotency keys) │
└───────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ API GATEWAY │
│ Auth, rate limiting, idempotency check │
└───────────────────────┬─────────────────────────────────────┘
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Payment │ │ Fraud │ │ Ledger │
│ Service │ │ Engine │ │ Service │
└────┬─────┘ └──────────┘ └────┬─────┘
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ Payment │ │ Ledger │
│ Gateway │ │ Database │
│ (Visa, │ │ (append- │
│ MC) │ │ only) │
└──────────┘ └──────────┘Step 3: Deep Dive — Idempotency
This is the single most important concept in payment system design. Network failures happen constantly. Without idempotency, a retry could charge a customer twice.
Problem:
1. Client sends payment request
2. Server processes payment, charges card
3. Network fails BEFORE response reaches client
4. Client retries (same payment)
5. Without idempotency: customer charged TWICE
Solution: Idempotency keys
- Client sends a unique idempotency_key with each request
- Server stores: idempotency_key → {status, response}
- On retry: if key exists, return cached response
- Never process the same key twice
Implementation:
POST /v1/payments
Headers:
Idempotency-Key: "order_123_payment_attempt_1"
Authorization: Bearer sk_...
Server logic:
1. Check idempotency store for this key
2. If found AND completed → return cached response
3. If found AND in-progress → return 409 Conflict
4. If not found → create entry with status "processing"
5. Process payment
6. Update entry with response
7. Return response
Storage:
- Redis for hot lookups (TTL: 24-48 hours)
- PostgreSQL for permanent record
- Key must be unique per merchant + operationStep 4: Deep Dive — Double-Entry Ledger
The ledger is the source of truth for all money movement.
Double-entry bookkeeping principle:
Every transaction creates TWO entries that sum to zero.
Money is never created or destroyed, only moved.
Example: Customer pays $100 to merchant
Entry 1: Customer account -$100 (debit)
Entry 2: Merchant account +$100 (credit)
Sum = $0 ✓
Example: Merchant refunds $30
Entry 1: Merchant account -$30 (debit)
Entry 2: Customer account +$30 (credit)
Sum = $0 ✓
Database schema:
CREATE TABLE ledger_entries (
id BIGINT PRIMARY KEY,
transaction_id UUID NOT NULL, -- groups related entries
account_id BIGINT NOT NULL,
amount DECIMAL(20,2) NOT NULL, -- positive=credit, negative=debit
currency CHAR(3) NOT NULL,
entry_type VARCHAR(20) NOT NULL, -- 'payment', 'refund', 'fee'
created_at TIMESTAMP NOT NULL,
-- APPEND-ONLY: no updates or deletes allowed
);
-- Account balance = SUM of all ledger entries for that account
-- Never store balance as a mutable field (derive from ledger)
Critical constraints:
- Ledger is APPEND-ONLY (never update or delete entries)
- Every transaction must have entries that sum to zero
- Use database transactions for atomicity
- Reconciliation job runs hourly to verify sumsStep 5: Payment Processing Flow
Full payment flow (authorize + capture pattern):
1. AUTHORIZE
Merchant → Our API → Fraud Check → Payment Gateway → Card Network → Bank
Bank reserves $100 on customer's card
Returns authorization code
No money moves yet
2. CAPTURE (within 7 days)
Merchant → Our API → Payment Gateway → Card Network
Bank transfers reserved $100
Money moves to settlement account
Ledger entries created
3. SETTLEMENT (next business day)
Batch process: aggregate captured payments
Transfer funds to merchant's bank account
Create ledger entries for settlement
Why separate authorize and capture?
- E-commerce: authorize at checkout, capture at shipment
- Hotels: authorize at booking, capture at checkout
- Reduces refund volume (cancel authorization instead)
Payment states:
CREATED → AUTHORIZED → CAPTURED → SETTLED
↓
VOIDED (cancel before capture)
↓
REFUNDED (after capture)Step 6: Fraud Detection
Fraud checks run before authorization:
Rule-based checks (fast, synchronous):
- Velocity: > 5 transactions in 1 minute → flag
- Amount: single transaction > $10,000 → manual review
- Geography: transaction from new country → step-up auth
- Card testing: many small amounts → block
ML-based scoring (parallel):
- Feature vector: amount, merchant category, time of day,
device fingerprint, IP geolocation, transaction history
- Model outputs risk score 0-100
- Threshold: > 80 → decline, 50-80 → 3DS verification, < 50 → approve
Response time budget:
- Total fraud check must complete in < 200ms
- Rule engine: < 10ms
- ML scoring: < 100ms (pre-loaded model)
- Parallel execution, take max of bothStep 7: Reliability Patterns
Payment systems use specific reliability patterns:
1. Saga Pattern (for multi-step transactions)
- Each step has a compensating action (undo)
- If step 3 fails: undo step 2, undo step 1
- Example: authorize → charge → settle. If charge fails, void authorization
2. Outbox Pattern (for reliable event publishing)
- Write to database AND outbox table in same transaction
- Separate process reads outbox and publishes events
- Guarantees: if payment saved, webhook WILL be sent
3. Dead Letter Queue
- Failed webhook deliveries go to DLQ
- Retry with exponential backoff (1min, 5min, 30min, 2hr, 24hr)
- Alert after all retries exhausted
4. Circuit Breaker
- If payment gateway is down, fail fast
- Don't queue payments (money is time-sensitive)
- Route to backup payment processor if availableKey Takeaways for the Interview
- Idempotency is non-negotiable: Lead with this — it shows you understand financial systems
- Double-entry ledger: Every money movement has two entries summing to zero
- Append-only: Never update or delete financial records. Corrections are new entries
- Authorize/Capture split: Shows you understand real-world payment flows
- Correctness over speed: This is the opposite of social media design — consistency matters most
Practice This on HireReady
Payment system design is asked at Stripe, Square, PayPal, Coinbase, and any fintech. Practice explaining idempotency and ledger design with our AI voice interviewer.