Harden money-safety re-review findings: nonce-independent idempotency keys, reconciliation-required logging, terminal-state classification
Re-review (2 Oracle + security + QA + librarian + context-miner) surfaced fixes, all applied: (1) UserPaymentModal and the account-page gift-card buy now key the cached idempotency key on a stable 'new-card' sentinel instead of the cnon: nonce, so clearing the nonce on a failed charge no longer regenerates the key — a lost-response retry now dedups at Square instead of double-charging (the tip flows already keyed on amount only). (2) The create-with-redeem clawback now logs CRITICAL when the guarded balance reversal is blocked (previously silent), and its transaction DELETE is scoped to this sale instead of deleting every transaction on the card. (3) reconcileStalePaymentAtSquare now treats APPROVED/PENDING as non-terminal (leave pending) instead of definitively failed, matching Square's documented state machine. (4) GetTillCheckoutStatus returns 404 for a sale already swept to failed instead of reporting a live state. (5) The critical-payment scan job skips candidates whose booking was hard-deleted, so one orphan can no longer silence all critical alerts.
This commit is contained in:
@@ -404,8 +404,13 @@
|
||||
}
|
||||
|
||||
// Cache the idempotency key per amount+type+card so a lost-response
|
||||
// retry reuses it (backend dedups) instead of double-charging.
|
||||
const cardKey = cardId ?? `new:${newCardToken ?? ''}`;
|
||||
// retry reuses it (backend dedups) instead of double-charging. The
|
||||
// new-card identity is a STABLE sentinel, NOT the cnon: nonce: the
|
||||
// nonce is one-shot and cleared on a failed charge, so keying on it
|
||||
// would regenerate the key on retry and a network-timeout retry (where
|
||||
// the charge actually landed) would double-charge. The nonce changes
|
||||
// per tokenize but represents the same logical card intent.
|
||||
const cardKey = cardId ?? 'new-card';
|
||||
if (
|
||||
!payIdempotencyKey ||
|
||||
payKeyedAmount !== amountCents ||
|
||||
|
||||
@@ -317,7 +317,13 @@
|
||||
// Cache the idempotency key per amount+card so a lost-response retry
|
||||
// reuses the same key (backend dedups) instead of double-charging.
|
||||
// Regenerate when the amount or card changes.
|
||||
const cardKey = cardId || `new:${newCardToken ?? ''}`;
|
||||
// Cache the idempotency key per amount+card so a lost-response
|
||||
// retry reuses it (backend dedups) instead of double-charging. The
|
||||
// new-card identity is a STABLE sentinel, NOT the cnon: nonce: the
|
||||
// nonce is one-shot and cleared on a failed charge, so keying on it
|
||||
// would regenerate the key on retry and a network-timeout retry
|
||||
// (where the charge actually landed) would double-charge.
|
||||
const cardKey = cardId || 'new-card';
|
||||
if (!buyIdempotencyKey || buyKeyedAmount !== buyAmount || buyKeyedCard !== cardKey) {
|
||||
buyIdempotencyKey = generateIdempotencyKey();
|
||||
buyKeyedAmount = buyAmount;
|
||||
|
||||
Reference in New Issue
Block a user