100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace

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.

hardQ40 of 224 in System Design Est. time: 6 minsLast updated:
Open Code Lab

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

  1. 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.

  2. 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.

  3. 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.

  4. 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

Idempotent payment authorization with a double-entry ledger write
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")
        raise

Follow-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.

1 / 4

Continue Learning