Design an Idempotent Payment System
Design an Idempotent Payment System
Financial payment systems must guarantee that money is never created or destroyed out of thin air, duplicate charges never occur during network timeouts, and every single transaction is immutably audited via double-entry bookkeeping.
1. System Requirements
Functional Requirements
- Pay-in Processing: Accept customer payment methods (credit card, bank transfer) and process charge authorizations.
- Double-Entry Financial Ledger: Maintain an immutable accounting record where total debits equal total credits for every transaction.
- Third-Party Payment Service Provider (PSP) Integration: Dispatch external API calls to providers like Visa, Mastercard, or Stripe.
- Reconciliation Engine: Asynchronously compare internal transaction records with daily bank settlement files to catch discrepancies.
- Refunds & Chargebacks: Support full and partial refunds with reversible ledger entries.
Non-Functional Requirements
- Strict Idempotency: Guarantee exactly-once payment processing even when network retries occur.
- Zero Financial Discrepancies: Total ledger consistency; transactions must satisfy ACID guarantees.
- High Availability: 99.999% uptime for the payment ingestion gateway.
- Auditability: Every modification must be traceable to a cryptographically verifiable transaction log.
2. Core Concepts: The Double-Entry Ledger
In financial accounting, money does not simply change a single balance column. Instead, every movement of capital is represented as two offsetting entries: $$\sum \text{Debits} = \sum \text{Credits}$$
Example: Customer Buys a $100 Item
Account | Debit | Credit
---------------------------|---------|---------
Customer Cash Holding | | $100.00
Platform Escrow Holding | $100.00 | Ledger entries are strictly append-only. Corrections are made by creating a new reversing entry, never by modifying past database rows.
3. High-Level Architecture Blueprint
+-----------------------+
| Checkout UI |
+-----------+-----------+
|
| POST /payments (with Idempotency-Key)
v
+-----------------------+
| Payment Gateway |
| (Rate Limit & Verify) |
+-----------+-----------+
|
v
+-----------------------+
| Payment Service |
+-----+-----------+-----+
| |
+--------------------------+ +--------------------------+
| |
v (1. Acquire Lock & Check State) v (2. Insert Pending)
+------------------------+ +------------------------+
| Redis Distributed | | PostgreSQL Primary DB |
| Lock (Redlock) | | (Orders & Payments) |
+------------------------+ +-----------+------------+
|
v (3. Enqueue Outbox)
+------------------------+
| Transactional Outbox |
| & Kafka Task Queue |
+-----------+------------+
|
v
+------------------------+
| Payment Execution Svc |
+-----------+------------+
|
v Call External API
+------------------------+
| Third-Party PSP (Visa) |
+------------------------+4. Staff-Level Deep Dives
Deep Dive 1: End-to-End Idempotency Pattern
When a user clicks "Pay", mobile networks can drop right after the credit card is charged, prompting the user to hit "Pay" again.
- Idempotency Key Generation: The client generates a unique UUID (e.g.
Idempotency-Key: c9b8a...) prior to submitting the payment. - Atomically Claim Key: The server queries the database within a transaction:
INSERT INTO payment_idempotency_keys (key, user_id, status)
VALUES ('c9b8a...', 1234, 'PROCESSING')
ON CONFLICT (key) DO NOTHING;- If the insert succeeds, this is a fresh request; proceed.
- If conflict occurs:
- If status is
PROCESSING: Return HTTP 409 Conflict ("Transaction in progress, please wait"). - If status is
COMPLETED: Return the previously cached response payload immediately without charging the card again.
Deep Dive 2: The Two-Phase PSP Execution (Auth & Capture)
Never debit an internal account before the card network approves, but never approve without recording the intent.
- Authorization: Holds the funds on the customer's card (e.g. 7-day reserve).
- Capture: Transfers the authorized funds into the merchant's merchant account after the order is fulfilled.
Deep Dive 3: The Reconciliation Engine
- External banks deliver end-of-day settlement files (BAI2, MT940, CSV).
- An automated batch pipeline joins internal ledger entries with bank settlement rows.
- Discrepancies are categorized:
- Timing Differences: Transactions settled on different calendar days.
- Missing Fees: Unexpected interchange fee deductions.
- Unreconciled Losses: Charges present in bank records but missing from the internal database (triggered by failed webhooks). Escalated to operations.
Key Architecture Trade-Offs & Decision Matrix
| Decision Factor | Choice A | Choice B | Selected Architectural Rationale |
|---|---|---|---|
| Communication Protocol | REST over HTTP/2 | WebSockets / gRPC | Use REST for stateless CRUD APIs; use WebSockets for low-latency bidirectional streaming. |
| State Management | Centralized Distributed Store | Local Memory with Sticky Sessions | Centralized Redis / Database state allows seamless container auto-scaling and zero-downtime rolling deploys. |
| Concurrency Control | Optimistic Locking (version column) | Pessimistic Locking (SELECT FOR UPDATE) | Optimistic locking delivers superior throughput for low-conflict consumer operations. |
Related Topics
Expand your knowledge by learning about adjacent concepts in system design.
Consistent Hashing Architecture & Implementation
Deep dive into consistent hashing algorithms, virtual nodes, token rings, and minimal key remapping during cluster resizing in distributed databases.
Design a URL Shortener (TinyURL / Bitly Architecture)
Design a scalable URL shortening service capable of handling billions of redirects: Base62 encoding, distributed ID generation, database indexing, and 301 vs 302 redirect trade-offs.
Dealing with High Concurrency & Contention
Strategies for mitigating hot keys, race conditions, and lock contention: Optimistic Concurrency Control, Pessimistic Locking, Distributed Redlock, and Write Coalescing.
Cheat Sheet Utility
View and print a concise system design reference card.