System design interview guide
Payment System Design: How to Design a Payment System
Short answer
A payment system moves money from a customer to a merchant without ever charging twice or losing a cent. Every request carries an idempotency key, so a retry is safe. Every money movement goes into a double-entry ledger whose rows add up to zero. A timeout leaves the payment pending, not failed. Every day, the ledger is reconciled against the provider and the bank.
This guide comes from our System Design course: 770 lessons, ₹499 once in India or $49 elsewhere, with lifetime access.
Businesses on Stripe moved $1.9 trillion in 2025 (Stripe annual letter, February 2026). At the 2025 Black Friday peak, Stripe handled more than 152,000 transactions a minute. That is about 2,500 every second. India's UPI handled 24.51 billion payments in August 2026, about 9,000 a second on average. At that scale, rare failures happen every minute. A timeout, a retry or a lost message can charge someone twice or lose money. The design exists to make that impossible.
A payment system takes money from a customer and gets it to a merchant. It never charges twice and never loses a cent. Speed is not the hard part. Correctness under failure is. The network can time out after the bank has already taken the money. A server can crash between two writes. A webhook can arrive twice, or out of order. So the design rests on a few ideas. Every request carries an idempotency key, so a retry is safe. Every money movement is written to a double-entry ledger, where debits and credits always add up to zero. A payment whose result is unknown stays pending. It is never marked failed by guesswork. Events are saved in the same transaction as the ledger write, so none are lost. Every day, the ledger is matched against the payment provider and the bank. Stripe, Razorpay, UPI and PayPal all follow this pattern on different rails.
Where it shows up
A standard question at payments companies such as Stripe, PayPal, Adyen, Razorpay and PhonePe. It also comes up at any company that takes money online. Think of e-commerce, ride-hailing, food delivery and subscription apps. It appears in other forms too. Design a payment gateway. Design a wallet. Design a checkout that never double charges.
Spec sheeta Payment System
- 01Payments per day (interview example)
- 10 million
- 02Average and peak writes
- ~120 per sec, ~1,200 at peak
- 03Ledger rows per day
- 20 million or more
- 04Ledger storage per year
- ~1.5 TB
worked through below, with the maths
Why this question is asked
Most system design questions forgive small mistakes. A like count can be wrong for a second. A payment cannot. So this question tests whether you think about failure first. Interviewers listen for a few things. Do you make retries safe with idempotency keys? Do you keep money in a ledger instead of one balance number? Do you know that a timeout does not mean failure? Do you reconcile with the bank every day? And do you know when to choose consistency over speed? A candidate who only draws boxes and arrows misses all of this.
The payment system design series
Every payment system rests on the same few ideas: an idempotency key so a retry never charges twice, a double-entry ledger so no money appears or vanishes, and daily reconciliation against the bank. These guides show those ideas inside real products.
- Design a payment system(this page)Start here: the core pattern every guide below uses
- Design StripeCard payments API, idempotency keys, webhooks
- Design RazorpayPayment gateway for India: UPI, cards, settlement
- Design UPIIndia's central payment switch run by NPCI
- Design PayPalWallet, ledger and sagas across services
- Design PaytmUPI app and wallet, the DEEMED state
- Design PhonePeUPI at national scale, sharded MySQL
- Design a digital walletBalances, holds and captures
- Design CREDBill payments driven by order state machines
- Design fraud detectionScoring a payment in milliseconds
Live from the course bench
Before you design Payment, try the questions it rests on.
Real interview questions, live diagrams and measured lab results, pulled at random from the lessons. A new set every time you come back.
Requirements
Always clarify these in the first 5 minutes of the interview. Do not start drawing boxes until both lists are agreed.
Functional requirements
- A customer pays a merchant by card, bank transfer, UPI or wallet
- The merchant can capture an authorized payment later, or cancel it
- Full and partial refunds
- The merchant is told about every result through webhooks
- Money is paid out to the merchant's bank account on a schedule
- Every payment can be traced from start to end, for support and audits
Non-functional requirements
- Never charge a customer twice, even when the client retries
- Never lose a payment, even when a server crashes mid-way
- Strong consistency for money; history and analytics can lag a little
- High availability: checkout is down means sales are lost
- Card numbers never touch most services, which keeps PCI DSS scope small
- A full audit trail: money records are added, never edited
Back-of-envelope scale estimates
Show your math. Pulling numbers from thin air signals you have not thought about the load.
Payments per day (interview example)
10 million
A good number to design for in an interview. It is large but not extreme. State it, then do the math from it.
Average and peak writes
~120 per sec, ~1,200 at peak
10 million divided by 86,400 seconds is about 116 a second. Sales and paydays can bring ten times that. This is an estimate.
Ledger rows per day
20 million or more
Each payment writes at least two postings: one debit and one credit. Fees, refunds and payouts add more. Estimate.
Ledger storage per year
~1.5 TB
20 million rows at about 200 bytes is about 4 GB a day. Times 365, that is about 1.5 TB a year. Estimate.
A real peak: Stripe, Black Friday 2025
152,000+ per minute
Stripe's own number for the 2025 Black Friday to Cyber Monday weekend. That is about 2,500 a second at peak.
A real average: UPI, August 2026
~9,000 per second
24.51 billion UPI payments in the month, from NPCI data. That is about 791 million a day.
High-level architecture
Start with the main path of one payment. The merchant's checkout calls your Payment API with an idempotency key. The API gateway checks the merchant's key and applies rate limits. The Payment Service checks the idempotency store first. If it has seen this key, it returns the saved answer and does nothing else. If the key is new, it saves the payment as PENDING before calling anyone. Then a risk service scores the payment for fraud. Next, a PSP adapter sends the payment to the outside world. That means a card network, a bank, or UPI. The answer can be approved, declined, or no answer at all. On a clear answer, one database transaction does three things. It updates the payment's state. It writes balanced rows to the ledger. It adds an event to an outbox table. A relay process reads the outbox and publishes events to a queue. From the queue, other services send webhooks, update the merchant's balance view, and feed analytics. Off the main path, two jobs keep everything honest. A status checker asks the provider about payments stuck in PENDING. A daily reconciliation job matches the ledger against the provider's report and the bank statement. Card numbers never enter this path at all. They go straight to a separate vault, and every other service sees only a token.
In a real interview, sketch this on the whiteboard before diving into any single box.
How a card payment flows, step by step
This is one card payment, from the click to the money in the merchant's bank. UPI and wallets change the rails in steps 6 and 9. The rest stays the same.
- 1
Checkout sends the request with an idempotency key
The merchant's server creates a payment and sends a unique key with it, such as a random UUID. If the call times out, it retries with the same key.
- 2
The card goes to a vault, not to your servers
The customer types the card into a field hosted by the payment provider. The card number goes to a secure vault. Your servers only see a token.
- 3
Check the idempotency key
Seen this key before? Return the saved answer and stop. A new key? Save it in the same database as the payment, so the two cannot disagree.
- 4
Save the payment as PENDING
Write the payment row before calling anyone outside. If the server crashes after this, the payment is still on record and a job can finish it.
- 5
Score it for fraud
A risk service looks at the card, the device, the amount and recent history. It returns allow, review or block in milliseconds.
- 6
Ask for authorization
The provider sends the request through the card network to the customer's bank. The bank approves or declines, and holds the money on approval.
- 7
Record the result, the ledger and the event together
One database transaction updates the payment, writes balanced ledger rows and adds an outbox event. Either all three are saved, or none are.
- 8
Capture, then tell the merchant
The payment is captured now or later, for example at shipping. A webhook tells the merchant. The merchant ships when the webhook arrives, not on the browser redirect.
- 9
Settle and pay out
Real money moves later, in batches. The card network settles with the provider, and the provider pays the merchant's bank on a schedule.
- 10
Reconcile every day
A job matches every ledger row against the provider's settlement report and the bank statement. Any mismatch becomes a ticket for a person to fix.
Core components
Walk through each service. The interviewer wants to hear what each one owns, not just the names.
Payment API and gateway
The front door. It checks the merchant's API key, applies rate limits and validates the request. Every write call must carry an idempotency key.
Payment Service
Owns the life of one payment as a state machine. States are PENDING, AUTHORIZED, CAPTURED, FAILED and REFUNDED. Only allowed moves between states are accepted.
Idempotency store
Remembers each key, a hash of the request, and the saved response. Keep it in the same database as the payments, so one transaction covers both.
Risk and fraud service
Scores each payment before money moves. It uses rules and a model. One feature might be how often this card was used in the last hour.
PSP adapters
One adapter per outside rail: a card processor, a bank, UPI. Each turns your request into that rail's format and turns its answer back into your states.
Ledger service
An append-only double-entry ledger. Every money movement is a set of rows that add up to zero. Balances are sums of rows, never a number you edit.
Outbox relay and event queue
Reads new rows from the outbox table and publishes them to a queue such as Kafka. Webhooks, notifications and analytics all read from that queue.
Webhook service
Delivers events to the merchant's URL with a signature. It retries with growing waits. Receivers must expect duplicates and events out of order.
Status checker
Finds payments stuck in PENDING and asks the provider for their real status. It moves each one to a final state. It never guesses.
Reconciliation service
Each day, it matches the ledger against the provider's settlement file and the bank statement. It flags missing, extra or different rows.
Card vault
A small, locked-down service that stores card numbers and gives out tokens. Only it is in full PCI DSS scope. Everything else sees tokens.
Data model
Pick the right store per table. Justify each choice with the access pattern, not by reflex.
paymentspayment_id (PK)merchant_idamount_minorcurrencystatuspsp_referencecreated_atupdated_atOne row per payment. Amounts are whole numbers in the smallest unit, such as cents or paise, never floats. Shard by merchant_id.
idempotency_keysmerchant_ididem_keyrequest_hashresponse_coderesponse_bodycreated_atUnique on (merchant_id, idem_key). The request hash catches a reused key with a different body. Old keys can be deleted after a day or more.
ledger_postingsposting_id (PK)txn_idaccount_idamount_minor (signed)currencyposted_atAppend-only. The postings of one txn_id must add up to zero. A correction is a new reversing posting, never an UPDATE.
accountsaccount_id (PK)owner_idtype (customer, merchant, fee, clearing)currencyEvery party and every in-between pot is an account. Clearing accounts hold money in flight. They should return to zero once it lands.
outboxevent_id (PK)aggregate_idevent_typepayloadcreated_atpublished_atWritten in the same transaction as the ledger. The relay sets published_at after it publishes. Consumers dedupe by event_id.
recon_breaksbreak_id (PK)run_datesource (psp, bank)external_refour_amounttheir_amountstatusOne row per mismatch found by reconciliation. Each one is worked by a person or a fix-up job until it is closed.
Deep dives
These are the conversations the interviewer is steering you toward. Practice each one until you can talk through it without notes.
Idempotency keys: the retry that must not charge twice
The client sends a payment. The server charges the card. Then the reply is lost on the network. The client sees a timeout and retries. Without protection, the customer pays twice. The fix is an idempotency key. The client makes one random key per payment and sends it on every retry. The server saves the key with the result of the first call. A retry with the same key gets the same answer, and no new charge. Stripe documents the details. Keys can be up to 255 characters. Stripe saves the status code and body of the first call, even a 500 error. A reused key with different parameters is rejected. Keys can be deleted once they are at least 24 hours old. Keep keys in the same database as the payment, so one transaction writes both.
-- one row per (merchant, key)
INSERT INTO idempotency_keys (merchant_id, idem_key, request_hash)
VALUES ($1, $2, $3)
ON CONFLICT (merchant_id, idem_key) DO NOTHING
RETURNING idem_key;
-- a row back: new key, process the payment
-- no row back: seen before.
-- same request_hash -> return the saved response
-- different request_hash -> reject the requestA timeout is not a failure: the unknown state
You call the bank. Thirty seconds pass with no reply. Did the money move? You do not know. If you mark it FAILED, the customer may retry and pay twice. If you mark it SUCCESS, you may ship goods that were never paid for. So the payment stays PENDING. A status checker asks the provider about it later, with backoff. The provider's answer moves it to a final state. If the provider still has no answer, reconciliation settles it the next day. UPI calls this the deemed state. Never retry a money call with a new key just because it timed out. Retry with the same key, or ask for the status.
The double-entry ledger: money never appears or vanishes
Do not store a balance as one number you edit. Store every movement as rows that add up to zero. A $100 card payment with a $3.20 fee is three rows. Minus 100.00 from card clearing. Plus 96.80 to the merchant. Plus 3.20 to fee revenue. A balance is the sum of an account's rows. Rows are never changed. A mistake is fixed with a new reversing row. This gives a full audit trail for free. It also makes errors easy to find. Stripe's Ledger uses clearing accounts that should return to zero. A missing or late transaction leaves a non-zero balance that one query can find. Stripe says its Ledger sees five billion events a day (Stripe, February 2024).
BEGIN;
INSERT INTO ledger_postings (txn_id, account_id, amount_minor, currency) VALUES
('t_81', 'card_clearing', -10000, 'USD'),
('t_81', 'merchant_pending', 9680, 'USD'),
('t_81', 'fee_revenue', 320, 'USD');
-- invariant: SELECT SUM(amount_minor) FROM ledger_postings
-- WHERE txn_id = 't_81'; -> 0
COMMIT;Never lose the event: the transactional outbox
After a payment succeeds, other services must hear about it. The merchant needs a webhook. The receipt email must go out. A tempting design writes the database, then publishes to Kafka. But the server can crash between the two. Then the payment exists and nobody is told. Or the event goes out, and the database write rolls back. The fix is the outbox. Write the event into an outbox table, in the same transaction as the ledger rows. A separate relay reads the outbox and publishes. The relay may publish an event twice, so every consumer must dedupe by event ID. That is the idempotent consumer pattern.
Webhooks arrive late, twice, or out of order
Webhooks are how a provider tells the merchant what happened. They are at-least-once. Stripe retries a failed delivery for up to three days in live mode, with growing gaps. Stripe also says it does not promise order. An invoice.paid event can arrive before invoice.created. And the same event can arrive twice. So a good receiver checks the signature first. It returns a 2xx quickly and does the real work from a queue. It records each event ID and skips IDs it has seen. When order matters, it fetches the current object from the API instead of trusting the event. And it fulfils the order from the webhook, not from the browser redirect. The customer can close the tab before the redirect.
Reconciliation: trust the bank, check yourself
Your ledger says what you think happened. The provider's settlement report and the bank statement say what really happened. Reconciliation matches all three, row by row, every day. Each row is matched on a reference, an amount and a date. The breaks fall into a few kinds. Some rows are missing on our side, usually a lost callback. Some are missing on their side, usually a payment that never really went through. Some have amounts that differ, such as fees or currency rounding. Each break becomes a ticket. Matching is not instant either. Stripe says 99.99% of its dollar volume is fully ingested and verified within four days.
Many services, one payment: sagas instead of two-phase commit
A refund may touch the payment service, the ledger, the provider and the merchant's balance. You cannot hold one lock across all of them. Two-phase commit needs every party to take part, and an outside bank will not. So use a saga. Each step is a local transaction. If a later step fails, earlier steps are undone by compensating steps. For money, a compensation is a new reversing ledger entry. It never deletes the old one. The saga's progress is saved, so a crashed worker can pick it up and finish.
Authorize now, capture later, settle in batches
A card payment is not one event. Authorization asks the customer's bank to approve and hold the money. Capture says take the held money now. Many shops capture when the item ships. A hotel may authorize at check-in and capture at check-out. Settlement is when real money moves between banks. That happens later, in batches. So your ledger needs states for each stage. Money that is authorized is not yet the merchant's. Money that is captured is owed but not yet paid. Money that is settled has arrived. Mixing these up is how balances drift.
Keep card numbers out of your system
Any system that stores or handles raw card numbers falls under PCI DSS. Stripe's security guide says that can mean more than 300 security controls. The way out is tokenization. The card field on the checkout page is served by the provider. The card number goes straight to the provider's vault. Your server gets back a token that is useless to a thief. Inside a provider like Stripe, only the vault holds card numbers. Every other service, including the ledger, works with tokens.
Trade-offs to discuss
Every senior interviewer expects you to surface at least 3 of these. Pick the decisions, state the alternatives, and justify your choice.
Idempotency keys in the payments database vs in Redis
Redis is fast, but it is a second system. A crash between the Redis write and the database write leaves them disagreeing. Keeping keys in the payments database lets one transaction write both. Use Redis only as a cache in front.
Store a balance column vs derive balances from the ledger
A balance column is fast to read but easy to corrupt, and it has no history. Deriving balances from postings is always correct but slower. Most systems derive from postings and keep a cached balance that is checked against them.
Strong consistency vs eventual consistency
Use strong consistency where money moves: the payment state, the ledger, the idempotency keys. Use eventual consistency where a short delay is fine: dashboards, search, analytics and email.
Call the provider in the request vs from a queue
Calling inside the request gives the shopper an answer at once, which checkout needs. Slow rails, payouts and refunds can go through a queue. In both cases, save the payment as PENDING first.
Two-phase commit vs sagas
Two-phase commit needs every party to join one protocol, and outside banks do not. Sagas work across services and companies, at the cost of writing compensations. In payments, sagas win.
Build your own processor links vs use a gateway
Direct links to card networks and banks cut fees but take long work and certification. A gateway like Stripe or Razorpay ships in days. Start with a gateway. Consider direct links when the fees become a large cost.
How a Payment System actually does it
Stripe has published a lot about how it keeps payments correct. Its API docs explain idempotency keys in detail. Results are saved even for failed calls, and a key reused with different parameters is rejected. Its webhook docs say events are retried for up to three days and are not delivered in order. Stripe's Ledger, described in February 2024, is a double-entry system of record. It sees five billion events a day. Clearing accounts that should return to zero reveal missing or late transactions. Stripe's product applications use DocDB, a database service built on MongoDB Community. In June 2024, Stripe said DocDB serves over five million queries a second across more than 2,000 shards. For scale, Stripe reported $1.9 trillion in total volume for 2025. Over Black Friday to Cyber Monday 2025, it processed more than 578 million transactions. It peaked above 152,000 a minute, and API uptime was above 99.9999%. India's UPI shows the same pattern on public rails. NPCI's central switch routes each payment between two banks. A timeout leaves the payment pending until its real status is known. UPI handled 24.51 billion payments in August 2026.
Sources
- Stripe docs: Idempotent requests (read 2026-10-04)
- Stripe docs: Webhooks, retries and event ordering (read 2026-10-04)
- Stripe docs: Payment status updates (read 2026-10-04)
- Stripe docs: Integration security guide, PCI DSS (read 2026-10-04)
- Stripe: Ledger, Stripe's system for tracking money movement (2024-02-16)
- Stripe: How Stripe's document databases supported 99.999% uptime (2024-06-06)
- Stripe newsroom: Black Friday to Cyber Monday 2025 (2025-12-02)
- Stripe newsroom: 2025 annual letter (2026-02-24)
- MediaNama: UPI transactions in August 2026, NPCI data (2026-09)
Lessons to study before this interview
If any of these topics are fuzzy, the interviewer will catch it. Each lesson is 15 to 60 minutes with diagrams, code, and a quiz.
Idempotency
foundation / core fundamentals
Idempotency Keys
intermediate / api design protocols
ACID Properties
foundation / core fundamentals
Transactional Outbox Pattern
intermediate / messaging event systems
Idempotent Consumer
intermediate / messaging event systems
Saga Pattern
advanced / distributed systems core
Two-Phase Commit
advanced / distributed systems core
Timeout Patterns
advanced / reliability resilience
Capstone: Design a Payment System
capstone / capstone
