A checkout rebuild that stopped losing one basket in five at the payment step.
21% of baskets that reached the payment step never completed. Session replays showed the same thing over and over: a card declined for a reason the page never explained, and a customer who left rather than guess.
Worse, a retry after a network timeout occasionally charged twice. It had happened 43 times in a year, and each one cost a refund, a support ticket and a chargeback risk.
Every write in the flow got an idempotency key derived from the basket, so a retry is provably the same request. That closed the double-charge on its own.
Decline codes stopped being swallowed. Each one maps to a sentence a human wrote — “your bank asked for verification; try again and approve it in your banking app” — and the page keeps the entered card, so a retry is one tap.
The whole flow is three server-rendered steps with the summary as the only shared state. No client-side router, no optimistic anything. A checkout is the wrong place to be clever.
Completed checkouts rose 18.4% within six weeks and held through the following quarter.
Zero double charges since launch, across 2.4 million orders. The support queue for payment issues shrank by roughly two thirds.
+18.4%
Checkout conversion
43/yr → 0
Double charges
down 64%
Payment support tickets