PSR 2017 reg 100 makes SCA mandatory and non-waivable for customer-initiated
stored-credential charges; a merchant-side 2FA check cannot legally substitute
for it (authorising a token-less charge via 2FA leaves the MERCHANT liable for
ECI 7 / SLI 210 chargebacks and reg 77(6) compensation regardless of consent).
- payments/twofa.go: the homegrown 2FA fallback for token-less saved-card
charges is REMOVED ENTIRELY. requireTwoFactorForCardAccess is now SCA-only:
a non-empty Square verification_token (charge surfaces, token forwarded to
Square) skips the gate; anything else is refused 402 verification_required.
enforceSCAFallbackConsent is a compile-compatible no-op (fallback never runs).
- New requireTwoFactorForCardAccessWithTokenValidation distinguishes surfaces
where the token IS forwarded to Square (charge — Square validates it) from
card-SAVE surfaces (token client-asserted, never forwarded: a non-empty token
must NOT skip the save gate, auth-F1).
- SCA tokenize-result wire contract (C1): a saved card charged with a fresh
one-time tokenize-result sends the token as the charge SOURCE (new_card_token
-> source_id) alongside saved_card_id, never a separate verification_token.
resolveChargeSource resolves the saved-card branch FIRST (customer from the
card row, token as source) so combined token+card requests are SCA-clean.
- C6 consent fields (consent_version / consent_accepted) added to the booking/
tip/till/gift-card charge requests, enforced server-side before any fallback
charge could reach Square and recorded on the 2fa_fallback_charge audit row;
logVerificationTokenProvenance traces minted tokens to their charge.
- user 2FA issuance gate refactored into pure build-agnostic functions
(twoFAPepperConfigured / twoFADeliveryChannelConfigured /
twoFAEnsureIssueAllowedStrict) shared with the payments re-issue path and
exercised directly by the test,dev suite; TWO_FACTOR_FALLBACK switch and
.env.example entry removed; startup posture notes updated.
- Test coverage: fail-closed 2FA production gates (pepper/delivery), token
validation on save vs charge surfaces, completion idempotency, idempotency
key determinism, refund-policy 72h/24h epsilon boundaries, VAT parity.
New test files cover previously untested paths across DAV, validators,
S3, Square, mw, bookings, user, and payments packages.
Includes mock fix: HoldCheckouts flag on MockClient allows tests to
pause auto-complete goroutine for testing PENDING checkout states.
Coverage: 50.4% → 65.0% (+14.6pp)
The phase 3 payment query used ORDER BY created_at DESC LIMIT 1.
Within a single transaction, NOW() returns the same timestamp for all
inserts, making the ordering non-deterministic when other parallel tests
insert payment rows with the same timestamp. Fix by parsing the
response's payment ID and querying by it directly.
Also revert eslint.config.js back to error-level rules for all categories
(removing the previous 'warn' overrides).
Refactor BuyGiftCard to insert pending payment before Square call with VAT applied. Add transaction wrapping to gift card handlers. Remove redundant Content-Type header sets. Migrate all time.Now() to clock.Now().
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Add GetVATConfig for reading VAT settings via any db.Querier, ApplyVATToBookingPayment for applying VAT to payment records, and ApplyVATToTillSale for SPV gift card till sales. Errors are logged without blocking payment flow.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>