Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Non-blocking findings from the auto code review of PR #516 (fix(pos): cancelling must not void a payment the buyer already made (Gitea #753)) — captured for batch cleanup rather than blocking the PR.
Commit
99ef405· https://github.com/Gambit-Inc/gambit/pull/516SHOULD-FIX:
backend/app/routes/transactions.py:5422-5449— The new guard has a TOCTOU hole that reproduces the bug it fixes.billing.py:466,_record_paid_tender) does a baredb.get(TransactionTender, …)and flipspendingtoauthorized. It never takes the transaction row lock thatcancel_transactionholds (lock=True), so the two don't serialize.pendingand passes. The webhook then commitsauthorized. The locking tender SELECT at line 5443 still hasstatus.in_(["pending", "authorized"]), so it picks up the freshly authorized row and voids it (line 5456). The sale is cancelled with the buyer's money captured.with_for_update()on the tenders, raise 409 if any locked row isauthorized, and only voidpending. Alternatively, drop"authorized"from thatINlist and raise if the locked set contains it.backend/app/routes/transactions.py:4006— This is outside the diff but is the same money-taken/sale-dropped outcome by another route.void_tenderstill lets a cashier void anauthorized(already captured) Stripe tender with no refund, and the tender can then be cancelled. If that is deliberate recovery/admin behaviour, it needs an explicit guard or a documented decision. Otherwise this PR only closes one of two paths to the same outcome. File it as a follow-up.frontend/app/hq/pos/PosPage.jsx:928-929— When cancel now returns this 409, the catch only sets the error. The 409 fires precisely when the webhook landed aftercancelSale'sgetTransactionat line 917, so the rail still shows the stalependingStripe tender. The message says "finish it instead", but the UI still shows the tender as unpaid. Thevoid-tenderhandler (around line 899) already refetches the aggregate on error; do the same here.NITS:
none
Filed from a Claude Code session