Low-Level Design (Object-Oriented Design) Module 14 – Case studies: money and commerce
Low-level design: payment gateway
Design a payment gateway in code: a payment state machine, idempotent create, capture and refund, provider adapters, and webhooks that come late or twice.
What you will learn
- Model payments, attempts, providers and webhooks with a clear state machine
- Design idempotent create, capture and refund operations
- Route between providers and handle late or duplicate callbacks
- Compare policies for a success that arrives after a timeout, with numbers
Before you start
What you’ll learn · Before you start
What you will learn
- Model payments, attempts, providers and webhooks with a clear state machine
- Design idempotent create, capture and refund operations
- Route between providers and handle late or duplicate callbacks
- Compare policies for a success that arrives after a timeout, with numbers
Before you start
On this page
Designing a payment gateway in a low-level design (LLD) round means modelling what sits between a shop’s checkout and the companies that actually move money: the payment providers. A Payment moves through a small state machine, from processing to authorised and captured, and on to refunds in part or in full, or to failed. Each provider is reached through an adapter that turns its own status words into the gateway’s events. Three rules carry the design. Every request that moves money carries an idempotency key, so a retry returns the stored answer. Every webhook carries an event id, so a repeated one changes nothing. And silence is not failure: a payment whose provider has not answered is still processing, so a success that arrives late is reconciled, never charged a second time.
The prompt, the rules and the questions it must answer
The prompt: “Design the classes of a payment gateway that shops call to take card payments, with two providers behind it.” The clarifying answers that matter: shops create a payment, capture it once the order ships, and refund it in parts; providers answer some calls at once and report the rest by webhook, sometimes twice and sometimes out of order; and when the cheaper provider fails, payments should move to the other one.
- G1 A payment is in exactly one state, and only the transitions of one table can change it.
- G2 Each provider’s statuses are translated by its adapter; a status the adapter does not know sends the payment to review, never to success.
- G3 Create, capture and refund carry an idempotency key: the same key and request return the stored answer, and the key sent with another request is refused.
- G4 Webhooks are applied once per event id, and news of a state the payment has already passed is ignored.
- G5 Refunds never add up to more than was captured, counting refunds still waiting for the provider.
- G6 A timeout is an unknown outcome: the gateway asks for the status before anything else is charged.
- G7 A success that arrives after the payment failed goes to review, and the money goes back to the customer.
- G8 Calls go to the cheapest healthy provider for the method, and a failing provider is skipped for a while.
A payment's states and the events that move it
Text description of the diagram
The diagram shows the states of one payment, from top to bottom, and the event on each arrow.
- The merchant's create(amount, key) starts the payment in processing: it has been sent to a provider.
- From processing, the event authorised leads to authorised (the money is reserved at the bank), and the event captured leads straight to captured when the capture is reported before its authorisation.
- From authorised, captured leads to captured (the money is taken).
- From captured, a refund of part of the amount leads to partially_refunded, and a refund of the rest leads on to refunded, which is final. A refund of everything leads from captured straight to refunded.
- At the side, processing leads to failed when the payment is declined or its deadline passes without an answer. Failed is final unless news arrives.
- Processing leads to needs_review when a provider reports a status that its adapter does not know, and failed leads to needs_review when a success arrives after the payment failed. In needs_review a person or a job decides what happens next.
The sizes are modest for one gateway, but they decide what the stores must hold. Work out the first two answers below before you check them.
Check yourself
4 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
The in-depth parts take the design through these questions, in the order an interviewer tends to ask them:
- Which classes own the state machine, and how do adapters keep the providers’ vocabularies out of it?
- What does each webhook do to a payment when it is repeated, late, out of order or in an unknown status? The same scenario is replayed in Python, JavaScript, Java and Go.
- How are create, capture and refund made idempotent from the shop to the provider? An idempotency key turns a retried request into a lookup of the stored response.
- A payment timed out, failed, and then succeeded. What should the gateway have done, and what does each policy cost? One policy fails at the deadline and ignores late news, one fails and refunds late successes, and one stays pending and asks the provider.
- How does routing pick a provider? It can follow cost, recent success rate or fixed weights. A circuit breaker stops calls to a failing provider and lets one trial through after a pause.
- Which points does each level of candidate need to make, and how should a 45-minute rehearsal of this design be spent?
The classes and the transition table Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- One scenario in four languages: duplicates, stale news and unknown statuses, replayed
- Idempotent create, capture and refund, from the shop to the provider
- A late success after a timeout: three policies, drilled with numbers
- Routing with a circuit breaker per provider, and why a timeout never fails over
- Practise: partial refunds and the daily report
- Partial refunds within what was captured, and a daily report
- What each level is expected to cover
- A 45-minute self-mock
- Interview questions
- Why should a payment gateway translate each provider's statuses through an adapter instead of storing the provider's own status on the payment?
- Your gateway marked a payment failed after a 30-second timeout and told the shop. Four minutes later the provider's webhook says the payment was captured. What should the gateway do now, and how would you design it so that this case is routine rather than an incident?
- A shop retries POST /payments with the same idempotency key while the first request is still waiting for the provider. What should the second request get, and how do you implement that safely when the gateway runs on several servers?
- The cheaper of your two providers starts failing a third of its calls, some with refusals and some with timeouts. How would you design routing with a circuit breaker so that payments move to the other provider without charging anyone twice?
- Key takeaways
1 diagram · 5 runnable examples (Python, Replay.java, replay.go, replay.mjs, replay.py, replay_session.py, scenario.mjs) · 1 exercise with sample tests · 3 interview answers · about 25 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; nothing renews unless you choose to. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Read a complete sample first: Low-level design: parking lot (the model)
References
- The Idempotency-Key HTTP Header Field (Internet-Draft) (IETF HTTPAPI Working Group)
- RFC 9110: HTTP Semantics (IETF)
- Circuit Breaker pattern (Azure Architecture Center) (Microsoft)
- Receive events in your webhook endpoint (Stripe)
- Records (Java Language Specification, Java SE 25) (Oracle)
- The Go Programming Language Specification: Switch statements (The Go Authors)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress