Case studies
Authorize, commit, capture
The order of checkout steps decides whether a buyer can be charged for a seat they lost. Authorize the card, commit the seat, then capture the money, and undo each step on failure.
Checkout touches two systems: the seat inventory and the payment provider. They cannot share one transaction, so the order of steps decides what happens when something fails in between. The rule is to capture money only after the seat is committed.
Concept
Before you start
- Authorization: the card network reserves funds for a payment. Capture actually takes them. A void releases an authorization that will not be captured.
- Saga: a multi-step process where each step has an undo action that runs if a later step fails. See distributed transactions.
The big idea
Picture buying a ticket at a counter. The clerk first confirms the seat is still yours, then runs your card. If the clerk ran the card first and then found the seat gone, you would need a refund, which takes days and erodes trust.
The picture breaks when the system crashes between steps. Then you need recovery, not just ordering.
Walking through it
The safe order is authorize, commit the seat, then capture. Here are the three cases:
Happy path:
hold valid → authorize → UPDATE seats to booked → capture → ticket
Payment declined:
hold valid → authorize declines → release hold (or let TTL expire)
Seat commit loses the race:
authorize OK → UPDATE returns 0 rows → void authorization → buyer retriesIn the last case, the buyer's card has a hold on it that is never captured. Voiding it releases the funds, so the buyer is not charged.
A crash is the harder case. If the system dies after authorization but before the seat commit, a reconciliation job either voids the authorization or completes the commit from a durable outbox. Payment calls carry an idempotency key, so retrying a timed-out request cannot create a second charge. See idempotency.
Common mistakes
- Capturing before the seat commit. A buyer who loses the seat has been charged for nothing.
- Forgetting to void after a lost race. Funds stay on hold on the buyer's card.
- Retrying a payment without an idempotency key. A timeout followed by a retry can charge twice.
- Treating a payment timeout as a failure. The outcome is unknown, so reconcile instead of guessing.
Check yourself
1. The seat commit returns 0 rows after authorization succeeded. What happens to the buyer's money?
Answer: The authorization is voided, which releases the funds, so the buyer is not charged.
2. Why not capture first and commit the seat afterwards?
Answer: If the commit then fails, you hold money for a seat you cannot give, and you must refund it.
Key takeaways
- Checkout spans two systems, so it runs as a saga, not one transaction.
- Authorize, commit the seat, then capture.
- Every failure path needs an undo: release the hold or void the authorization.
- Use idempotency keys and reconciliation to handle timeouts and crashes.
Further learning
Return to the ticket booking case study for the full design, or read the payment system case study for the payment side in depth.
Related
Case study: ticket booking
Ticketmaster-style inventory under extreme contention, with holds that expire, a virtual waiting room and atomic seat commits so one seat is never sold twice.
Case study: payment system
Moving money correctly, with payment intents, idempotency at every hop, a double-entry ledger, webhooks and reconciliation when the network lies. Start here for the payments map.
Idempotency and retries
Making operations safe to retry, with idempotency keys, deduplicating consumers and jittered backoff, so at-least-once delivery never means a double charge.
Distributed transactions
Keeping data consistent when one business operation spans several databases or services, using two-phase commit, sagas, the transactional outbox and reservations, and redrawing boundaries first.