LOADING…
0%
Complete changelog

Commit

Make payout processing and cancellation atomic and race-safe

Commit details

Commit notes

processPayout and cancelPayout both used a read-then-check status guard followed by separate, non-transactional UPDATEs whose WHERE clauses did not re-check the status. This left two money bugs under concurrency/retries:

- processPayout: two calls (double-click, webhook retry) could both pass the `status === "pending"` read, then each deduct pendingBalanceCents and add to totalPayoutsCents — double-counting the payout. - cancelPayout: two concurrent cancels — or a cancel racing processPayout — could each return amountCents to availableBalanceCents, minting money the creator never earned (only floored at 0 on the pending side by GREATEST).

Both now follow the same atomic pattern already used by requestPayout and completePurchase: a single transaction whose status compare-and-swap (`WHERE status = 'pending' ... RETURNING`) is the sole point of mutual exclusion. If no row is returned, the call bails, so balances are adjusted exactly once regardless of concurrency or duplicate delivery.

processPayout also now sends its notification email using the post-commit balances (accurate figures) instead of pre-update reads.

No schema change; behavior is identical on the happy path.

[private session redacted]

Files changed
1
Lines added
+120
Lines removed
−121
This page is a permanent record of commit 0fd1ec18.