Charging twice on purpose#
Week 2 — SLOP3092
← Back to the lecture pageLast week#
A read timed out.
You retried it.
It worked.
Nothing bad happened#
That is the problem.
The reflex got rewarded before anybody examined it.
This week: same reflex, different call#
POST /charges { amount: 4200 }The timeout looks identical from the outside.
What does a timeout tell you?#
Two worlds#
| Question | The request was lost | The response was lost |
|---|---|---|
| Server charged? | no | yes |
| What you saw | timeout | timeout |
You cannot tell these apart. Ever.
So the reflex charges twice#
One customer. One button. Two charges.
Nobody wrote a bug. The retry was the bug.
The fix#
POST /charges
Idempotency-Key: 9f2c-a41e-...The client picks a key. The server checks it before it commits.
What it guarantees#
The second request lands on the same charge. It does not create a second one.
The retry still goes out. The key makes it a no-op.
What the server has to remember#
Every key, for as long as a client might still retry with it.
- window too short → a late retry double-charges anyway
- window too long → you are storing keys forever
Both ends are wrong in a different direction, so somebody has to pick the window and write down why.
In the lab#
- Build the naive retry against a stub that commits, then times out
- Produce the double charge on purpose
- Add the key
- Confirm the same retry now lands on the same charge
Bring the double charge to the first review.
Next week#
You fixed what the retry does.
Now: how often, and how fast.
← Back to the lecture page