Design a Payment System — From Checkout to Settlement
Why payments are the hardest system design problem#
Money can't be lost, duplicated, or delayed. Payment systems demand correctness that most systems don't need — exactly-once processing, audit trails, and regulatory compliance.
This is why "design a payment system" is a senior-level system design question.
The payment flow#
1. Checkout initiation#
User clicks "Pay $49.99" → Your frontend creates a payment intent via your API.
2. Payment authorization#
Your payment service calls the card network (Visa, Mastercard) through a payment processor to verify:
- Card is valid and not expired
- Sufficient funds or credit limit
- 3D Secure / SCA authentication if required
The processor returns an authorization code — funds are held but not yet transferred.
3. Capture#
After order fulfillment, you capture the authorized amount. This triggers the actual money movement.
Some businesses capture immediately (digital goods). Others authorize at checkout and capture at shipment (physical goods).
4. Settlement#
The processor batches captured transactions and settles with your bank. Funds arrive in your account 1-3 business days later.
5. Reconciliation#
Match your internal ledger against the processor's settlement reports. Catch discrepancies early.
Core components#
Payment Gateway — Encrypts card data, routes to the right processor. Handles PCI compliance at the network edge.
Payment Service — Orchestrates the payment flow: create intent → authorize → capture → settle. Manages state machine transitions.
Ledger — Double-entry accounting. Every transaction has equal debits and credits. This is your source of truth.
Fraud Engine — Risk scoring before authorization. Velocity checks (too many attempts), device fingerprinting, address verification.
Webhook Service — Notifies your application of async events: payment succeeded, refund processed, dispute opened.
Idempotency: the most critical requirement#
Network failures happen. The client might retry a payment request. Without idempotency, you'll charge the customer twice.
Solution: Every payment request includes an idempotency key (usually a UUID). The server checks if this key was already processed:
POST /payments
Idempotency-Key: pay_abc123
→ First request: Process payment, store result with key
→ Retry request: Return stored result, don't process again
Store idempotency keys with a TTL (24-48 hours). Use a unique constraint in your database.
Ledger design#
Double-entry bookkeeping — every transaction has two entries that balance:
Payment of $49.99:
Debit: Accounts Receivable $49.99
Credit: Customer Payment $49.99
Settlement to merchant:
Debit: Customer Payment $49.99
Credit: Merchant Payout $47.49
Credit: Platform Fee $2.50
The ledger is append-only. Never update or delete entries. Corrections are new entries that reverse the original.
Handling failures#
| Failure | Strategy |
|---|---|
| Network timeout during auth | Retry with idempotency key |
| Processor returns "decline" | Return error, don't retry |
| Capture fails after auth | Queue for retry, alert operations |
| Partial settlement | Reconciliation catches it, manual resolution |
| Double charge | Idempotency prevents it; if missed, auto-refund |
Fraud detection signals#
- Velocity — 5 attempts in 1 minute from same IP
- Geolocation — Card issued in US, transaction from Russia
- Device fingerprint — New device + high-value transaction
- AVS mismatch — Billing address doesn't match card
- BIN analysis — Prepaid cards, high-risk issuing banks
Score each signal. Block above threshold, flag for manual review in gray zone.
PCI compliance#
If you handle card numbers, you must comply with PCI DSS. The easiest approach:
Use tokenization. Never see the actual card number. Stripe, Adyen, or Braintree collect card data in their iframe/SDK. Your server only receives a token.
This reduces your PCI scope from 300+ controls to ~20 (SAQ A).
Scaling considerations#
- Idempotency store — Redis with TTL for fast lookups
- Ledger — Append-only PostgreSQL with partitioning by date
- Async processing — Capture and settlement via message queue
- Multi-region — Active-passive for disaster recovery (money systems can't go down)
- Rate limiting — Per-merchant and per-card to prevent abuse
Visualize your payment architecture#
See how payment gateway, processing, ledger, and fraud detection connect — try Codelit to generate an interactive diagram of a complete payment system.
Key takeaways#
- Idempotency is non-negotiable — every payment request needs a unique key
- Double-entry ledger — append-only, always balanced, source of truth
- Tokenize card data — never handle raw card numbers
- Authorize first, capture later — separate holds from actual charges
- Fraud scoring before auth — cheaper to block than to dispute
- Reconcile daily — catch discrepancies before they compound
Try it on Codelit
Chaos Mode
Simulate node failures and watch cascading impact across your architecture
90+ Templates
Practice with real-world architectures — Uber, Netflix, Slack, and more
Related articles
Try these templates
Uber Real-Time Location System
Handles 5M+ GPS pings per second using H3 hexagonal geospatial indexing.
6 componentsE-Commerce Checkout System
Production checkout flow with Stripe payments, inventory management, and fraud detection.
11 componentsNotification System
Multi-channel notification platform with preferences, templating, and delivery tracking.
9 componentsBuild this architecture
Generate an interactive architecture for Design a Payment System in seconds.
Try it in Codelit →
Comments