software-engineer-blog logoSoftware Engineer Blog

Question 15 · Track 5 · Protect it and charge for it

Design a payment system

The network times out after the money moved. Now what?

Protect it and charge for it. 4 clarifying questions to ask first, the answer in 4 moves, the follow-up that catches a memorised answer and 2 supporting topics to watch or read.

What it is really testing

Whether you can make a retry safe, and whether you know which data you are not allowed to store.

Ask these first

Before a single box goes on the board. The answers change the design, which is the point of asking out loud.

The answer, in 4 moves

In this order. Each move earns the next one — say them out loud rather than drawing all four and narrating afterwards.

  1. 1Every charge carries a caller-supplied idempotency key, stored with its result.
  2. 2Model the payment as an explicit state machine — authorised, captured, failed, refunded.
  3. 3Capture asynchronously, and run a reconciliation loop against the provider’s ledger.
  4. 4Never log or store raw card data; the provider’s token is the only thing you keep.

The trap

The follow-up that separates a rehearsed answer from a real one.

A timeout is not a failure — it is an unknown. Retrying without an idempotency key is how a customer gets charged twice, and "we will check the logs" is not a design.

Watch or read

The pieces of this answer, each covered on its own. Take them whichever way suits you, then give the whole answer without looking.

At-least-once vs exactly-once

A customer taps Pay once and gets charged twice — with no bug in your code. Delivery semantics from first principles: why a lost acknowledgement makes at-most-once, at-least-once and 'exactly-once' inevitable, and why exactly-once is a property of your consumer, not a setting on your broker.

ReelRead

Part of System Design Interviews, Answered Out Loud. The fundamentals underneath it are the free CS course.