Design a Mission-Critical Payment Processing Platform Like Stripe
A mission-critical payment processing platform (e.g., Stripe, Adyen, PayPal) enables online businesses to accept credit cards, digital wallets, and bank transfers safely and reliably. In payments, there is zero tolerance for data loss, double charging, or inconsistent balances. The platform requires strict end-to-end idempotency, double-entry financial ledger accounting, Payment Service Provider (PSP) orchestration, and asynchronous daily bank reconciliations.
1. Understanding the Problem
Functional Requirements
- Process Card Payment: Charge a customer's credit card, debit card, or digital wallet (Apple Pay, Google Pay).
- Idempotent Payment Execution: Guarantee that retrying a payment request (e.g. during mobile network drops or server timeouts) will never charge the customer twice.
- Double-Entry Accounting Ledger: Track every movement of funds (customer payment, platform processing fee, merchant payout) in an immutable double-entry ledger.
- PSP Routing & Redundancy: Route transactions to optimal Payment Service Providers / Acquirers (Visa, Mastercard, Chase Paymentech) with automated failover.
- Reconciliation Engine: Asynchronously reconcile internal ledger records against daily bank settlement statement files to detect discrepancies.
Non-Functional Requirements
- Strict Exactly-Once Financial Semantics: Overcharging or double-crediting money causes immediate regulatory penalties, merchant churn, and financial loss.
- High Availability:
99.999%uptime. A payment outage directly stops all business revenue. - Sub-Second Authorization Latency: Payment authorization must complete in
< 1.5 seconds(P95). - PCI-DSS Level 1 Compliance: Cardholder PAN and CVV must never touch general application servers; handled entirely by tokenized payment vaults.
Capacity Estimations & Sizing
- Daily Transaction Volume: 50 Million payments per day.
- Throughput:
- 580 payments/sec average (peaking at 3,000 payments/sec during Black Friday / Cyber Monday).
- Storage Calculation (5 Years):
- 50M payments/day 365 days 5 years 91 Billion transactions.
- Payment record:
payment_id(16 bytes) +customer_id(16 bytes) +amount(8 bytes) +status(8 bytes) + timestamps 256 bytes. - Ledger entries: 4 double-entry postings per payment 128 bytes 512 bytes.
- Total Storage = 91B 768 bytes 70 Terabytes (TB) stored across horizontally partitioned relational databases (PostgreSQL / CockroachDB).
2. The Set Up
Defining the Core Entities
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PAYMENT β
ββββββββββββββββββββ¬βββββββββββββββ¬βββββββββββββββββββββββ€
β payment_id β UUID β PRIMARY KEY β
β idempotency_key β VARCHAR(64) β UNIQUE, NOT NULL β
β merchant_id β UUID β INDEX, FK β
β customer_id β UUID β INDEX, FK β
β amount_cents β BIGINT β In smallest currency β
β currency β CHAR(3) β USD, EUR, JPY β
β status β VARCHAR(16) β PENDING / AUTHORIZED β
β β β CAPTURED / FAILED β
β psp_reference β VARCHAR(128) β Acquirer Trans ID β
β created_at β TIMESTAMP β NOT NULL β
ββββββββββββββββββββ΄βββββββββββββββ΄βββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β LEDGER_POSTING β
ββββββββββββββββββββ¬βββββββββββββββ¬βββββββββββββββββββββββ€
β posting_id β UUID β PRIMARY KEY β
β payment_id β UUID β INDEX, FK β
β account_id β UUID β Internal Account ID β
β entry_type β VARCHAR(6) β DEBIT / CREDIT β
β amount_cents β BIGINT β Financial Amount β
β currency β CHAR(3) β USD, EUR β
β created_at β TIMESTAMP β Immutable audit time β
ββββββββββββββββββββ΄βββββββββββββββ΄βββββββββββββββββββββββ
The API Design
Charge Customer Payment Requestβ
POST /api/v1/payments/charge
Content-Type: application/json
Authorization: Bearer <secret_key>
Idempotency-Key: pay_req_88192a01-b201-4412
{
"amount_cents": 10000, // $100.00
"currency": "USD",
"payment_method": "tok_visa_4242",
"merchant_id": "mch_991203",
"description": "Enterprise Subscription - Monthly"
}
Response (200 OK):
{
"payment_id": "pay_99a81203",
"status": "CAPTURED",
"amount_cents": 10000,
"currency": "USD",
"receipt_url": "https://pay.stripe.com/receipts/pay_99a81203"
}
3. High-Level Design
Walkthrough of the Payment Execution Lifecycle
1. Ingestion & Idempotency Layerβ
- Client issues
POST /api/v1/payments/chargewith a uniqueIdempotency-Key. - The Payment Gateway acquires a distributed lock in Redis on
idempotency_key:- Case A: Key Already Exists with Status
SUCCESS: Returns the cached previous HTTP response immediately without re-processing! - Case B: Key Already Exists with Status
IN_PROGRESS: Returns409 Conflictor waits for in-flight transaction to finish. - Case C: New Key: Creates a new record in PostgreSQL with
status: PENDING.
- Case A: Key Already Exists with Status
2. Risk & PSP Orchestrationβ
- Fraud Engine (Radar): Scores transaction risk using ML models (IP geolocation, card velocity, device fingerprinting).
- PSP Router: Selects the optimal acquiring bank / card network (e.g. Chase for US Visa, Adyen for European SEPA) based on fee optimization and current bank health metrics.
- Invokes the chosen PSP API:
- Auth (Authorization): Reserves the $100.00 on customer's credit line.
- Capture: Commits the funds transfer.
3. Double-Entry Ledger Commitmentβ
- Upon PSP success confirmation, the Ledger Service executes an atomic database transaction:
- Sets
PAYMENTstatus toCAPTURED. - Writes immutable double-entry postings:
DEBIT: Customer Card Asset Account (-$100.00)CREDIT: Merchant Payable Account (+$97.10)CREDIT: Stripe Processing Fee Account (+$2.90)- Verification: Debits (97.10 + \equiv 0$.
- Sets
- The Redis idempotency lock is updated with the completed response payload (cached with a 24-hour TTL).
- Emits
PaymentSucceededEventto Apache Kafka for customer receipt emailing.
4. Potential Deep Dives & Bottlenecks
Deep Dive 1: The Idempotency Key Pattern (Under the Hood)
What happens if the customer taps "Pay $100", the server charges the card at Visa, but the customer's mobile cellular connection drops before receiving the HTTP response?
Client App Payment Server Visa Acquirer
β β β
ββββ 1. POST /charge (Key: K1) βββββββββββββΊβ β
β ββββ 2. Auth & Capture ($100) ββββββΊβ
β β β
β ββββ 3. Visa Success (Auth code) ββββ
β β β
β β [Cellular Network Connection Drops!]
β X β
β [Client Times Out after 5s] β β
β β β
ββββ 4. RETRY: POST /charge (Key: K1) ββββββΊβ β
β β β
β β Detects Key K1 in Database! β
β β Status = ALREADY CAPTURED! β
β β (Does NOT call Visa again!) β
β β β
ββββ 5. Returns Existing Receipt ($100) βββββ β
β ZERO DOUBLE CHARGE! Transaction is 100% idempotent.
Production Implementation in PostgreSQL:β
CREATE TABLE idempotency_records (
idempotency_key VARCHAR(64) PRIMARY KEY,
user_id UUID NOT NULL,
request_hash CHAR(64) NOT NULL,
response_status INT,
response_body JSONB,
created_at TIMESTAMP NOT NULL,
locked_until TIMESTAMP NOT NULL
);
- If a duplicate request arrives with the same
idempotency_keybut a different request payload hash, the server rejects it immediately with400 Bad Request("Idempotency key reused with different parameters").
Deep Dive 2: Distributed Sagas vs Two-Phase Commit (2PC) in Payments
When orchestrating a payment involving internal wallet balances, external banks, and inventory deductions, why is 2PC avoided?
- Why Two-Phase Commit (2PC) Fails in Payments:
- 2PC is a blocking protocol. External acquiring banks (Chase, Visa) do not participate in our XA/2PC database transactions!
- If a network partition occurs during the prepare phase, resources remain locked indefinitely, causing cascading connection pool exhaustion.
- The Orchestrated Saga Pattern:
- The payment flow is executed as a sequence of independent local database transactions coordinated by a central Payment Orchestrator:
- Local Tx 1: Hold Inventory.
- Local Tx 2: Call Stripe API (External).
- Local Tx 3: Commit Ledger.
- Compensating Transactions: If Step 2 fails (e.g. Card Declined), the orchestrator executes a compensating rollback transaction: Release Inventory Hold.
- The payment flow is executed as a sequence of independent local database transactions coordinated by a central Payment Orchestrator:
Deep Dive 3: The Daily Bank Reconciliation Engine
How do payment companies detect if a bank silently lost a transaction or double-deducted processing fees?
Internal Ledger Database Acquiring Bank Settlement
(Stripe's Ledger_Posting Table) (Daily EOD CSV / MT940 File)
β β
ββββββββββββββββββββββββ¬ββββββββββββββββββββββ
βΌ
Batch Reconciliation Worker
β
ββββββββββββββββββββββββ΄ββββββββββββββββββββββ
βΌ βΌ
[Exact Matches (99.98%)] [Discrepancies (0.02%)]
- Amounts match - Missing at Bank
- PIDs match - Currency conversion drift
β Status: RECONCILED - Unknown fee deduction
β Flaggable for Manual Audit
- Every night at 00:00 UTC, partner banks generate a settlement batch file (CAMT.053 / BAI2 / CSV format) detailing every transaction settled through the Federal Reserve / ACH network.
- The Reconciliation Engine runs a distributed join between internal ledger entries and the bank statement:
- Discrepancies are routed to human operations queues for financial arbitration.
5. Architectural Trade-Off Matrix
| Design Area | Option A | Option B | Selected Choice & Rationale |
|---|---|---|---|
| Coordination | Two-Phase Commit (2PC) | Orchestrated Saga with Compensations | Orchestrated Saga: External bank APIs cannot participate in internal database locks. Sagas provide non-blocking asynchronous resiliency. |
| Accounting Model | Single Balance Column Mutation | Immutable Double-Entry Ledger Postings | Double-Entry Ledger: Mandatory for financial software. Guarantees mathematical balance integrity () and complete auditability. |
| Payment Ingestion | Asynchronous Kafka Buffering | Synchronous HTTP Gateway with Idempotency | Synchronous HTTP: E-commerce customers expect instant feedback ("Card Approved"). The gateway executes authorization synchronously while offloading receipts and analytics asynchronously. |
6. What is Expected at Each Level?
Mid-Level (L4 / IC4)
- Understands the necessity of the Idempotency Key to prevent double charging.
- Designs relational schemas for Payments, Customers, and Merchants.
- Explains the difference between Payment Authorization and Payment Capture.
- Proposes basic retry logic with exponential backoff.
Senior (L5 / IC5)
- Details the complete Idempotency Key lifecycle (Redis lock PostgreSQL state machine payload hash verification).
- Implements the Double-Entry Bookkeeping ledger with strict balance conservation rules.
- Explains the Orchestrated Saga pattern and compensating rollback transactions.
- Designs the daily automated bank settlement reconciliation engine.
Staff+ (L6 / Principal)
- Evaluates multi-PSP dynamic routing: Optimizing transaction authorization rates in real-time based on bank uptime telemetry and interchange fees.
- Architects PCI-DSS Level 1 tokenization vaults: Isolating raw card numbers (PANs) into air-gapped cryptographic hardware security modules (HSMs).
- Solves foreign exchange (FX) currency settlement volatility: Managing currency conversion rate locking between authorization time and settlement time across cross-border transactions.
