Files
Crussell/frontend/src/lib/components/payments/ScaFallbackConsentDialog.svelte
T
popertots 0fdb2f02cd 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.
2026-08-22 00:34:50 +01:00

57 lines
1.8 KiB
Svelte

<!--
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}