APS-53Required
Make each change happen exactly once
Changes to money, stock and single-use items must happen once, even when the same request arrives twice at the same moment.
How to do it
Do the check and the change in one atomic database step, such as a conditional update or a unique constraint. Give payment requests an idempotency key, so a repeat returns the first result. Avoid reading a value, deciding, and writing it back in separate steps. For more depth, see conditional writes.
Examples
SQL example
-- One step: the coupon is used only if it was still unused
UPDATE coupons SET used_by = :user WHERE code = :code AND used_by IS NULL;
-- 0 rows changed means someone else already used it
How to check
On the test copy, send the same coupon or withdrawal request 10 times at once with a test account. Only one succeeds, and the balance changes by exactly one use.
Sources
Related rules
- DAT-15 Know your isolation level in Data and databases
- DAT-16 Make conditional writes in Data and databases
- PAY-14 Create each charge with an idempotency key in Payments and billing
- PAY-15 Keep one open payment per order in Payments and billing
- ECM-7 Decrement stock in one conditional update in E-commerce and fulfilment
- ECM-23 Hand out each licence key once in E-commerce and fulfilment
- ECM-37 Limit each coupon per customer instead of per account in E-commerce and fulfilment
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/application-security/limits-and-business-logic/race-conditions">Agent Wranglers application security playbook</a>. In code you write, add a comment next to it with the same link.