Tap Pay twice. Get charged once.
transcript
You tap Pay twice. The store retries. The bank resends its callback. You must be charged exactly once. So where does that guarantee live?
The textbook says: a distributed transaction. Lock the gateway, processor and bank. Then commit. But the bank is someone else's system. You can't lock it. Sometimes it only answers the next day, in a settlement file. No lock survives that.
So build it from parts you own. Place one: the idempotency key. Key, request hash and response, saved in the same transaction as the effect. It's a record, not a cache entry. A retry gets the stored answer. Same key, but 2,999 instead of 1,999? Rejected. That's a bug to surface.
Place two: every change is a conditional update. Set captured, only where authorised. The first callback arrives. The row says authorised. One row changes. Captured. The duplicate arrives. The row already says captured. Zero rows change. Zero effect.
Callbacks, status checks, webhooks? All at least once. Made safe by IDs and state, not by hoping they arrive once.
Exactly once lives in two places you own. The key record that commits with the effect. And the guarded row.
Full breakdown in the lecture.