SCA is now the PRIMARY authorisation for saved-card (ccof) charges (PSR 2017 /
chargeback liability shift); the homegrown 2FA becomes a BACKUP used only when
SCA is unavailable (e.g. a bank without in-app approval), with a strict audit
trail. The 'approve in your banking app' UX comes from Square buyer
verification. Email/SMS remains the intended 2FA delivery channel; the [2FA]
stdout-log relay (TWO_FACTOR_ALLOW_LOG_DELIVERY=true) is the explicit-insecure
pre-email/SMS stopgap.
BACKEND:
- CreateTerminalPaymentRequest gains VerificationToken (forwarded to Square in
the admin saved-card branch; validated like the other charge handlers)
- Structured SCA-required error surfacing: isVerificationRequiredError +
writeVerificationRequiredResponse (HTTP 402 with {code:'verification_required'})
at all 5 charge error sites — the frontend keys on it to trigger the challenge
- requireTwoFactorForCardAccess reworked: SCA token present => 2FA skipped
(SCA primary); no token => 2FA fallback requires delivery channel + consume +
insertTwoFAFallbackAudit (admin_audit_log reason 2fa_fallback_charge,
{sca_performed:false,...}); TWO_FACTOR_FALLBACK env flag (default true) gates
the fallback; false => SCA-only posture
- MIT vs CIT: admin till saved-card + admin booking saved-card charges now flag
customer_initiated=false (merchant-initiated, no SCA, no liability shift);
customer-initiated online flows keep true
FRONTEND:
- square_card_id threaded through SavedCard/SelectableCard + admin lists
- isVerificationRequiredSignal + shouldFallbackTo2FA helpers (402 + code / text
fallback); VERIFICATION_REQUIRED_MESSAGE
- tokenizeSavedCardWithVerification (Square SDK tokenize(details, squareCardId))
with verified/challenge-cancelled/sca-unavailable/sca-failed outcomes
- Per-surface SCA retry with the SAME idempotency key + fresh verification_token
(booking/tip/till/gift-card/admin); 'waiting for approval in your banking
app' state on admin surfaces; 2FA backup-only UX in the shared composable
MOCK PARITY:
- SimulateSavedCardVerificationRequired toggle (default off) + grandfathering
- Challenge state (ApprovePendingVerification/DenyPendingVerification,
ChallengeResult config, token-encoded _ok|_deny outcome)
- One-time-use verify_mock_ token ledger + amount/source binding
- MockCardForm saved-card verification simulation + mock Approve button
- Tests: saved-card SCA gate, one-time-use, denied, amount-mismatch,
grandfathered; frontend helper tests
DOCS: payments-doc SCA appendix, Technical Manual 2FA section, README,
Overview, Feature Catalog updated to SCA-primary + 2FA-backup; env-var
documented (42/42).
26/26 backend packages; 95/95 frontend tests + build; env-docs 42/42.
192 lines
5.1 KiB
JSON
192 lines
5.1 KiB
JSON
{
|
|
"main": {
|
|
"id": "118cffd7cdee3b11",
|
|
"type": "split",
|
|
"children": [
|
|
{
|
|
"id": "d45436e02729bff7",
|
|
"type": "tabs",
|
|
"children": [
|
|
{
|
|
"id": "7b4ed20d72674471",
|
|
"type": "leaf",
|
|
"state": {
|
|
"type": "markdown",
|
|
"state": {
|
|
"file": "Crussell/payments and money processes.md",
|
|
"mode": "source",
|
|
"source": false
|
|
},
|
|
"icon": "lucide-file",
|
|
"title": "payments and money processes"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"direction": "vertical"
|
|
},
|
|
"left": {
|
|
"id": "53a883d203e9233d",
|
|
"type": "split",
|
|
"children": [
|
|
{
|
|
"id": "490f9de080de140d",
|
|
"type": "tabs",
|
|
"children": [
|
|
{
|
|
"id": "42aca4a7881f98cd",
|
|
"type": "leaf",
|
|
"state": {
|
|
"type": "file-explorer",
|
|
"state": {
|
|
"sortOrder": "alphabetical",
|
|
"autoReveal": false,
|
|
"showSearch": false,
|
|
"searchQuery": ""
|
|
},
|
|
"icon": "lucide-folder-closed",
|
|
"title": "Files"
|
|
}
|
|
},
|
|
{
|
|
"id": "3f5a2398495786b0",
|
|
"type": "leaf",
|
|
"state": {
|
|
"type": "search",
|
|
"state": {
|
|
"query": "",
|
|
"matchingCase": false,
|
|
"explainSearch": false,
|
|
"collapseAll": false,
|
|
"extraContext": false,
|
|
"sortOrder": "alphabetical"
|
|
},
|
|
"icon": "lucide-search",
|
|
"title": "Search"
|
|
}
|
|
},
|
|
{
|
|
"id": "332fce93850753ed",
|
|
"type": "leaf",
|
|
"state": {
|
|
"type": "bookmarks",
|
|
"state": {},
|
|
"icon": "lucide-bookmark",
|
|
"title": "Bookmarks"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"direction": "horizontal",
|
|
"width": 300,
|
|
"collapsed": true
|
|
},
|
|
"right": {
|
|
"id": "2750d7726f904ef3",
|
|
"type": "split",
|
|
"children": [
|
|
{
|
|
"id": "8cf8aee55a46981b",
|
|
"type": "tabs",
|
|
"children": [
|
|
{
|
|
"id": "5c7ce50806d60318",
|
|
"type": "leaf",
|
|
"state": {
|
|
"type": "backlink",
|
|
"state": {
|
|
"file": "Crussell/payments and money processes.md",
|
|
"collapseAll": false,
|
|
"extraContext": false,
|
|
"sortOrder": "alphabetical",
|
|
"showSearch": false,
|
|
"searchQuery": "",
|
|
"backlinkCollapsed": false,
|
|
"unlinkedCollapsed": true
|
|
},
|
|
"icon": "links-coming-in",
|
|
"title": "Backlinks"
|
|
}
|
|
},
|
|
{
|
|
"id": "77c2a28112c134e9",
|
|
"type": "leaf",
|
|
"state": {
|
|
"type": "outgoing-link",
|
|
"state": {
|
|
"file": "Crussell/payments and money processes.md",
|
|
"linksCollapsed": false,
|
|
"unlinkedCollapsed": true
|
|
},
|
|
"icon": "links-going-out",
|
|
"title": "Outgoing links"
|
|
}
|
|
},
|
|
{
|
|
"id": "0f217e57cf9fb6d9",
|
|
"type": "leaf",
|
|
"state": {
|
|
"type": "tag",
|
|
"state": {
|
|
"sortOrder": "frequency",
|
|
"useHierarchy": true,
|
|
"showSearch": false,
|
|
"searchQuery": ""
|
|
},
|
|
"icon": "lucide-tags",
|
|
"title": "Tags"
|
|
}
|
|
},
|
|
{
|
|
"id": "013dd65f19607fbd",
|
|
"type": "leaf",
|
|
"state": {
|
|
"type": "outline",
|
|
"state": {
|
|
"file": "Crussell/payments and money processes.md",
|
|
"followCursor": false,
|
|
"showSearch": false,
|
|
"searchQuery": ""
|
|
},
|
|
"icon": "lucide-list",
|
|
"title": "Outline"
|
|
}
|
|
}
|
|
],
|
|
"currentTab": 3
|
|
}
|
|
],
|
|
"direction": "horizontal",
|
|
"width": 300,
|
|
"collapsed": true
|
|
},
|
|
"left-ribbon": {
|
|
"hiddenItems": {
|
|
"switcher:Open quick switcher": false,
|
|
"graph:Open graph view": false,
|
|
"canvas:Create new canvas": false,
|
|
"daily-notes:Open today's daily note": false,
|
|
"templates:Insert template": false,
|
|
"command-palette:Open command palette": false,
|
|
"bases:Create new base": false
|
|
}
|
|
},
|
|
"active": "7b4ed20d72674471",
|
|
"lastOpenFiles": [
|
|
"Untitled.canvas",
|
|
"Crussell/payments and money processes.md",
|
|
"Crussell/Overview.md",
|
|
"Crussell/User Manual.md",
|
|
"Crussell/Future Work - Gap Backlog.md",
|
|
"Crussell/Technical Manual.md",
|
|
"Crussell/Loyalty & Discount System Reference.md",
|
|
"Crussell/Admin Manual.md",
|
|
"Crussell/Test Implementation Plan.md",
|
|
"Crussell/Crussell Nails.md",
|
|
"Crussell/Backend/bookings.md",
|
|
"Untitled.base",
|
|
"Express.js Cheat Sheet.md"
|
|
]
|
|
} |