Files
Crussell/backend/handlers/payments
popertots 9bb812669e fix: fresh-review round — 2FA deliverability, disable re-verification, GDPR batch scrub, dispute alerting, docs accuracy
Second fresh-eyes review pass (7 agents: goal, security, code-quality,
context-mining, webhooks+2FA, client+mock+sweep, refunds/giftcards/handlers).
Money-safety core verified sound (identical-body replay byte-lossless, clawback
gated on definitive proof, no double-charge window). This round fixes the
issues the fresh pass surfaced:

2FA:
- Setup now DELIVERS the code via the [2FA] server log in ALL modes (was:
  nothing in enforced mode -> production 2FA was an unbreakable dead-end and
  saved-card charges were permanently 403). Enforced mode still withholds the
  code from the API response; the log line is the fake delivery channel until
  email/SMS lands (P6).
- Disabling 2FA now requires a fresh verification code when enforcement is ON
  (previously ignored the code -> a password-only attacker could lift the gate).
  Shares the 5-attempt lockout and timing-safe compare. Dev bypass retained.
- REQUIRE_2FA parsing normalized (false/0/off/no, case-insensitive);
  startup warning extended to the empty-env/mock-client/enforced-2FA confusion.

GDPR:
- anonymize_user() SQL now scrubs two_factor_* columns + staff notes, so the
  idle-account batch cleanup (CleanupIdleAccounts) is erasure-clean, not just
  the user-initiated delete path.

Webhooks:
- dispute.created for an untracked Square payment now raises a
  critical_payment_log admin notification (chargeback the app can't reconcile
  is never silent). Reason strings truncated on rune boundaries (valid UTF-8).
  Stale at-most-once comment corrected; revertTillSaleGiftCardFunding
  duplication noted.

Sweep/mock parity:
- Mock CreatePayment dedup is now source-aware (IDEMPOTENCY_KEY_REUSED on
  source mismatch) matching ReplayPaymentByKey and real Square.
- COMPLETED-but-never-polled terminal till-sale checkouts are now recorded by
  the sweep (previously only booking checkouts were; till charges were
  invisible until the 24h blind-fail WARN).
- Legacy snapshot-less minimal-body replay, SQUARE_LOCATION_ID drift, and
  in-memory-mock-restart limitations documented.

Docs:
- Webhook path corrected everywhere (/webhooks/square, not /api/webhooks/square
  - a deployer following the old path would 404 and silently lose all webhook
  reconciliation).
- 2FA enforcement semantics + code-delivery mechanism documented accurately
  (fail-closed default; log-delivery channel; disable re-verification).
- README/User Manual note the 2FA requirement on online saved-card payments.

Tests: 2,151 (up from 2,142). Backend 26/27 packages green (crussell/db fails
only in this environment: local postgres doesn't offer scram-sha-256 for the
test role; package is byte-identical to HEAD and untouched here). Frontend
builds; svelte-check 0 errors.
2026-08-22 00:34:49 +01:00
..