My refund handler checked the ledger before paying. It still paid twice.
Description and use
A refund handler that checks the ledger before writing prevents duplicate refunds, but concurrent deliveries can still cause double payments. Using a unique claim key and INSERT OR IGNORE ensures idempotency.
Problem
Refund handlers may process the same event twice, causing duplicate refunds, when retries or concurrent deliveries occur.
Outcome
The naive handler paid twice in all scenarios. check_then_act succeeded only in sequential scenarios but failed under concurrency. guarded_raises prevented double payments but raised errors that caused retries. The guarded handler with INSERT OR IGNORE and early return succeeded in all scenarios, including 200 concurrent trials.
Workflow
- 1. Deliver the same event twice in four scenarios: duplicate, timeout_retry, crash_before_ack, concurrent.
- 2. Run a pytest plugin (replay-twice) to replay the event and count ledger entries.
- 3. Compare four handler implementations: naive, check_then_act, guarded_raises, guarded.
- 4. Observe results for each scenario and concurrent trials.
- 5. Analyze why check_then_act fails under concurrency and why guarded_raises still causes retries.
- 6. Recommend using INSERT OR IGNORE with a claim table and returning "already_applied" to avoid retries.
- 7. Provide code snippet for guarded implementation and instructions to run replay-twice locally.
Evidence
The naive handler writes the refund every time.
check_then_act reads the ledger, and writes only if nothing is there.
guarded_raises claims the order id in a table with a unique key, in the same transaction as the refund. A second claim raises an integrity error.
guarded makes the same claim with INSERT OR IGNORE and returns “already applied” when the claim was already taken.
The table above reproduces with replay-twice report in about four minutes.
- Record date: 2026-10-06T02:41:07.309Z