@flopay/react 1.2.8 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -406,6 +406,13 @@ Pass `onTokenizedBody` to handle backend submission yourself:
406
406
  />
407
407
  ```
408
408
 
409
+ > **Vault card path — `onTokenizedBody` does not fire.** When the session uses the
410
+ > [vault PCI card form](#vault-pci-card-form), the backend-served widget owns the
411
+ > whole charge (tokenize → PaymentIntent → 3DS → result), so there is no
412
+ > client-side tokenization step to override. Use `onComplete` / `onDecline` /
413
+ > `onError` instead — the SDK relays the widget's terminal outcome to them. The
414
+ > legacy Stripe path's `onTokenizedBody` is unchanged.
415
+
409
416
  ### SplitCardForm (Split Card Fields + PayPal)
410
417
 
411
418
  `SplitCardForm` renders separate CardNumber, CardExpiry, and CardCVC fields with an integrated PayPal button. It matches the existing checkout/StripeCardForm layout.
@@ -457,6 +464,73 @@ The `SplitCardForm` layout:
457
464
  6. Full Name input
458
465
  7. Submit button
459
466
 
467
+ #### AVS postcode validation
468
+
469
+ When AVS collection is enabled (`enableAVS`) and the postcode field is shown,
470
+ `SplitCardForm` validates the postcode **format against the live selected
471
+ country before the card is captured**, on both the Stripe and vault card paths.
472
+ It reuses `@flopay/shared`'s country-aware
473
+ [postcode helpers](../shared/README.md#postal-code-helpers) (the same
474
+ `validator` rules the billing API applies), so client and server agree.
475
+
476
+ - **Supported country + malformed postcode** → the submit is blocked (the vault
477
+ widget's submit button is gated; the Stripe path returns before tokenizing)
478
+ and an inline, country-specific message shows the expected format
479
+ (e.g. *"Enter a valid ZIP Code (e.g. 12345 or 12345-6789)"*). On the vault
480
+ path the inline message appears once the field is blurred, since the disabled
481
+ submit means it can't be reached by a submit attempt.
482
+ - **Supported country + empty postcode** → the same submit gate applies, and
483
+ once the field is blurred (or a submit is attempted) an inline *required*
484
+ message shows (e.g. *"ZIP Code is required"*) rather than the expected-format
485
+ copy.
486
+ - **Country with no postal system** (or a locale `validator` doesn't recognise)
487
+ → the postcode field is shown but **optional**: empty and format checks are
488
+ both skipped, so these buyers are never blocked.
489
+
490
+ ### Vault PCI card form
491
+
492
+ When the billing API returns a hosted vault card form on the session
493
+ (`session.vault`), the card path renders a **backend-served, self-contained
494
+ hosted vault widget** (`VaultCardFields`) instead of Stripe's embedded card
495
+ elements (TeamFloPay/backend#823, Model A). The backend embeds this block for
496
+ any SDK that advertises `x-flo-sdk-version >= 1.3.0`, so it is fully
497
+ **server-driven** — there is no consumer prop to toggle it. The widget owns the
498
+ card fields, its own submit button, card tokenization, the PaymentIntent (created
499
+ **and** confirmed server-side), **3DS**, and the result, so **no Stripe.js runs
500
+ on the card path** and PAN / CVC never enter the SDK runtime. Wallets / PayPal /
501
+ APMs render exactly as before.
502
+
503
+ Because the widget owns the form, on the vault path `SplitCardForm` hides its own
504
+ card fields, cardholder-name input, AVS fields, and submit button. The flow is:
505
+
506
+ ```text
507
+ session includes a hosted vault card form (session.vault)
508
+ → SDK injects the widget HTML (session.vault.html, or POST /vault/capture)
509
+ → buyer pays inside the widget (tokenize → charge → 3DS, all backend-owned)
510
+ → widget postMessages its outcome → SDK fires onComplete / onDecline / onError
511
+ ```
512
+
513
+ - The widget HTML comes from the embedded `session.vault.html` (SDKs ≥ 1.3.0 send
514
+ `X-Flo-SDK-Version`, so the backend embeds it on create-session) or, if absent,
515
+ from `POST /v1/checkouts/sessions/{id}/vault/capture`.
516
+ - **Returning customers** with a card on file (`session.providerPaymentMethodId`)
517
+ are charged by the backend's auto-checkout cascade.
518
+ - **3DS** is handled inside the widget — there is no client-side `confirmCardPayment`.
519
+
520
+ **Failure modes:** the widget renders its own status / decline UI; `onDecline` /
521
+ `onError` receive the mapped reason + message so consumers can react.
522
+
523
+ > **Backend dependencies:** the hosted widget must emit the `flopay-vault`
524
+ > `postMessage` outcome the SDK listens for (otherwise it falls back to its own
525
+ > success redirect), handle the 3DS step internally, and own AVS for the vault
526
+ > path (the SDK no longer collects it inline; account-level address set at
527
+ > session-create still flows to the backend). Because the widget is injected
528
+ > **same-window**, every terminal outcome must be bound to the session id, and —
529
+ > to fully defend against same-window forgery — the backend should mint a
530
+ > per-session `messageToken` on the vault block and echo it in each
531
+ > `postMessage`; the SDK threads it through and rejects outcomes that omit or
532
+ > mismatch it (TeamFloPay/backend#823).
533
+
460
534
  ### PayPal Handling
461
535
 
462
536
  The SDK supports two PayPal paths, selected per-session based on what the billing API advertises under `gateways.*`:
@@ -543,7 +617,8 @@ function PaymentStatus() {
543
617
  |-----------|-------------|
544
618
  | `FloPayProvider` | Context provider. Accepts `flopay` (instance or promise), `options?`, and `children`. Creates the elements group automatically. |
545
619
  | `CheckoutForm` | Drop-in form with unified PaymentElement. Self-contained by default, or override with `onTokenizedBody`. Supports `ref` for imperative `handleNextAction()`. |
546
- | `SplitCardForm` | Split card form (CardNumber + CardExpiry + CardCvc + Full Name). Integrates PayPal via separate Elements instance. Supports `ref` for imperative `handleNextAction()`. |
620
+ | `SplitCardForm` | Split card form (CardNumber + CardExpiry + CardCvc + Full Name). Integrates PayPal via separate Elements instance. Renders the [vault PCI card form](#vault-pci-card-form) when the session includes a hosted vault card form (`session.vault`). Supports `ref` for imperative `handleNextAction()`. |
621
+ | `VaultCardFields` | Hosted vault PCI card fields. Used internally by `SplitCardForm` on the vault path; consumes a `CardCaptureAdapter` from `useFloPay().cardCapture()`. |
547
622
  | `PayPalButton` | Standalone PayPal button. Requires its own `FloPayProvider` with `paymentMethodCreation` set to something other than `'manual'`. |
548
623
  | `PaymentElement` | Unified payment element (cards, wallets, etc.) |
549
624
  | `CardElement` | Combined card input |