Files
Crussell/backend
popertots 2a47021673 fix: stale-pending sweep hardening — auto-refund stranded charges (M2), VAT re-apply (M5), single clock source (M3)
- M2: a stale pending payment COMPLETED at Square on a cancelled/lapsed/no-show
  booking no longer just fails the row + admin-notifies: an automatic pending
  refund row for the full stranded charge is created (same shape/origin as
  ProcessCancellationRefundTx, deterministic idempotency key, square_payment_id
  written when missing) so the pending-refund sweep issues it at Square.
- M5: sweep rescues re-apply VAT — rescued till sales run ApplyVATToTillSale and
  rescued payments apply ApplyVATToBookingPayment per record after the align
  UPDATE (which no longer NULLs the VAT fields), keeping rescued charges in VAT
  reporting. Both SQL functions are idempotent (guarded on vat_amount IS NULL).
- M3: every age-guard cutoff in the sweep is computed from clock.Now() and
  passed into SQL as parameters (never a DB NOW()-derived comparison) so the
  23h/24h Square idempotency-key retention decision cannot flip on clock skew;
  replayRescueUpperBoundSkew (5s) stops a legit same-key retry that raced the
  sweep from being misclassified as the sweep's own replay-created duplicate.
- C2: till cash/giftcard charges now serialize under the same
  crussell:payment:<bookingID> advisory lock as the online path (bounded
  try-lock) so remaining-balance checks can never both pass.
- webhooks_completion_asymmetry_test: webhook-first completion + sweep rescue
  double-complete race locked end-to-end through the real handler.
2026-08-22 00:34:50 +01:00
..