· software-engineers Editorial · Career  · 7 min read

Swe System Design Payment Processing System

Design a payment processing system for interviews: idempotency, double-entry ledgers, and failure recovery.

Why Payment Systems Are the Hardest System Design Question in 2026

Payment processing system design questions have become one of the most common senior and staff-level system design prompts at fintech companies (Stripe, Plaid, Brex) and increasingly at general-purpose big tech (Amazon, Uber, Airbnb checkout flows). Unlike a URL shortener or a news feed, a payment system cannot tolerate eventual consistency for money movement — a duplicate charge or a lost transaction is a legal and financial liability, not just a bad user experience.

This is exactly why the question is hard: candidates who default to “just use a message queue and eventual consistency, it’ll be fine” get filtered out immediately. Interviewers are testing whether you understand idempotency, exactly-once semantics (which don’t really exist, only effectively-once), double-entry accounting, and distributed transaction patterns like the saga pattern.

If you want a structured way to practice this exact category of question with worked examples, The 0-to-1 SWE Interview Playbook (https://www.amazon.com/dp/B0H256Z1MF?tag=sirjohnnymai-20) includes a full payments case study you can use as a template.

Core Requirements Gathering: What to Clarify Before Drawing Boxes

Before touching a whiteboard, clarify these with the interviewer — skipping this step is the single biggest reason candidates lose points on this question:

  • Scope: Are you designing the full payment gateway (like Stripe) or a merchant’s integration layer that calls out to a payment processor?
  • Payment methods: Cards only, or also ACH, wallets, and international rails (which have different settlement times and failure modes)?
  • Consistency requirement: Must reflect the actual regulatory requirement — money movement needs strong consistency for the ledger, even if UI-facing status can be eventually consistent.
  • Scale: Transactions per second, peak-to-average ratio (Black Friday spikes matter a lot here), and geographic distribution.
  • Failure tolerance: What happens if the downstream card network times out — do you retry, and if so, how do you avoid double-charging?

Architecture: The Core Components

A production-grade payment system generally has five core components:

1. API Gateway / Payment Intent Service — receives the charge request, validates it, and creates a PaymentIntent record with a unique idempotency key generated client-side (or accepted from the client) before any money movement begins.

2. Idempotency Layer — a fast key-value store (Redis or DynamoDB) that maps idempotency keys to request/response pairs. Any retried request with the same key returns the cached original response instead of re-processing. This is non-negotiable: network retries are guaranteed to happen (client timeout, mobile network drop, load balancer failover), and without this layer you will double-charge customers.

3. Ledger Service — the source of truth, implemented as an append-only double-entry ledger. Every transaction creates at least two entries (a debit and a credit) that must sum to zero. This is the same pattern real accounting systems use and it makes reconciliation and auditing tractable — you can always answer “where did this money go” by replaying the ledger.

4. Orchestrator (Saga Pattern) — coordinates the multi-step process of charging a card: reserve funds → call external processor → capture → settle → notify. Because this spans multiple services and an external network call to a card network, you cannot use a single ACID transaction. The saga pattern breaks it into a sequence of local transactions, each with a compensating action if a later step fails (e.g., “release reservation” compensates “reserve funds”).

5. Async Settlement & Reconciliation — batch jobs that reconcile your internal ledger against the actual bank/processor statements daily, flagging any discrepancy for manual review. This catches bugs that idempotency and sagas miss (e.g., a processor-side outage that silently drops a webhook).

Comparison Table: Design Choices and Their Tradeoffs

Design DecisionOption AOption BRecommended for Payments
Consistency model for ledgerEventual consistencyStrong consistency (single writer, synchronous replication)Strong consistency — ledger is source of truth
Duplicate request handlingRely on client-side dedup onlyServer-side idempotency key storeServer-side idempotency key store
Cross-service transaction2-phase commit (2PC)Saga pattern with compensating actionsSaga pattern (2PC doesn’t scale, blocks on network partitions)
Charge status deliverySynchronous response onlySynchronous ack + async webhookBoth — sync ack for immediate UX, webhook for final state
Data store for ledgerNoSQL document storeRelational DB with ACID transactionsRelational DB (Postgres/MySQL) for ledger integrity
Retry strategy on processor timeoutImmediate retryExponential backoff + idempotency key reuseExponential backoff, same idempotency key
Fraud check timingAfter charge succeedsBefore charge, synchronous risk scoringBefore charge, with async deep scoring post-charge

Handling the Hard Failure Cases

The differentiator between a mid-level and senior answer is how you handle failure, not the happy path. Walk through these explicitly:

Processor timeout, unknown outcome: Your service calls the card network, the request times out, but you don’t know if the charge succeeded on their end. You must NOT blindly retry with a new request — this could double-charge. Instead, retry with the same idempotency key, and if the processor also supports idempotency keys (most do, including Stripe and Adyen), they’ll return the original result. If they don’t, you query a status/reconciliation endpoint before retrying.

Partial saga failure: You reserved funds, called the processor successfully, but the “notify merchant” step fails. The saga orchestrator must persist state transitions durably (e.g., in the ledger or a dedicated saga state table) so a crashed orchestrator can resume from the last completed step on restart, rather than restarting the whole flow and re-charging.

Double-entry mismatch: If your debit and credit entries in the ledger don’t sum to zero after a batch job runs, that’s a P0 alert — it usually indicates either a bug in the orchestrator or a race condition in concurrent writes. Design the ledger table with a database-level check constraint or trigger where feasible to catch this at write time, not just in nightly batch reconciliation.

Scaling Considerations for High Throughput

At high transaction volume, the ledger’s single-writer requirement becomes a bottleneck. Common mitigations:

  • Sharding by account ID so writes to unrelated accounts don’t serialize against each other, while still guaranteeing strong consistency within a shard.
  • Write-ahead batching at the database layer to amortize commit overhead — but be careful this doesn’t compromise the durability guarantee for individual transactions.
  • Read replicas for reporting so analytics and dashboard queries never contend with the transactional write path.
  • Rate limiting and circuit breakers on the external processor call so a slow card network doesn’t cascade into your whole system backing up.

FAQ

Q: Do I need to mention double-entry accounting even if the interviewer just asked for “a payment system”? A: Yes, strongly recommended. It’s the single detail that most separates senior candidates from mid-level ones on this question — it signals you understand that a payment system is fundamentally an accounting system with an API on top, not just a CRUD app that happens to move money.

Q: Should I use 2PC or sagas for cross-service consistency? A: Sagas, in almost all real-world payment systems. 2PC requires all participants (including the external card network, which you don’t control) to support a blocking commit protocol, which is unrealistic across organizational boundaries. Mention 2PC only to explain why you’re rejecting it.

Q: How deep should I go on idempotency keys? A: Deep enough to explain the full lifecycle: client generates or the server issues a key, the key maps to a stored request hash and response, requests with the same key but different payloads should return a conflict error (not silently reuse the cached response), and keys should expire after a bounded window (e.g., 24 hours) to bound storage growth.

For more worked system design examples with this level of failure-mode depth, see The 0-to-1 SWE Interview Playbook (https://www.amazon.com/dp/B0H256Z1MF?tag=sirjohnnymai-20).

Back to Blog

Related Posts

View All Posts »