How Would You Design a Payment System?
Learn how to design a payment system covering idempotency keys, double-entry ledgers, sagas, and reconciliation for interviews.
Expected Interview Answer
A payment system is designed around an idempotent, append-only ledger where every state transition (authorize, capture, settle, refund) is recorded as an immutable event, with idempotency keys preventing double-charges and a saga-style workflow coordinating the payment gateway, risk checks, and the bank rail.
The core is a ledger service that treats money movement as double-entry bookkeeping: every transaction debits one account and credits another, and the sum across all accounts always nets to zero, which makes reconciliation and auditing tractable. Clients call a payment API with a client-generated idempotency key, so retries from network timeouts never double-charge the card. Because a payment touches multiple external systems (card network, fraud engine, bank), the flow is modeled as a saga with explicit compensating actions — if capture succeeds but settlement fails, the saga triggers a reversal rather than leaving money in an inconsistent state. Strong consistency is required for balances, so the ledger typically lives in a relational database with serializable transactions, while asynchronous webhooks and event streams propagate status changes to downstream services like notifications and accounting without blocking the critical path.
- Idempotency keys eliminate duplicate charges on retries and network failures
- Double-entry ledger makes every dollar traceable and reconciliation automatic
- Saga pattern keeps multi-step payment flows consistent even when a step fails
- Async event propagation keeps the payment API fast while other systems stay in sync
AI Mentor Explanation
Designing a payment system is like running the official scorebook where every run scored must be entered exactly once, even if the scorer’s radio message gets repeated due to static — a duplicate broadcast of the same ball must never add a second run. The scorebook also balances like a ledger: runs added to the batting side are mirrored in the bowling figures, so the two totals always reconcile at the end of the innings. If a run is later revoked (a no-ball overturned on review), a correcting entry is made rather than erasing history, preserving the full audit trail. That combination of duplicate-proof entries and a self-balancing double record is exactly what a payment ledger provides.
Step-by-Step Explanation
Step 1
Client submits with an idempotency key
The client generates a unique key per logical payment attempt; the API stores it so any retry returns the original result instead of re-processing.
Step 2
Authorize against the card network
The gateway calls the card network or bank to place a hold on funds, without yet moving money, and records a pending ledger entry.
Step 3
Run risk and fraud checks
A risk engine scores the transaction in parallel or before authorization, and can block or flag it before funds ever move.
Step 4
Capture, settle, and reconcile
On success the hold is captured, the double-entry ledger posts the final debit/credit, and async settlement batches reconcile against the bank statement, with compensating reversals for any failed step.
What Interviewer Expects
- Explains idempotency keys and why naive retries cause double charges
- Describes a double-entry ledger and why balances must always reconcile to zero
- Uses the saga pattern (or equivalent) with explicit compensating transactions for multi-step failures
- Distinguishes strongly consistent ledger writes from asynchronous downstream propagation (webhooks, notifications)
Common Mistakes
- Forgetting idempotency keys, leading to double-charge risk on client retries
- Treating balance updates as a single mutable number instead of an append-only ledger
- Not handling partial failure across authorize/capture/settle with compensating actions
- Storing payment card data directly instead of tokenizing via a PCI-compliant vault or processor
Best Answer (HR Friendly)
“I would build a payment system around a ledger that records every transaction as a paired debit and credit, so the books always balance and nothing can be silently lost. To stop accidental double-charges from network retries, every request carries a unique key so a repeat is recognized and ignored. And because a single payment touches several external systems like the bank and fraud checks, I would design it so that if any step fails partway through, the system automatically reverses what already happened instead of leaving things in a broken state.”
Code Example
def authorize_payment(idempotency_key, account_id, amount_cents, currency):
existing = ledger_db.find_by_idempotency_key(idempotency_key)
if existing is not None:
return existing # safe replay, no duplicate charge
with ledger_db.transaction(isolation="serializable"):
hold = payment_gateway.authorize(account_id, amount_cents, currency)
entry = ledger_db.insert_entry(
idempotency_key=idempotency_key,
debit_account=f"customer:{account_id}",
credit_account="pending_holds",
amount_cents=amount_cents,
currency=currency,
status="authorized",
gateway_ref=hold.id,
)
return entry
def capture_payment(entry_id):
entry = ledger_db.get(entry_id)
try:
payment_gateway.capture(entry.gateway_ref)
ledger_db.update_status(entry_id, "captured")
except GatewayError:
# compensating action: reverse the hold, keep the ledger balanced
ledger_db.insert_reversal(entry_id, reason="capture_failed")
raiseFollow-up Questions
- How would you prevent a payment from being captured twice if two servers process the same webhook concurrently?
- How do you handle a partial refund on an order that was paid with two different payment methods?
- Where would you store card data, and why should your own database never touch raw PANs?
- How would you reconcile your internal ledger against the bank’s end-of-day settlement file?
MCQ Practice
1. What is the primary purpose of an idempotency key in a payment API?
An idempotency key lets the server recognize a retried request and return the original result instead of processing the payment again.
2. In a double-entry payment ledger, what must always be true across all accounts?
Double-entry bookkeeping requires every debit to be matched by an equal credit elsewhere, so the system always nets to zero.
3. What is the role of a compensating transaction in a payment saga?
When a multi-step payment flow fails partway through, a compensating transaction reverses earlier successful steps to keep the system consistent.
Flash Cards
Why use an idempotency key in payments? — To guarantee a retried request never results in a duplicate charge.
What is a double-entry ledger? — A record where every transaction debits one account and credits another, always netting to zero.
What is a saga in payment systems? — A multi-step workflow with compensating actions that reverse prior steps if a later step fails.
Why should raw card numbers never be stored directly? — PCI compliance requires tokenizing card data via a vault or processor instead of storing raw PANs.