Skip to content

Design a Payment System, stage 5 of 13: break it

A buyer was charged twice

This is the prototype's checkout handler. It looks reasonable. Find every line that contributes to double charges or inconsistent state.

System so far· 4 parts
123CLIENTBuyer's browserSERVICECheckout APIDATABASEPostgresEXTERNALPayment provider

Select a component to see what it is responsible for and which state it owns.

  1. 1Buyer's browser → Payment provider: Card entry and 3-D Secure in hosted fields
  2. 2Buyer's browser → Checkout API: Pay (Idempotency-Key header); poll status
  3. 3Checkout API → Postgres: Claim attempt; conditional transitions

What you need to know

0 of 1 checks done
  1. Check-then-act reads a value, decides, then acts: "if the order isn't paid, charge it". Two requests running at once can both read "not paid" before either acts, and both charge.

    Only an operation the database performs atomically, like inserting under a unique constraint, can decide "first or not" correctly when requests overlap.

  2. Check

    A buyer double-clicks Pay. Both requests read status = 'pending', then each calls the provider without an idempotency key. What does the provider see?