Design a Payment System
Design a payment system for interviews: payment flow, idempotency keys, ledger data model, retries, reconciliation, fraud checks, scaling, and tradeoffs.
Quick answer: Design a Payment System
Design a Payment System is covered here as a practical system design interview problem. Design a payment system for interviews: payment flow, idempotency keys, ledger data model, retries, reconciliation, fraud checks, scaling, and tradeoffs. Use it to practice requirements, APIs, storage choices, scaling bottlenecks, data consistency, observability, and failure handling for a medium interview round.
Focus on the problem being solved, the main design choice, the failure mode, and the tradeoff you would defend in front of a senior engineer. This page is part of the DosiBridge system design knowledge graph, so it links related concepts, interview problems, tradeoffs, and references instead of leaving the topic isolated.
Problem Statement
Design a Payment System as a scarce or money-moving workflow where duplicate writes and race conditions are more dangerous than slow responses. For Design a Payment System, name the critical user action, the data that must remain correct, and the first bottleneck that appears when traffic grows.
Deep-Dive: Clarifying Questions for Payment System
- What payment methods? Credit/debit cards (Visa, Mastercard), bank transfers (ACH), digital wallets (Apple Pay, Google Pay), cryptocurrency? Each has different processing pipelines and settlement times.
- What is the transaction volume? Stripe processes billions of dollars per year. PayPal handles ~40 million transactions per day. Visa processes 65,000 transactions per second at peak.
- How do we prevent double-charging? Idempotency is non-negotiable. If a client retries a payment request (due to timeout), the system must guarantee the payment is processed exactly once.
- How do we handle refunds? Partial refunds, full refunds, and time-limited refund windows (e.g., 180 days for chargebacks).
- What compliance requirements? PCI DSS for card data, SOX for financial reporting, PSD2/SCA for European payments. These are not optional — they are legal requirements.
- How do we handle currency? Multi-currency support with real-time exchange rates. Never use floating-point for money — use integers (cents) or decimal types.
Specific Functional Requirements
- Payment Processing: Accept payments via multiple methods (card, bank transfer, wallet) with real-time authorization
- Idempotent Transactions: Every payment request includes an idempotency key to prevent duplicate charges
- Refunds: Support full and partial refunds with audit trail
- Ledger: Double-entry bookkeeping ledger recording every financial movement (debits and credits must always balance)
- Fraud Detection: Real-time scoring of transactions using ML models (velocity checks, geographic anomalies, device fingerprinting)
- Settlement: Batch settlement with payment processors and bank accounts on a daily/weekly schedule
- Webhooks: Notify merchants of payment events (succeeded, failed, refunded, disputed)
Specific API Endpoints
POST /api/v1/payments
Headers: { "Idempotency-Key": "unique_request_id_abc123" }
Body: { "amount": 2999, "currency": "usd", "payment_method_id": "pm_card_visa", "merchant_id": "m_456", "metadata": { "order_id": "ord_789" } }
Response: { "payment_id": "pay_abc", "status": "succeeded", "amount": 2999, "currency": "usd", "created_at": "..." }
POST /api/v1/refunds
Headers: { "Idempotency-Key": "refund_req_xyz" }
Body: { "payment_id": "pay_abc", "amount": 1500, "reason": "customer_request" }
Response: { "refund_id": "ref_def", "status": "pending", "amount": 1500 }
GET /api/v1/payments/:payment_id
Response: { "payment_id": "pay_abc", "status": "succeeded", "amount": 2999, "refunds": [...], "timeline": [...] }
GET /api/v1/ledger/balance?merchant_id=m_456¤cy=usd
Response: { "available": 150000, "pending": 25000, "currency": "usd" }
Specific Data Model
Payments (PostgreSQL — strong consistency required)
| Column | Type | Notes |
|---|---|---|
| payment_id | UUID | Primary key |
| idempotency_key | VARCHAR(255) | UNIQUE — prevents duplicate processing |
| merchant_id | VARCHAR | |
| amount | BIGINT | In smallest currency unit (cents) — NEVER use float |
| currency | VARCHAR(3) | ISO 4217 code |
| status | ENUM | pending, authorized, captured, failed, refunded |
| payment_method | JSONB | Tokenized card/bank details (never store raw card numbers) |
| created_at | TIMESTAMP | |
| updated_at | TIMESTAMP |
Ledger Entries (PostgreSQL — append-only, double-entry)
| Column | Type | Notes |
|---|---|---|
| entry_id | UUID | Primary key |
| payment_id | UUID | Reference |
| account_id | VARCHAR | Which account (merchant, platform, reserve) |
| type | ENUM | debit, credit |
| amount | BIGINT | Always positive; direction determined by type |
| balance_after | BIGINT | Running balance for reconciliation |
| created_at | TIMESTAMP | Immutable — ledger entries are never updated or deleted |
Idempotency Store (Redis + PostgreSQL): Redis for fast lookup (TTL: 24 hours), PostgreSQL for persistence. Key = idempotency_key, Value = payment_id + response. If a duplicate request arrives, return the stored response without reprocessing.
Specific Back-of-the-Envelope Numbers
Traffic (Stripe-scale):
- ~100M transactions/day = ~1,200 transactions/second average, 5,000/second peak
- Each transaction: 1 write (payment record) + 2 ledger entries (debit + credit) + 1 webhook = 4 DB operations
- Database write rate: ~5,000 writes/second at peak
Storage:
- Payment records: 100M/day * 500 bytes = 50 GB/day = 18 TB/year
- Ledger entries: 200M/day * 200 bytes = 40 GB/day = 14.5 TB/year
- Must retain financial records for 7+ years (regulatory) = 100+ TB of historical data
Latency targets:
- Payment authorization: under 2 seconds end-to-end (includes network call to card network)
- Idempotency check: under 5ms (Redis lookup)
- Webhook delivery: within 30 seconds of event
Availability:
- Payment systems target 99.999% availability (5.26 min downtime/year)
- Every second of downtime = lost revenue for merchants
- Requires active-active deployment across multiple regions with automatic failover
Original DosiBridge notes for Design a Payment System
This section is written specifically for this page, not copied from a general system-design checklist. The useful way to study Design a Payment System is to connect the concept to a scarce or money-moving workflow where duplicate writes and race conditions are more dangerous than slow responses. That framing changes the design discussion from definitions to operating choices.
What makes this topic distinct
- Core Entity: For Design a Payment System, decide the core entity before choosing tools; otherwise the design looks clean but fails when payment changes.
- Read/Write Path: For Design a Payment System, decide the read/write path before choosing tools; otherwise the design looks clean but fails when system changes.
- Contention Point: For Design a Payment System, decide the contention point before choosing tools; otherwise the design looks clean but fails when payment changes.
- Abuse Path: For Design a Payment System, decide the abuse path before choosing tools; otherwise the design looks clean but fails when system changes.
- Recovery Workflow: For Design a Payment System, decide the recovery workflow before choosing tools; otherwise the design looks clean but fails when payment changes.
Design a Payment System correctness boundary
For this problem, separate the user-visible action from background work. The correctness boundary is the smallest state transition that must not be duplicated, lost, or reordered. Naming that boundary makes the solution more original than a stock API/database/cache template.
Design a Payment System scaling bottleneck
The first bottleneck should be described in the language of this system: a hot timeline, a contested seat, a busy wallet ledger, a media-processing queue, or a search index update delay. That bottleneck decides which component deserves the deepest interview discussion.
Production review question
If Design a Payment System suddenly receives 10x traffic, which entity becomes contested first, and what queue, lock, shard, or cache protects that entity without hiding correctness bugs?
Practical metric to track
Track payment-system health as a named dashboard or review item: combine one user-facing metric, one saturation metric, and one correctness metric. For Design a Payment System, this keeps the discussion grounded in behavior that can be measured after deployment instead of generic advice that sounds good in an interview but never gets verified.