feat: frontend SCA-only posture — tokenize-result as charge source (C1), C6 refusal dialog, no 2FA fallback
- square.ts: shouldFallbackTo2FA replaced by shouldShowSCARefusal — a genuine 'sca-unavailable' now drives the REFUSAL path (the customer is told the payment cannot complete and to pay online later), never the 2FA code fallback (PSR 2017 SCA is non-waivable; merchant liability is not cured by consent). SCA_REFUSAL_MESSAGE_ONLINE/TILL copy added; SCA_FALLBACK_CONSENT_VERSION 'v1' + scaFallbackConsentFields() carry the versioned consent on the explicit opt-in path only (shipped surfaces send none). SquareTokenizeResult docs updated: tokenize-result token is the charge source, tokenless OK proceeds token-less under the backend's SCA-only gate. - C1 wire contract on every saved-card surface (booking, tip, gift-card buy, till, account): the proactive SCA tokenize-result is sent as new_card_token (the charge SOURCE alongside the saved-card ref), never the legacy verification_token; 402 verification-required now means the tokenize-result was consumed/expired between tokenize and charge. - New ScaFallbackConsentDialog surfaces the refusal notice; the code input (useTwoFactorCodeForSavedCard scaAvailable: () => true) only ever appears via a backend gate rejection (defensive/opt-in). - Till (M10): proactive saved-card SCA runs per sale line BEFORE the first charge; sca-unavailable aborts the whole sale before any charge. - Card save (M11/M12): STORE-intent tokenizeForStore with SCA at tokenization; 402 verification-required on save surfaces SCA-first guidance instead of a generic failure.
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
<!--
|
||||
ScaFallbackConsentDialog.svelte — C6 refusal notice shown whenever a
|
||||
saved-card charge hits genuine `sca-unavailable` (the issuer's in-app SCA
|
||||
challenge cannot run). The C6 legal verdict (PSR 2017 SCA is non-waivable;
|
||||
the merchant is liable regardless of consent) removed the homegrown 2FA code
|
||||
gate as an SCA fallback for saved-card charges, so this is now a REFUSAL:
|
||||
the customer is told the payment cannot complete and invited to pay online
|
||||
later, with a single OK button that closes the flow cleanly (no charge
|
||||
submitted). It must NEVER offer a "continue with verification code" action
|
||||
under the default SCA-only posture.
|
||||
|
||||
Inline panel (no portal) so it nests cleanly inside every payment surface —
|
||||
the modals, the booking-flow step, the tip card and the till card.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { SCA_REFUSAL_MESSAGE_ONLINE } from '$lib/square/square';
|
||||
|
||||
let {
|
||||
open = false,
|
||||
onOk,
|
||||
message = SCA_REFUSAL_MESSAGE_ONLINE
|
||||
}: {
|
||||
open?: boolean;
|
||||
onOk: () => void;
|
||||
message?: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
{#if open}
|
||||
<div
|
||||
role="alertdialog"
|
||||
aria-label="Payment cannot be completed"
|
||||
class="rounded-lg border border-red-200 bg-red-50 p-4"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<svg
|
||||
class="mt-0.5 h-5 w-5 shrink-0 text-red-600"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<line x1="12" y1="8" x2="12" y2="12" />
|
||||
<line x1="12" y1="16" x2="12.01" y2="16" />
|
||||
</svg>
|
||||
<div class="min-w-0">
|
||||
<h4 class="text-sm font-semibold text-red-900">Payment can't be processed right now</h4>
|
||||
<p class="mt-1 text-sm text-red-800">{message}</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button class="mt-4 min-h-11 w-full" onclick={onOk}>OK</Button>
|
||||
</div>
|
||||
{/if}
|
||||
Reference in New Issue
Block a user