wtf( )unctionsystem design, drawn
← all problemsPaymentsHard

The timeout that charged twice

A checkout call times out. The client retries. Customers are charged twice, and your database shows one order.

A timeout is ambiguous: the charge may have completed and only the response was lost. The retry is correct behaviour — what's missing is something that lets the payment provider recognise the second attempt as the same attempt.

Place the idempotency key, and the moment it becomes durable.
Components — tap one, then tap a slot on the diagram
!Customers are being charged twice. Your database shows one order.

Boundaries, outermost first: One transaction: orders row (pending), an empty slot for the persist with the order Outside every boundary: Checkout, Payment provider (FAILED: charged twice), an empty slot for the on every attempt Connections: Checkout calls orders row (step 1) orders row calls persist with the order — same commit (step 2) persist with the order calls on every attempt (step 3) on every attempt calls Payment provider — retry-safe (step 4)

orders rowpending
Checkout
Payment providercharged twice