Files
Adam Daley 69d8d7234a Fix once-per-client promo race in checkout (#4350)
* 🔒 Fix once-per-client promo race in checkout

Re-check the once-per-client promo limit inside the checkout transaction
under a client-row lock, so concurrent checkouts for the same client
serialize instead of all passing the pre-transaction check. Losing
checkouts fail with the same error as a normal reuse.

* Clarify advisory sibling references

* 🔒 Skip the aggregate lock on PostgreSQL in the promo guard

PostgreSQL rejects FOR UPDATE on aggregate queries, so the locking
COUNT fails there with a database error. The client-row mutex stays
locking on every driver; the COUNT goes plain on PostgreSQL, which
loses nothing under READ COMMITTED once the mutex is held. Adds mock
and live-PostgreSQL coverage for both shapes.

* 🔒 Skip the aggregate lock on PostgreSQL in the balance guard

The same PostgreSQL restriction found in review applies to the
pre-existing client-balance locking read: SUM with FOR UPDATE fails
there, breaking every credit payment on PostgreSQL installs. Same
treatment as the promo guard: mutex stays locking everywhere, the
aggregate goes plain on PostgreSQL, with mock and live-PostgreSQL
coverage.
2026-09-18 00:32:58 +01:00
..