@vtex/faststore 0.0.4 → 0.0.6
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/CHANGELOG.md +67 -0
- package/dist/actions.json +181 -0
- package/dist/catalog.json +1595 -481
- package/dist/client.d.ts +2 -1
- package/dist/client.js +2 -1
- package/dist/components.d.ts +31 -7
- package/dist/components.js +35 -13
- package/dist/content-types.json +73 -0
- package/dist/docs/access-code-sign-in.md +1 -1
- package/dist/docs/account-access-gate.md +153 -0
- package/dist/docs/account-entry-point.md +1 -2
- package/dist/docs/account-menu.md +164 -0
- package/dist/docs/address-summary.md +24 -3
- package/dist/docs/alert-dialog.md +204 -0
- package/dist/docs/billing-address.md +284 -0
- package/dist/docs/buy-button.md +1 -1
- package/dist/docs/card-entry.md +250 -0
- package/dist/docs/cardholder-document.md +142 -0
- package/dist/docs/cart.md +1 -1
- package/dist/docs/client-preferences-fieldset.md +148 -0
- package/dist/docs/client-preferences.md +2 -1
- package/dist/docs/client-profile-fieldset.md +184 -0
- package/dist/docs/client-profile.md +1 -1
- package/dist/docs/document-field.md +58 -4
- package/dist/docs/field.md +317 -0
- package/dist/docs/form.md +307 -0
- package/dist/docs/identity-edit.md +6 -6
- package/dist/docs/identity-summary.md +5 -5
- package/dist/docs/installments.md +248 -0
- package/dist/docs/my-account-profile.md +1 -0
- package/dist/docs/order-placed-payment-summary.md +138 -0
- package/dist/docs/payment-method-badges.md +232 -0
- package/dist/docs/payment-methods.md +1 -0
- package/dist/docs/payment.md +218 -0
- package/dist/docs/phone-field.md +58 -4
- package/dist/docs/place-order.md +157 -0
- package/dist/docs/save-card.md +186 -0
- package/dist/docs/saved-cards.md +218 -0
- package/dist/docs/select.md +465 -0
- package/dist/docs/session.md +1 -1
- package/dist/docs/shipping-address.md +110 -258
- package/dist/docs/sign-in-redirect.md +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/internal/client.d.ts +3 -1
- package/dist/internal/client.js +3 -1
- package/dist/matter/access-code-sign-in/access-code-sign-in.js +9 -8
- package/dist/matter/access-code-sign-in/access-code-sign-in.js.map +1 -1
- package/dist/matter/account-access-gate/account-access-gate.d.ts +86 -0
- package/dist/matter/account-access-gate/account-access-gate.js +283 -0
- package/dist/matter/account-access-gate/account-access-gate.js.map +1 -0
- package/dist/matter/account-access-gate/sign-out-intent.d.ts +17 -0
- package/dist/matter/account-access-gate/sign-out-intent.js +62 -0
- package/dist/matter/account-access-gate/sign-out-intent.js.map +1 -0
- package/dist/matter/account-entry-point/account-entry-point.js +2 -2
- package/dist/matter/account-entry-point/account-entry-point.js.map +1 -1
- package/dist/matter/account-menu/account-menu.d.ts +88 -0
- package/dist/matter/account-menu/account-menu.js +246 -0
- package/dist/matter/account-menu/account-menu.js.map +1 -0
- package/dist/matter/billing-address/billing-address.d.ts +165 -0
- package/dist/matter/billing-address/billing-address.js +408 -0
- package/dist/matter/billing-address/billing-address.js.map +1 -0
- package/dist/matter/card-entry/card-capture.d.ts +25 -0
- package/dist/matter/card-entry/card-capture.js +25 -0
- package/dist/matter/card-entry/card-capture.js.map +1 -0
- package/dist/matter/card-entry/card-entry.d.ts +120 -0
- package/dist/matter/card-entry/card-entry.js +832 -0
- package/dist/matter/card-entry/card-entry.js.map +1 -0
- package/dist/matter/cardholder-document/cardholder-document.d.ts +45 -0
- package/dist/matter/cardholder-document/cardholder-document.js +70 -0
- package/dist/matter/cardholder-document/cardholder-document.js.map +1 -0
- package/dist/matter/cart/cart.d.ts +1 -1
- package/dist/matter/cart/cart.js +2 -2
- package/dist/matter/cart/cart.js.map +1 -1
- package/dist/matter/client-preferences/client-preferences-fieldset.d.ts +77 -0
- package/dist/matter/client-preferences/client-preferences-fieldset.js +299 -0
- package/dist/matter/client-preferences/client-preferences-fieldset.js.map +1 -0
- package/dist/matter/client-preferences/client-preferences.d.ts +3 -5
- package/dist/matter/client-preferences/client-preferences.js +6 -6
- package/dist/matter/client-preferences/client-preferences.js.map +1 -1
- package/dist/matter/client-profile/client-profile.d.ts +4 -6
- package/dist/matter/client-profile/client-profile.js +9 -6
- package/dist/matter/client-profile/client-profile.js.map +1 -1
- package/dist/matter/client-profile-form/client-profile-fieldset.d.ts +119 -0
- package/dist/matter/client-profile-form/client-profile-fieldset.js +488 -0
- package/dist/matter/client-profile-form/client-profile-fieldset.js.map +1 -0
- package/dist/matter/delivery-options/delivery-options.js.map +1 -1
- package/dist/matter/identity-edit/identity-edit.js +2 -2
- package/dist/matter/identity-edit/identity-edit.js.map +1 -1
- package/dist/matter/identity-summary/identity-summary.js +2 -2
- package/dist/matter/identity-summary/identity-summary.js.map +1 -1
- package/dist/matter/installments/installments-select.d.ts +42 -0
- package/dist/matter/installments/installments-select.js +167 -0
- package/dist/matter/installments/installments-select.js.map +1 -0
- package/dist/matter/installments/installments.d.ts +58 -0
- package/dist/matter/installments/installments.js +176 -0
- package/dist/matter/installments/installments.js.map +1 -0
- package/dist/matter/installments/use-installments-selection.d.ts +29 -0
- package/dist/matter/installments/use-installments-selection.js +95 -0
- package/dist/matter/installments/use-installments-selection.js.map +1 -0
- package/dist/matter/my-account-profile/my-account-profile.js +5 -5
- package/dist/matter/my-account-profile/my-account-profile.js.map +1 -1
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.d.ts +63 -0
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js +305 -0
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js.map +1 -0
- package/dist/matter/payment/payment-card-capture.d.ts +19 -0
- package/dist/matter/payment/payment-card-capture.js +129 -0
- package/dist/matter/payment/payment-card-capture.js.map +1 -0
- package/dist/matter/payment/payment-controller.d.ts +24 -0
- package/dist/matter/payment/payment-controller.js +107 -0
- package/dist/matter/payment/payment-controller.js.map +1 -0
- package/dist/matter/payment/payment-method-badges.d.ts +57 -0
- package/dist/matter/payment/payment-method-badges.js +192 -0
- package/dist/matter/payment/payment-method-badges.js.map +1 -0
- package/dist/matter/payment/payment.d.ts +89 -0
- package/dist/matter/payment/payment.js +263 -0
- package/dist/matter/payment/payment.js.map +1 -0
- package/dist/matter/payment/use-payment-method-selection.d.ts +302 -0
- package/dist/matter/payment/use-payment-method-selection.js +458 -0
- package/dist/matter/payment/use-payment-method-selection.js.map +1 -0
- package/dist/matter/payment/use-payment-submit-gate.d.ts +111 -0
- package/dist/matter/payment/use-payment-submit-gate.js +144 -0
- package/dist/matter/payment/use-payment-submit-gate.js.map +1 -0
- package/dist/matter/place-order/place-order.d.ts +74 -0
- package/dist/matter/place-order/place-order.js +379 -0
- package/dist/matter/place-order/place-order.js.map +1 -0
- package/dist/matter/product-list/product-list.d.ts +1 -0
- package/dist/matter/product-list/product-list.js.map +1 -1
- package/dist/matter/product-shelf/product-shelf.d.ts +1 -0
- package/dist/matter/product-shelf/product-shelf.js +2 -0
- package/dist/matter/product-shelf/product-shelf.js.map +1 -1
- package/dist/matter/region/region.js +1 -1
- package/dist/matter/region/region.js.map +1 -1
- package/dist/matter/save-card/save-card.d.ts +49 -0
- package/dist/matter/save-card/save-card.js +188 -0
- package/dist/matter/save-card/save-card.js.map +1 -0
- package/dist/matter/saved-cards/saved-cards.d.ts +79 -0
- package/dist/matter/saved-cards/saved-cards.js +387 -0
- package/dist/matter/saved-cards/saved-cards.js.map +1 -0
- package/dist/matter/shipping-address/country-descriptors.js +20 -10
- package/dist/matter/shipping-address/country-descriptors.js.map +1 -1
- package/dist/matter/shipping-address/shipping-address.d.ts +113 -174
- package/dist/matter/shipping-address/shipping-address.js +481 -792
- package/dist/matter/shipping-address/shipping-address.js.map +1 -1
- package/dist/matter/shipping-simulator/shipping-simulator.js.map +1 -1
- package/dist/matter/variant-selector/variant-selector.js +1 -1
- package/dist/particles/address-summary/address-summary.d.ts +24 -0
- package/dist/particles/address-summary/address-summary.js +83 -2
- package/dist/particles/address-summary/address-summary.js.map +1 -1
- package/dist/particles/alert-dialog/alert-dialog.d.ts +68 -0
- package/dist/particles/alert-dialog/alert-dialog.js +187 -0
- package/dist/particles/alert-dialog/alert-dialog.js.map +1 -0
- package/dist/particles/checkbox/checkbox.d.ts +7 -3
- package/dist/particles/checkbox/checkbox.js +4 -1
- package/dist/particles/checkbox/checkbox.js.map +1 -1
- package/dist/particles/document-field/document-field.d.ts +48 -5
- package/dist/particles/document-field/document-field.js +189 -32
- package/dist/particles/document-field/document-field.js.map +1 -1
- package/dist/particles/field/bound-field-registry.js +52 -0
- package/dist/particles/field/bound-field-registry.js.map +1 -0
- package/dist/particles/field/field.d.ts +116 -0
- package/dist/particles/field/field.js +497 -6
- package/dist/particles/field/field.js.map +1 -1
- package/dist/particles/fieldset/fieldset.js +2 -2
- package/dist/particles/fieldset/fieldset.js.map +1 -1
- package/dist/particles/footer-social/footer-social.js +1 -1
- package/dist/particles/form/form.d.ts +104 -0
- package/dist/particles/form/form.js +485 -0
- package/dist/particles/form/form.js.map +1 -0
- package/dist/particles/incentives/incentives.js +1 -1
- package/dist/particles/phone-field/phone-field.d.ts +38 -10
- package/dist/particles/phone-field/phone-field.js +183 -39
- package/dist/particles/phone-field/phone-field.js.map +1 -1
- package/dist/particles/postal-code-field/postal-code-field.js +2 -2
- package/dist/particles/postal-code-field/postal-code-field.js.map +1 -1
- package/dist/particles/select/field-select.d.ts +43 -0
- package/dist/particles/select/field-select.js +150 -0
- package/dist/particles/select/field-select.js.map +1 -0
- package/dist/particles/select/select.d.ts +2 -0
- package/dist/particles/select/select.js.map +1 -1
- package/dist/particles/shared/compose-refs.js +21 -0
- package/dist/particles/shared/compose-refs.js.map +1 -0
- package/dist/schema.graphql +701 -2
- package/dist/sdk/actions/analytics-action-provider.d.ts +15 -0
- package/dist/sdk/actions/analytics-action-provider.js +19 -0
- package/dist/sdk/actions/analytics-action-provider.js.map +1 -0
- package/dist/sdk/actions/cart-action-provider.d.ts +1 -1
- package/dist/sdk/actions/cart-action-provider.js +1 -1
- package/dist/sdk/actions/cart-action-provider.js.map +1 -1
- package/dist/sdk/actions/index.d.ts +3 -1
- package/dist/sdk/actions/index.js +3 -1
- package/dist/sdk/actions/payment-action-provider.d.ts +22 -0
- package/dist/sdk/actions/payment-action-provider.js +26 -0
- package/dist/sdk/actions/payment-action-provider.js.map +1 -0
- package/dist/sdk/actions/session-action-provider.d.ts +1 -1
- package/dist/sdk/actions/session-action-provider.js +1 -1
- package/dist/sdk/actions/session-action-provider.js.map +1 -1
- package/dist/sdk/actions/shipping-action-provider.d.ts +1 -1
- package/dist/sdk/actions/shipping-action-provider.js +1 -1
- package/dist/sdk/actions/shipping-action-provider.js.map +1 -1
- package/dist/sdk/actions/types.d.ts +6 -2
- package/dist/sdk/analytics/analytics-destination.d.ts +43 -0
- package/dist/sdk/analytics/analytics-destination.js +90 -0
- package/dist/sdk/analytics/analytics-destination.js.map +1 -0
- package/dist/sdk/analytics/use-analytics-actions.d.ts +57 -0
- package/dist/sdk/analytics/use-analytics-actions.js +108 -0
- package/dist/sdk/analytics/use-analytics-actions.js.map +1 -0
- package/dist/sdk/cache/storefront-cache.js +1 -1
- package/dist/sdk/cache/storefront-cache.js.map +1 -1
- package/dist/sdk/cart/operations.d.ts +4 -4
- package/dist/sdk/cart/operations.js +1 -1
- package/dist/sdk/cart/operations.js.map +1 -1
- package/dist/sdk/cart/optimistic.js +1 -1
- package/dist/sdk/cart/optimistic.js.map +1 -1
- package/dist/sdk/cart/use-cart-actions.d.ts +1 -1
- package/dist/sdk/cart/use-cart-actions.js +4 -4
- package/dist/sdk/cart/use-cart-actions.js.map +1 -1
- package/dist/sdk/cart/use-cart-validate-on-open.js +1 -1
- package/dist/sdk/cart/use-cart-validate-on-open.js.map +1 -1
- package/dist/sdk/cart/write-gate.js +1 -1
- package/dist/sdk/cart/write-gate.js.map +1 -1
- package/dist/sdk/data/entity-resolution-context.js +31 -0
- package/dist/sdk/data/entity-resolution-context.js.map +1 -0
- package/dist/sdk/data/entity-subscriber.js +18 -8
- package/dist/sdk/data/entity-subscriber.js.map +1 -1
- package/dist/sdk/data/index.js +1 -0
- package/dist/sdk/data/page-reactive-data-root.d.ts +38 -2
- package/dist/sdk/data/page-reactive-data-root.js +4 -2
- package/dist/sdk/data/page-reactive-data-root.js.map +1 -1
- package/dist/sdk/data/reactive-data-provider.d.ts +1 -1
- package/dist/sdk/data/reactive-data-provider.js +8 -4
- package/dist/sdk/data/reactive-data-provider.js.map +1 -1
- package/dist/sdk/data/reactive-list-item-data-provider.d.ts +1 -1
- package/dist/sdk/data/reactive-list-item-data-provider.js +13 -5
- package/dist/sdk/data/reactive-list-item-data-provider.js.map +1 -1
- package/dist/sdk/data/use-reactive-data-settled.js +39 -0
- package/dist/sdk/data/use-reactive-data-settled.js.map +1 -0
- package/dist/sdk/data/use-reactive-entity.js +6 -2
- package/dist/sdk/data/use-reactive-entity.js.map +1 -1
- package/dist/sdk/generated/fragment-types.js +2 -0
- package/dist/sdk/generated/fragment-types.js.map +1 -1
- package/dist/sdk/generated/gql.js +2 -1
- package/dist/sdk/generated/gql.js.map +1 -1
- package/dist/sdk/generated/graphql.js +85 -1
- package/dist/sdk/generated/graphql.js.map +1 -1
- package/dist/sdk/i18n/messages/en-US.js +3 -0
- package/dist/sdk/i18n/messages/en-US.json +3 -0
- package/dist/sdk/i18n/messages/en-US.json.d.ts +3 -0
- package/dist/sdk/i18n/messages/es-ES.js +3 -0
- package/dist/sdk/i18n/messages/es-ES.json +3 -0
- package/dist/sdk/i18n/messages/pt-BR.js +3 -0
- package/dist/sdk/i18n/messages/pt-BR.json +3 -0
- package/dist/sdk/identity/operations.js.map +1 -1
- package/dist/sdk/identity/use-identity-actions.d.ts +1 -1
- package/dist/sdk/identity/use-identity-actions.js +3 -2
- package/dist/sdk/identity/use-identity-actions.js.map +1 -1
- package/dist/sdk/payment/gateway-origin.js +35 -0
- package/dist/sdk/payment/gateway-origin.js.map +1 -0
- package/dist/sdk/payment/gateway.d.ts +85 -0
- package/dist/sdk/payment/gateway.js +183 -0
- package/dist/sdk/payment/gateway.js.map +1 -0
- package/dist/sdk/payment/operations.d.ts +52 -0
- package/dist/sdk/payment/operations.js +223 -0
- package/dist/sdk/payment/operations.js.map +1 -0
- package/dist/sdk/payment/security-code-mask.js +71 -0
- package/dist/sdk/payment/security-code-mask.js.map +1 -0
- package/dist/sdk/payment/submission-registry.d.ts +33 -0
- package/dist/sdk/payment/submission-registry.js +58 -0
- package/dist/sdk/payment/submission-registry.js.map +1 -0
- package/dist/sdk/payment/use-payment-actions.d.ts +78 -0
- package/dist/sdk/payment/use-payment-actions.js +276 -0
- package/dist/sdk/payment/use-payment-actions.js.map +1 -0
- package/dist/sdk/payment/use-place-order.d.ts +65 -0
- package/dist/sdk/payment/use-place-order.js +640 -0
- package/dist/sdk/payment/use-place-order.js.map +1 -0
- package/dist/sdk/session/auth-operations.d.ts +1 -1
- package/dist/sdk/session/auth-operations.js.map +1 -1
- package/dist/sdk/session/use-session-actions.d.ts +1 -1
- package/dist/sdk/session/use-session-actions.js.map +1 -1
- package/dist/sdk/session/use-session-revalidation.js +65 -0
- package/dist/sdk/session/use-session-revalidation.js.map +1 -0
- package/dist/sdk/shipping/option-select-operations.d.ts +1 -1
- package/dist/sdk/shipping/option-select-operations.js.map +1 -1
- package/dist/sdk/shipping/request-timeout.js +1 -1
- package/dist/sdk/shipping/request-timeout.js.map +1 -1
- package/dist/sdk/shipping/shipping-operations.d.ts +1 -1
- package/dist/sdk/shipping/shipping-operations.js.map +1 -1
- package/dist/sdk/shipping/use-shipping-actions.d.ts +2 -2
- package/dist/sdk/shipping/use-shipping-actions.js +1 -1
- package/dist/sdk/shipping/use-shipping-actions.js.map +1 -1
- package/dist/sdk/state/cart.d.ts +3 -4
- package/dist/sdk/state/cart.js +6 -7
- package/dist/sdk/state/cart.js.map +1 -1
- package/dist/utils/loggable-error-name.js +49 -0
- package/dist/utils/loggable-error-name.js.map +1 -0
- package/dist/utils/resolve-authored-copy.js +18 -0
- package/dist/utils/resolve-authored-copy.js.map +1 -0
- package/dist/utils/safe-href.js +3 -3
- package/dist/utils/safe-href.js.map +1 -1
- package/dist/utils/substitute-template.js +33 -0
- package/dist/utils/substitute-template.js.map +1 -0
- package/package.json +2 -1
- package/src/styles/tailwind.css +34 -0
- package/dist/docs/client-profile-form.md +0 -192
- package/dist/matter/client-profile-form/client-profile-form.d.ts +0 -126
- package/dist/matter/client-profile-form/client-profile-form.js +0 -662
- package/dist/matter/client-profile-form/client-profile-form.js.map +0 -1
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { defineComponent } from "../../core/dist/define-component.js";
|
|
3
|
+
import "../../core/dist/index.js";
|
|
4
|
+
import { useActionsOverride } from "../../sdk/actions/actions-override-context.js";
|
|
5
|
+
import { useDataOverride } from "../../sdk/data/data-override-context.js";
|
|
6
|
+
import { resolveAuthoredCopy } from "../../utils/resolve-authored-copy.js";
|
|
7
|
+
import { Fieldset } from "../../particles/fieldset/fieldset.js";
|
|
8
|
+
import { RadioGroup, RadioIndicator, RadioRoot } from "../../particles/radio/radio.js";
|
|
9
|
+
import { PaymentSubmissionProvider } from "../../sdk/payment/submission-registry.js";
|
|
10
|
+
import { resolveCanRevealCardPayment, resolvePreselectionCandidateId, splitCardMethods, usePaymentMethodSelection } from "./use-payment-method-selection.js";
|
|
11
|
+
import { PaymentCardCaptureProvider } from "./payment-card-capture.js";
|
|
12
|
+
import { createContext, useContext, useId, useLayoutEffect, useMemo } from "react";
|
|
13
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
14
|
+
//#region src/matter/payment/payment.tsx
|
|
15
|
+
/**
|
|
16
|
+
* The `PaymentSection` matter root (specs/payment-section-root.md, T5 of the Credit Card epic;
|
|
17
|
+
* docs/features/credit-card/rfc.md § D1). `PaymentSectionRoot` is the always-visible presentation
|
|
18
|
+
* variant over the `Payment` entity T1 projects: it owns a thin `fragment` (the method list with its
|
|
19
|
+
* server-driven validator config, plus the current selection), runs the shared
|
|
20
|
+
* `usePaymentMethodSelection` engine, and provides the `payment` context its leaves read — the card
|
|
21
|
+
* entry (T7), cardholder document (T8), installments (T9), save-card (T10), billing address (T11),
|
|
22
|
+
* saved cards (T12), and place-order CTA (T14) — through the guarded `usePayment()` hook rather than
|
|
23
|
+
* drilled props (ADR-0009). `PaymentSectionController` (payment-controller.tsx) is the second provider
|
|
24
|
+
* of the same context for a collapsed overlay surface; ADR-0009 matches by contract identifier, so the
|
|
25
|
+
* two substitute for one another with zero edits to any consumer.
|
|
26
|
+
*
|
|
27
|
+
* "Thin" means no `installmentOptions` (T9's own fragment), no `savedCards` (T12's), and no
|
|
28
|
+
* `selectedInstallment` (T6's) — the heavy, optional, and iterated subtrees ADR-0011/ADR-0012 split out.
|
|
29
|
+
* `methods[].validator` *is* selected here: it is a non-normalized value object off `Payment.methods`
|
|
30
|
+
* that cannot be subscribed separately, and T7 tests every method's `numberRegex` to detect a network.
|
|
31
|
+
*
|
|
32
|
+
* `children` is the section composition, not a per-method template — the method picker is
|
|
33
|
+
* `PaymentMethodList`, an independently placeable part with its own hooks.
|
|
34
|
+
*/
|
|
35
|
+
const fragment = `
|
|
36
|
+
fragment PaymentSectionData on Query {
|
|
37
|
+
payment {
|
|
38
|
+
__typename
|
|
39
|
+
id
|
|
40
|
+
methods {
|
|
41
|
+
__typename
|
|
42
|
+
id
|
|
43
|
+
name
|
|
44
|
+
groupName
|
|
45
|
+
validator {
|
|
46
|
+
__typename
|
|
47
|
+
numberMask
|
|
48
|
+
numberRegex
|
|
49
|
+
cvvMask
|
|
50
|
+
cvvRegex
|
|
51
|
+
cvvRequired
|
|
52
|
+
expirationRequired
|
|
53
|
+
holderNameRequired
|
|
54
|
+
billingAddressRequired
|
|
55
|
+
documentDisplayable
|
|
56
|
+
documentRequired
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
selectedMethodId
|
|
60
|
+
selectedAccountId
|
|
61
|
+
selectedBin
|
|
62
|
+
}
|
|
63
|
+
cart {
|
|
64
|
+
__typename
|
|
65
|
+
id
|
|
66
|
+
total {
|
|
67
|
+
__typename
|
|
68
|
+
amount
|
|
69
|
+
currencyCode
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
`;
|
|
74
|
+
const DEFAULT_METHOD_GROUP = ["creditCardPaymentGroup", "debitCardPaymentGroup"];
|
|
75
|
+
const PAYMENT_CONTEXT_UNSET = Symbol("PaymentContextUnset");
|
|
76
|
+
const PaymentContextInternal = createContext(PAYMENT_CONTEXT_UNSET);
|
|
77
|
+
/** The provider both section roots render. Exported for `PaymentSectionController`. */
|
|
78
|
+
const PaymentContext = PaymentContextInternal;
|
|
79
|
+
/**
|
|
80
|
+
* Read the payment section's selection state, draft, and overlay disclosure from the nearest
|
|
81
|
+
* `PaymentSectionRoot` or `PaymentSectionController`. Throws outside either subtree — a misplaced
|
|
82
|
+
* consumer is a composition bug the author should see immediately, not a silent degrade to an empty
|
|
83
|
+
* selection — mirroring `useSession()` / `useDeliveryOptions()`. ADR-0009's compiler check catches the
|
|
84
|
+
* same mistake before emission for schema-authored pages; this is the defense-in-depth layer for
|
|
85
|
+
* Storybook, tests, and hand-written JSX.
|
|
86
|
+
*/
|
|
87
|
+
function usePayment() {
|
|
88
|
+
const ctx = useContext(PaymentContextInternal);
|
|
89
|
+
if (ctx === PAYMENT_CONTEXT_UNSET) throw new Error("usePayment() must be used inside a <PaymentSectionRoot> or <PaymentSectionController> subtree.");
|
|
90
|
+
return ctx;
|
|
91
|
+
}
|
|
92
|
+
const NO_DISCLOSURE = () => {};
|
|
93
|
+
const PaymentSectionRootDefinition = defineComponent({
|
|
94
|
+
name: "PaymentSectionRoot",
|
|
95
|
+
importPath: "@vtex/faststore/components",
|
|
96
|
+
component: PaymentSectionRoot,
|
|
97
|
+
fragment,
|
|
98
|
+
fetchStrategy: "reactive",
|
|
99
|
+
actions: {
|
|
100
|
+
payment: ["setMethod", "sendPayments"],
|
|
101
|
+
analytics: ["track"]
|
|
102
|
+
},
|
|
103
|
+
providesContext: ["payment", "card-capture"],
|
|
104
|
+
editor: {
|
|
105
|
+
label: "Payment Section",
|
|
106
|
+
category: "commerce"
|
|
107
|
+
},
|
|
108
|
+
schema: { group: {
|
|
109
|
+
type: "array",
|
|
110
|
+
default: ["creditCardPaymentGroup", "debitCardPaymentGroup"],
|
|
111
|
+
label: "Payment method group"
|
|
112
|
+
} },
|
|
113
|
+
guideline: "Root of the checkout payment section: reads the payment methods and the current selection from the order form and provides the 'payment' context every payment leaf needs and the 'card-capture' context CardEntry needs, so place CardEntry, CardholderDocument, Installments, SaveCard, BillingAddress, SavedCards, PlaceOrder, and PaymentMethodList as its children — none of them render without a payment root ancestor. Renders one container div and no chrome or copy of its own; headings and layout belong to the composition. Selecting a payment option dispatches $actions.payment.setMethod and every subscriber re-renders from the cache with no refetch — and for a card it is the number the shopper types that decides the network, not an option they pick, so PaymentMethodList shows every in-scope credit and debit method as ONE card option. Which methods exist is always real data — never author them as props; group only scopes the list by the store's payment group(s) — defaults to credit and debit together, accepts one group name or a set of them, and set it to \"\" to show every method, one option each, with no card option. Use PaymentSectionController instead when the section should live in a drawer or modal. See payment.md."
|
|
114
|
+
});
|
|
115
|
+
const PaymentMethodListDefinition = defineComponent({
|
|
116
|
+
name: "PaymentMethodList",
|
|
117
|
+
importPath: "@vtex/faststore/components",
|
|
118
|
+
component: PaymentMethodList,
|
|
119
|
+
requiresContext: "payment",
|
|
120
|
+
editor: {
|
|
121
|
+
label: "Payment Method List",
|
|
122
|
+
category: "commerce"
|
|
123
|
+
},
|
|
124
|
+
schema: {
|
|
125
|
+
legend: {
|
|
126
|
+
type: "string",
|
|
127
|
+
default: "Payment method",
|
|
128
|
+
label: "Legend"
|
|
129
|
+
},
|
|
130
|
+
cardLabel: {
|
|
131
|
+
type: "string",
|
|
132
|
+
default: "Pay with Credit / Debit Card",
|
|
133
|
+
label: "Card option label"
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
guideline: "The payment-method picker: a radio group of the payment options the order form offers, plus the inline selection error (role=\"alert\"). Every in-scope credit and debit method is ONE option — the network comes from the card number the shopper types, never from a row they pick — and any other in-scope method (e.g. Pix) keeps a row of its own; set group to \"\" on the root to switch that off and get one row per method. Selecting an option dispatches the method selection. Selecting a non-card option hides the card form, and the card option brings it back. Place it inside a PaymentSectionRoot, or inside the DialogPopup/DrawerPopup of a PaymentSectionController. It renders its own options from real data, so it takes no children and no method props; author the card option's wording with cardLabel. Keep it composed: it is the only element that surfaces a rejected selection, and the only control that can recover a selection this section does not serve — omit it and a shopper whose persisted method sits outside group has no reachable way back. Must be inside a payment root."
|
|
137
|
+
});
|
|
138
|
+
function PaymentSectionRoot({ group = DEFAULT_METHOD_GROUP, $data: $dataProp, $actions: $actionsProp, className, children, ...props }) {
|
|
139
|
+
const $data = useDataOverride($dataProp);
|
|
140
|
+
const $actions = useActionsOverride($actionsProp);
|
|
141
|
+
const selection = usePaymentMethodSelection({
|
|
142
|
+
group,
|
|
143
|
+
$data: $data?.payment ?? null,
|
|
144
|
+
$actions
|
|
145
|
+
});
|
|
146
|
+
const payment = useMemo(() => ({
|
|
147
|
+
...selection,
|
|
148
|
+
open: true,
|
|
149
|
+
setOpen: NO_DISCLOSURE
|
|
150
|
+
}), [selection]);
|
|
151
|
+
return /* @__PURE__ */ jsx(PaymentContextInternal, {
|
|
152
|
+
value: payment,
|
|
153
|
+
children: /* @__PURE__ */ jsx(PaymentSubmissionProvider, {
|
|
154
|
+
path: selection.draft.path,
|
|
155
|
+
children: /* @__PURE__ */ jsx(PaymentCardCaptureProvider, {
|
|
156
|
+
selection,
|
|
157
|
+
cart: $data?.cart,
|
|
158
|
+
$actions,
|
|
159
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
160
|
+
...props,
|
|
161
|
+
className,
|
|
162
|
+
"data-fs-payment-section": "",
|
|
163
|
+
...selection.draft.path ? { "data-fs-payment-section-path": selection.draft.path } : {},
|
|
164
|
+
...selection.placing ? { "data-fs-payment-section-placing": "" } : {},
|
|
165
|
+
children
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* The picker's presence state, always emitted so a swatch can surface a row-less container — otherwise
|
|
173
|
+
* a zero-height element under any stacking swatch, and the "visible empty region" the section's
|
|
174
|
+
* group-default risk is accepted on. `loading` (still resolving) and `empty` (resolved, nothing in
|
|
175
|
+
* scope) stay distinct: one is a wait, the other a misconfiguration.
|
|
176
|
+
*/
|
|
177
|
+
function methodListState({ loading, count }) {
|
|
178
|
+
if (loading) return "loading";
|
|
179
|
+
if (count === 0) return "empty";
|
|
180
|
+
return "ready";
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* The DOM value the collapsed card control carries inside the `RadioGroup`. Base UI requires every
|
|
184
|
+
* radio root to have one, and this control stands for N methods, so there is no `method.id` it could
|
|
185
|
+
* use.
|
|
186
|
+
*
|
|
187
|
+
* It is a radio-group value and nothing else — **never a method id** (INV-1, ADR-0023). It is never
|
|
188
|
+
* dispatched: `handleValueChange` below maps it back to `resolvePreselectionCandidateId`'s answer
|
|
189
|
+
* before calling `selectMethod`, and records card mode through `selectNewCard()`, which dispatches
|
|
190
|
+
* nothing, when there is no candidate (T191, FR-6). The prefix keeps it from ever colliding with a
|
|
191
|
+
* `PaymentMethod.id` a standalone row is bound to.
|
|
192
|
+
*/
|
|
193
|
+
const CARD_CONTROL_VALUE = "__fs-card-control";
|
|
194
|
+
function PaymentMethodList({ legend, cardLabel, className, ...props }) {
|
|
195
|
+
const { methods, allMethods, persistedMethodId, selectedMethodId, selectionError, selectMethod, selectNewCard, cardModeActive, loading, placing, setMethodListMounted } = usePayment();
|
|
196
|
+
const { collapsedCardMethods, standaloneMethods } = useMemo(() => splitCardMethods({
|
|
197
|
+
methods,
|
|
198
|
+
allMethods
|
|
199
|
+
}), [methods, allMethods]);
|
|
200
|
+
const cardCandidateId = useMemo(() => resolvePreselectionCandidateId(methods), [methods]);
|
|
201
|
+
const canRevealCardPayment = resolveCanRevealCardPayment({
|
|
202
|
+
collapsedCardMethods,
|
|
203
|
+
standaloneMethods
|
|
204
|
+
});
|
|
205
|
+
const cardControlChecked = collapsedCardMethods.length > 0 && (persistedMethodId === null || collapsedCardMethods.some((method) => method.id === selectedMethodId) || cardModeActive);
|
|
206
|
+
const instanceKey = useId();
|
|
207
|
+
useLayoutEffect(() => {
|
|
208
|
+
setMethodListMounted(instanceKey, canRevealCardPayment);
|
|
209
|
+
return () => setMethodListMounted(instanceKey, false);
|
|
210
|
+
}, [
|
|
211
|
+
instanceKey,
|
|
212
|
+
setMethodListMounted,
|
|
213
|
+
canRevealCardPayment
|
|
214
|
+
]);
|
|
215
|
+
function handleValueChange(value) {
|
|
216
|
+
if (value === CARD_CONTROL_VALUE) {
|
|
217
|
+
if (cardCandidateId !== null) selectMethod({ methodId: cardCandidateId });
|
|
218
|
+
else selectNewCard();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
selectMethod({ methodId: value });
|
|
222
|
+
}
|
|
223
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
224
|
+
...props,
|
|
225
|
+
className,
|
|
226
|
+
inert: placing,
|
|
227
|
+
"aria-disabled": placing || void 0,
|
|
228
|
+
"data-fs-payment-method-list": "",
|
|
229
|
+
"data-fs-payment-method-list-state": methodListState({
|
|
230
|
+
loading,
|
|
231
|
+
count: methods.length
|
|
232
|
+
}),
|
|
233
|
+
...placing ? { "data-fs-payment-method-list-placing": "" } : {},
|
|
234
|
+
children: [selectionError ? /* @__PURE__ */ jsx("div", {
|
|
235
|
+
role: "alert",
|
|
236
|
+
"data-fs-payment-method-list-error": "",
|
|
237
|
+
children: selectionError
|
|
238
|
+
}) : null, methods.length > 0 ? /* @__PURE__ */ jsxs(Fieldset.Root, {
|
|
239
|
+
"data-fs-payment-method-list-fieldset": "",
|
|
240
|
+
children: [/* @__PURE__ */ jsx(Fieldset.Legend, { children: resolveAuthoredCopy(legend, "Payment method") }), /* @__PURE__ */ jsxs(RadioGroup, {
|
|
241
|
+
value: cardControlChecked ? CARD_CONTROL_VALUE : selectedMethodId ?? "",
|
|
242
|
+
onValueChange: handleValueChange,
|
|
243
|
+
children: [collapsedCardMethods.length > 0 ? /* @__PURE__ */ jsxs(RadioRoot, {
|
|
244
|
+
value: CARD_CONTROL_VALUE,
|
|
245
|
+
children: [/* @__PURE__ */ jsx(RadioIndicator, { "data-fs-payment-method-indicator": "" }), /* @__PURE__ */ jsx("span", {
|
|
246
|
+
"data-fs-payment-method-name": "",
|
|
247
|
+
children: resolveAuthoredCopy(cardLabel, "Pay with Credit / Debit Card")
|
|
248
|
+
})]
|
|
249
|
+
}) : null, standaloneMethods.map((method) => /* @__PURE__ */ jsxs(RadioRoot, {
|
|
250
|
+
value: method.id,
|
|
251
|
+
children: [/* @__PURE__ */ jsx(RadioIndicator, { "data-fs-payment-method-indicator": "" }), /* @__PURE__ */ jsx("span", {
|
|
252
|
+
"data-fs-payment-method-name": "",
|
|
253
|
+
children: method.name
|
|
254
|
+
})]
|
|
255
|
+
}, method.id))]
|
|
256
|
+
})]
|
|
257
|
+
}) : null]
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
//#endregion
|
|
261
|
+
export { DEFAULT_METHOD_GROUP, PaymentContext, PaymentMethodList, PaymentMethodListDefinition, PaymentSectionRoot, PaymentSectionRootDefinition, usePayment };
|
|
262
|
+
|
|
263
|
+
//# sourceMappingURL=payment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment.js","names":[],"sources":["../../../src/matter/payment/payment.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport { type ComponentPropsWithRef, createContext, useContext, useId, useLayoutEffect, useMemo } from 'react';\nimport { Fieldset } from '../../particles/fieldset/fieldset.js';\nimport { RadioGroup, RadioIndicator, RadioRoot } from '../../particles/radio/radio.js';\nimport { useActionsOverride } from '../../sdk/actions/actions-override-context.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport { PaymentSubmissionProvider } from '../../sdk/payment/submission-registry.js';\nimport { resolveAuthoredCopy } from '../../utils/resolve-authored-copy.js';\nimport { PaymentCardCaptureProvider, type PaymentCartData } from './payment-card-capture.js';\nimport {\n type PaymentMethodSelection,\n type PaymentSectionData,\n resolveCanRevealCardPayment,\n resolvePreselectionCandidateId,\n splitCardMethods,\n usePaymentMethodSelection,\n} from './use-payment-method-selection.js';\n\n/**\n * The `PaymentSection` matter root (specs/payment-section-root.md, T5 of the Credit Card epic;\n * docs/features/credit-card/rfc.md § D1). `PaymentSectionRoot` is the always-visible presentation\n * variant over the `Payment` entity T1 projects: it owns a thin `fragment` (the method list with its\n * server-driven validator config, plus the current selection), runs the shared\n * `usePaymentMethodSelection` engine, and provides the `payment` context its leaves read — the card\n * entry (T7), cardholder document (T8), installments (T9), save-card (T10), billing address (T11),\n * saved cards (T12), and place-order CTA (T14) — through the guarded `usePayment()` hook rather than\n * drilled props (ADR-0009). `PaymentSectionController` (payment-controller.tsx) is the second provider\n * of the same context for a collapsed overlay surface; ADR-0009 matches by contract identifier, so the\n * two substitute for one another with zero edits to any consumer.\n *\n * \"Thin\" means no `installmentOptions` (T9's own fragment), no `savedCards` (T12's), and no\n * `selectedInstallment` (T6's) — the heavy, optional, and iterated subtrees ADR-0011/ADR-0012 split out.\n * `methods[].validator` *is* selected here: it is a non-normalized value object off `Payment.methods`\n * that cannot be subscribed separately, and T7 tests every method's `numberRegex` to detect a network.\n *\n * `children` is the section composition, not a per-method template — the method picker is\n * `PaymentMethodList`, an independently placeable part with its own hooks.\n */\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — raw template literal, matching the merged `payment-summary` sibling. The codegen\n// `documents` glob does not scan `src/matter/payment*`, so the typed `graphql()` factory is\n// unavailable here; `data-component-contract.test.ts` validates this fragment against the real schema.\n//\n// Selects `payment { id }` so Apollo's InMemoryCache normalizes `Payment` by id — required for the\n// `Payment` entity that `paymentMethodSelect` echoes back to update the same cache entity this\n// fragment reads. `fetchStrategy: 'reactive'`'s no-refetch guarantee holds only if both sides\n// normalize to one entity.\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment PaymentSectionData on Query {\n payment {\n __typename\n id\n methods {\n __typename\n id\n name\n groupName\n validator {\n __typename\n numberMask\n numberRegex\n cvvMask\n cvvRegex\n cvvRequired\n expirationRequired\n holderNameRequired\n billingAddressRequired\n documentDisplayable\n documentRequired\n }\n }\n selectedMethodId\n selectedAccountId\n selectedBin\n }\n cart {\n __typename\n id\n total {\n __typename\n amount\n currencyCode\n }\n }\n }\n`;\n\n/**\n * The default payment-group scope: credit and debit (T140, FR-2, Decision 2). Both strings are\n * store-configured, so a store whose groups differ must set `group` explicitly; `group: ''` scopes\n * nothing (specs/payment-section-root.md § Documentation Gaps item 1). Widened from a single string to\n * a set (Decision 1) — a public API break for any consumer importing it directly (Interfaces).\n */\n/**\n * The roots' keyed `$data` (ADR-0015, arity 2): the `Payment` slice the selection engine reads, and the\n * cart total `add_payment_info` needs (moved here from `CardEntry`, which reads no cart).\n */\nexport type PaymentSectionRootData = {\n payment?: PaymentSectionData;\n cart?: PaymentCartData;\n} | null;\n\nexport const DEFAULT_METHOD_GROUP: readonly string[] = ['creditCardPaymentGroup', 'debitCardPaymentGroup'];\n\n// ---------------------------------------------------------------------------\n// Payment context — provided by both section roots, read by every leaf\n// ---------------------------------------------------------------------------\n\n/**\n * The `payment` context contract, normative for T7–T12 and T14. Everything the selection engine\n * resolves, plus the overlay disclosure. What is deliberately absent is part of the contract too: no\n * card number, CVV, expiration, or holder name (RFC D13 — they live only in the card entry's local\n * state); no submission channel for the place-order sequence, which is the separate, narrow\n * `PaymentSubmissionProvider` below (T13) precisely so no card-shaped value ever joins this contract;\n * no `installmentOptions` / `savedCards` / `selectedInstallment` (their owners' fragments); and no\n * optimistic mirror of the persisted selection.\n */\nexport type PaymentContextValue = Readonly<\n PaymentMethodSelection & {\n /** The collapsed surface's disclosure state. Always `true` under `PaymentSectionRoot`. */\n open: boolean;\n /**\n * Drives the collapsed surface. A no-op under `PaymentSectionRoot`, so a leaf written for the\n * overlay composes unchanged inline. Selecting a method never closes the surface on its own — the\n * shopper still has a card to type — so closing is the composition's call (Decision 5).\n */\n setOpen: (open: boolean) => void;\n }\n>;\n\nconst PAYMENT_CONTEXT_UNSET = Symbol('PaymentContextUnset');\n\nconst PaymentContextInternal = createContext<PaymentContextValue | typeof PAYMENT_CONTEXT_UNSET>(PAYMENT_CONTEXT_UNSET);\n\n/** The provider both section roots render. Exported for `PaymentSectionController`. */\nexport const PaymentContext = PaymentContextInternal as React.Context<PaymentContextValue>;\n\n/**\n * Read the payment section's selection state, draft, and overlay disclosure from the nearest\n * `PaymentSectionRoot` or `PaymentSectionController`. Throws outside either subtree — a misplaced\n * consumer is a composition bug the author should see immediately, not a silent degrade to an empty\n * selection — mirroring `useSession()` / `useDeliveryOptions()`. ADR-0009's compiler check catches the\n * same mistake before emission for schema-authored pages; this is the defense-in-depth layer for\n * Storybook, tests, and hand-written JSX.\n */\nexport function usePayment(): PaymentContextValue {\n const ctx = useContext(PaymentContextInternal);\n if (ctx === PAYMENT_CONTEXT_UNSET) {\n throw new Error('usePayment() must be used inside a <PaymentSectionRoot> or <PaymentSectionController> subtree.');\n }\n return ctx;\n}\n\nconst NO_DISCLOSURE = () => {};\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport type PaymentSectionRootProps = ComponentPropsWithRef<'div'> & {\n /**\n * Scopes the method list to a **set** of `PaymentMethod.groupName` values — a method is in scope\n * when its `groupName` is a member (T140, FR-1, Decision 1). A single string is still accepted and\n * scopes to exactly that one group (FR-4, US-3). T1's read model is method-agnostic and assigns the\n * narrowing to the consumer; this prop supplies no method, label, or ordering — the list itself is\n * fragment data. An empty string scopes nothing.\n */\n group?: string | readonly string[];\n /** Keyed `{ payment, cart }` forwarded by the reactive runtime (arity-2 fragment, ADR-0015). */\n $data?: PaymentSectionRootData;\n /** Compiler-injected action surface; carries `payment.setMethod`, `payment.sendPayments`, and `analytics.track`. */\n $actions?: ComponentActions;\n};\n\n/** Renders its own controls from the context's method list — `children` would have nothing to template. */\nexport type PaymentMethodListProps = Omit<ComponentPropsWithRef<'div'>, 'children'> & {\n /**\n * The radio group's accessible name, rendered as a visible `Fieldset.Legend` (T38, Decision 1).\n * Resolved through `resolveAuthoredCopy`: an authored empty or whitespace-only string falls back to\n * the default rather than rendering a nameless group.\n */\n legend?: string;\n /**\n * The single card control's name (T171, Decision 8). The control stands for every in-scope\n * credit/debit method at once, so it cannot take its name from one of their `method.name` values the\n * way a per-network row did — it is authored copy instead, defaulting to the PRD's own string.\n * Resolved through `resolveAuthoredCopy` exactly as `legend` is, so an authored empty or\n * whitespace-only string falls back rather than rendering a nameless control.\n */\n cardLabel?: string;\n};\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const PaymentSectionRootDefinition = defineComponent<PaymentSectionRootProps>({\n name: 'PaymentSectionRoot',\n importPath: '@vtex/faststore/components',\n component: PaymentSectionRoot,\n fragment,\n fetchStrategy: 'reactive',\n actions: { payment: ['setMethod', 'sendPayments'], analytics: ['track'] },\n providesContext: ['payment', 'card-capture'],\n editor: { label: 'Payment Section', category: 'commerce' },\n schema: {\n // A literal, not `DEFAULT_METHOD_GROUP`: the native catalog builder extracts `schema` statically\n // and silently drops an identifier-valued default, so the built entry would carry no default and\n // the compiler would emit the section unscoped. `payment.test.tsx` asserts the literal matches the\n // constant, and that the built catalog kept the default.\n // T140, Decision 2/OQ-6: `type: 'array'` — no code path in packages/sukima/crates/sukima-core/\n // validates an authored prop's JSON value against this declared type, so a store's existing bare\n // string keeps compiling. No `items`: an enum would validate against a closed set of group names,\n // which is wrong for a store that renamed its group (OQ-3) — the exact fragility Decision 1 closes.\n group: {\n type: 'array',\n default: ['creditCardPaymentGroup', 'debitCardPaymentGroup'],\n label: 'Payment method group',\n },\n },\n guideline:\n \"Root of the checkout payment section: reads the payment methods and the current selection from the order form and provides the 'payment' context every payment leaf needs and the 'card-capture' context CardEntry needs, so place CardEntry, CardholderDocument, Installments, SaveCard, BillingAddress, SavedCards, PlaceOrder, and PaymentMethodList as its children — none of them render without a payment root ancestor. Renders one container div and no chrome or copy of its own; headings and layout belong to the composition. Selecting a payment option dispatches $actions.payment.setMethod and every subscriber re-renders from the cache with no refetch — and for a card it is the number the shopper types that decides the network, not an option they pick, so PaymentMethodList shows every in-scope credit and debit method as ONE card option. Which methods exist is always real data — never author them as props; group only scopes the list by the store's payment group(s) — defaults to credit and debit together, accepts one group name or a set of them, and set it to \\\"\\\" to show every method, one option each, with no card option. Use PaymentSectionController instead when the section should live in a drawer or modal. See payment.md.\",\n});\n\nexport const PaymentMethodListDefinition = defineComponent<PaymentMethodListProps>({\n name: 'PaymentMethodList',\n importPath: '@vtex/faststore/components',\n component: PaymentMethodList,\n requiresContext: 'payment',\n editor: { label: 'Payment Method List', category: 'commerce' },\n schema: {\n // T38/T171: inline literals, never identifiers — the native catalog builder extracts `schema`\n // statically and silently drops an identifier-valued default (INV-8).\n legend: { type: 'string', default: 'Payment method', label: 'Legend' },\n cardLabel: { type: 'string', default: 'Pay with Credit / Debit Card', label: 'Card option label' },\n },\n guideline:\n 'The payment-method picker: a radio group of the payment options the order form offers, plus the inline selection error (role=\"alert\"). Every in-scope credit and debit method is ONE option — the network comes from the card number the shopper types, never from a row they pick — and any other in-scope method (e.g. Pix) keeps a row of its own; set group to \"\" on the root to switch that off and get one row per method. Selecting an option dispatches the method selection. Selecting a non-card option hides the card form, and the card option brings it back. Place it inside a PaymentSectionRoot, or inside the DialogPopup/DrawerPopup of a PaymentSectionController. It renders its own options from real data, so it takes no children and no method props; author the card option\\'s wording with cardLabel. Keep it composed: it is the only element that surfaces a rejected selection, and the only control that can recover a selection this section does not serve — omit it and a shopper whose persisted method sits outside group has no reachable way back. Must be inside a payment root.',\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\nexport function PaymentSectionRoot({\n group = DEFAULT_METHOD_GROUP,\n $data: $dataProp,\n $actions: $actionsProp,\n className,\n children,\n ...props\n}: PaymentSectionRootProps) {\n const $data = useDataOverride($dataProp);\n const $actions = useActionsOverride($actionsProp);\n const selection = usePaymentMethodSelection({ group, $data: $data?.payment ?? null, $actions });\n const payment = useMemo<PaymentContextValue>(\n () => ({ ...selection, open: true, setOpen: NO_DISCLOSURE }),\n [selection],\n );\n\n return (\n <PaymentContextInternal value={payment}>\n <PaymentSubmissionProvider path={selection.draft.path}>\n <PaymentCardCaptureProvider selection={selection} cart={$data?.cart} $actions={$actions}>\n <div\n {...props}\n className={className}\n data-fs-payment-section=\"\"\n {...(selection.draft.path ? { 'data-fs-payment-section-path': selection.draft.path } : {})}\n // T28, Decision 5: presence attribute only, deliberately never `inert` — `PlaceOrder` composes\n // as a child of this same container (FR-6), so making it inert here would swallow the CTA's\n // own role=\"alert\"/role=\"status\" regions, the shopper's only channel for the outcome.\n {...(selection.placing ? { 'data-fs-payment-section-placing': '' } : {})}\n >\n {children}\n </div>\n </PaymentCardCaptureProvider>\n </PaymentSubmissionProvider>\n </PaymentContextInternal>\n );\n}\n\n/**\n * The picker's presence state, always emitted so a swatch can surface a row-less container — otherwise\n * a zero-height element under any stacking swatch, and the \"visible empty region\" the section's\n * group-default risk is accepted on. `loading` (still resolving) and `empty` (resolved, nothing in\n * scope) stay distinct: one is a wait, the other a misconfiguration.\n */\nfunction methodListState({ loading, count }: { loading: boolean; count: number }): 'loading' | 'empty' | 'ready' {\n if (loading) return 'loading';\n if (count === 0) return 'empty';\n return 'ready';\n}\n\n/**\n * The DOM value the collapsed card control carries inside the `RadioGroup`. Base UI requires every\n * radio root to have one, and this control stands for N methods, so there is no `method.id` it could\n * use.\n *\n * It is a radio-group value and nothing else — **never a method id** (INV-1, ADR-0023). It is never\n * dispatched: `handleValueChange` below maps it back to `resolvePreselectionCandidateId`'s answer\n * before calling `selectMethod`, and records card mode through `selectNewCard()`, which dispatches\n * nothing, when there is no candidate (T191, FR-6). The prefix keeps it from ever colliding with a\n * `PaymentMethod.id` a standalone row is bound to.\n */\nconst CARD_CONTROL_VALUE = '__fs-card-control';\n\nexport function PaymentMethodList({ legend, cardLabel, className, ...props }: PaymentMethodListProps) {\n const {\n methods,\n allMethods,\n persistedMethodId,\n selectedMethodId,\n selectionError,\n selectMethod,\n selectNewCard,\n cardModeActive,\n loading,\n placing,\n setMethodListMounted,\n } = usePayment();\n\n // T171, FR-1/FR-2: one control for the in-scope card methods, one row for every other in-scope\n // method. Memoized off the two lists only (NFR-5) — nothing here may widen what re-creates the\n // `payment` context value, which a keystroke already churns enough.\n const { collapsedCardMethods, standaloneMethods } = useMemo(\n () => splitCardMethods({ methods, allMethods }),\n [methods, allMethods],\n );\n // Decision 3: the collapsed control dispatches the *shared* preselection candidate, so the escape\n // hatch and the preselection can never disagree about whether an id is available.\n const cardCandidateId = useMemo(() => resolvePreselectionCandidateId(methods), [methods]);\n const canRevealCardPayment = resolveCanRevealCardPayment({ collapsedCardMethods, standaloneMethods });\n\n // Decision 2: derived, never bound to one `method.id` — a control standing for N methods has none.\n // Checked when the persisted selection resolves inside the collapsed set, and when nothing is\n // persisted at all (PRD REQ-02's \"the card payment option is selected by default\"). The second arm\n // is a deliberate divergence from the pre-T171 bound-to-`$data` behaviour: the control can read\n // checked while `$data.selectedMethodId` is still null, having dispatched nothing. `persistedMethodId`\n // is the raw, unscoped fact on purpose — a persisted method this section does not serve must leave the\n // control unchecked, which is what keeps it activatable as US-5's way back. T191 (FR-6): card mode is\n // the third arm, the card option chosen over such a selection or over a non-card one.\n const cardControlChecked =\n collapsedCardMethods.length > 0 &&\n (persistedMethodId === null ||\n collapsedCardMethods.some((method) => method.id === selectedMethodId) ||\n cardModeActive);\n\n // Fix pass, finding R2: registers this instance with the shared engine so the card leaves know an\n // escape hatch exists before they ever collapse — a section composed with no `PaymentMethodList` at\n // all must never hide the only remaining way to change the method (typing a new card number).\n // Retracted unconditionally on unmount, unlike `setPlacing`'s own \"leave it if still placing\"\n // carve-out: there is no in-flight sequence here for an abandoned instance to still be part of.\n //\n // T191, Decision 5 (after T171's Decision 4): what is registered is `canRevealCardPayment`, not the\n // bare fact of being mounted — whether this list can bring the card form back. A rendered card\n // option always can, since card mode needs no method id; without one, only a row bound to a card\n // method can. See `resolveCanRevealCardPayment`'s own doc comment. The dependency list carries it,\n // so the registration re-runs when the method list resolves.\n //\n // Fix pass round 2 (flash regression): `useLayoutEffect`, not `useEffect` — mirroring\n // `place-order.tsx`'s own `setPlacing` registration, the structurally identical precedent for\n // publishing a ref-counted fact to this shared engine from a leaf's mount. A passive effect runs\n // after the browser has already painted, so a root whose persisted selection is genuinely out of\n // scope committed one real frame with `methodListMounted` still `false` — the collapse (then\n // `selectionOutOfScope && methodListMounted`, now `cardPaymentCollapsed`) computed `false` for that\n // frame, painting the card form visible, focusable, and with its security code not yet cleared,\n // before the registration ever landed and a second commit hid it. A layout effect's own state update\n // is guaranteed to flush synchronously before the browser paints, so the very first frame a shopper\n // can ever see already reflects `methodListMounted`. The card leaves read it purely at render time,\n // not from an effect of their own, so this reorders nothing on that side: whichever commit this\n // layout effect's own update produces is the one their next render (still pre-paint) sees.\n const instanceKey = useId();\n useLayoutEffect(() => {\n setMethodListMounted(instanceKey, canRevealCardPayment);\n return () => setMethodListMounted(instanceKey, false);\n }, [instanceKey, setMethodListMounted, canRevealCardPayment]);\n\n // INV-1: only an id the read model supplied is ever dispatched. A standalone row's value *is* its own\n // `method.id`; the collapsed control's value is a DOM value with no method behind it, so it routes\n // through the shared candidate instead (FR-4). With no candidate to name it records card mode, which\n // brings the card form back and sends nothing (T191, FR-6, Decision 6).\n function handleValueChange(value: string) {\n if (value === CARD_CONTROL_VALUE) {\n if (cardCandidateId !== null) {\n selectMethod({ methodId: cardCandidateId });\n } else {\n selectNewCard();\n }\n return;\n }\n selectMethod({ methodId: value });\n }\n\n return (\n <div\n {...props}\n className={className}\n inert={placing}\n aria-disabled={placing || undefined}\n data-fs-payment-method-list=\"\"\n data-fs-payment-method-list-state={methodListState({ loading, count: methods.length })}\n {...(placing ? { 'data-fs-payment-method-list-placing': '' } : {})}\n >\n {selectionError ? (\n <div role=\"alert\" data-fs-payment-method-list-error=\"\">\n {selectionError}\n </div>\n ) : null}\n {methods.length > 0 ? (\n // T38, Decision 1: Fieldset.Root + Fieldset.Legend, exactly what card-entry.tsx:1101-1112\n // already ships — Base UI propagates the legend's id as aria-labelledby onto the descendant\n // RadioGroup, so this one wrapper names both the fieldset and the radiogroup (measured).\n <Fieldset.Root data-fs-payment-method-list-fieldset=\"\">\n <Fieldset.Legend>{resolveAuthoredCopy(legend, 'Payment method')}</Fieldset.Legend>\n <RadioGroup\n value={cardControlChecked ? CARD_CONTROL_VALUE : (selectedMethodId ?? '')}\n onValueChange={handleValueChange}\n >\n {collapsedCardMethods.length > 0 ? (\n // One control for every in-scope credit and debit method (FR-1, Decision 1). Both\n // styling hooks stay exactly where a per-network row carried them — `payment.md`'s\n // styling table documents them, so a store's swatch keeps its selectors.\n <RadioRoot value={CARD_CONTROL_VALUE}>\n <RadioIndicator data-fs-payment-method-indicator=\"\" />\n <span data-fs-payment-method-name=\"\">\n {resolveAuthoredCopy(cardLabel, 'Pay with Credit / Debit Card')}\n </span>\n </RadioRoot>\n ) : null}\n {standaloneMethods.map((method) => (\n <RadioRoot key={method.id} value={method.id}>\n <RadioIndicator data-fs-payment-method-indicator=\"\" />\n <span data-fs-payment-method-name=\"\">{method.name}</span>\n </RadioRoot>\n ))}\n </RadioGroup>\n </Fieldset.Root>\n ) : null}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqDA,MAAM,WAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuD/B,MAAa,uBAA0C,CAAC,0BAA0B,uBAAuB;AA4BzG,MAAM,wBAAwB,OAAO,qBAAqB;AAE1D,MAAM,yBAAyB,cAAkE,qBAAqB;;AAGtH,MAAa,iBAAiB;;;;;;;;;AAU9B,SAAgB,aAAkC;CAChD,MAAM,MAAM,WAAW,sBAAsB;CAC7C,IAAI,QAAQ,uBACV,MAAM,IAAI,MAAM,gGAAgG;CAElH,OAAO;AACT;AAEA,MAAM,sBAAsB,CAAC;AA2C7B,MAAa,+BAA+B,gBAAyC;CACnF,MAAM;CACN,YAAY;CACZ,WAAW;CACX;CACA,eAAe;CACf,SAAS;EAAE,SAAS,CAAC,aAAa,cAAc;EAAG,WAAW,CAAC,OAAO;CAAE;CACxE,iBAAiB,CAAC,WAAW,cAAc;CAC3C,QAAQ;EAAE,OAAO;EAAmB,UAAU;CAAW;CACzD,QAAQ,EASN,OAAO;EACL,MAAM;EACN,SAAS,CAAC,0BAA0B,uBAAuB;EAC3D,OAAO;CACT,EACF;CACA,WACE;AACJ,CAAC;AAED,MAAa,8BAA8B,gBAAwC;CACjF,MAAM;CACN,YAAY;CACZ,WAAW;CACX,iBAAiB;CACjB,QAAQ;EAAE,OAAO;EAAuB,UAAU;CAAW;CAC7D,QAAQ;EAGN,QAAQ;GAAE,MAAM;GAAU,SAAS;GAAkB,OAAO;EAAS;EACrE,WAAW;GAAE,MAAM;GAAU,SAAS;GAAgC,OAAO;EAAoB;CACnG;CACA,WACE;AACJ,CAAC;AAMD,SAAgB,mBAAmB,EACjC,QAAQ,sBACR,OAAO,WACP,UAAU,cACV,WACA,UACA,GAAG,SACuB;CAC1B,MAAM,QAAQ,gBAAgB,SAAS;CACvC,MAAM,WAAW,mBAAmB,YAAY;CAChD,MAAM,YAAY,0BAA0B;EAAE;EAAO,OAAO,OAAO,WAAW;EAAM;CAAS,CAAC;CAC9F,MAAM,UAAU,eACP;EAAE,GAAG;EAAW,MAAM;EAAM,SAAS;CAAc,IAC1D,CAAC,SAAS,CACZ;CAEA,OACE,oBAAC,wBAAD;EAAwB,OAAO;EAC7B,UAAA,oBAAC,2BAAD;GAA2B,MAAM,UAAU,MAAM;GAC/C,UAAA,oBAAC,4BAAD;IAAuC;IAAW,MAAM,OAAO;IAAgB;IAC7E,UAAA,oBAAC,OAAD;KACE,GAAI;KACO;KACX,2BAAwB;KACxB,GAAK,UAAU,MAAM,OAAO,EAAE,gCAAgC,UAAU,MAAM,KAAK,IAAI,CAAC;KAIxF,GAAK,UAAU,UAAU,EAAE,mCAAmC,GAAG,IAAI,CAAC;KAErE;IACE,CAAA;GACqB,CAAA;EACH,CAAA;CACL,CAAA;AAE5B;;;;;;;AAQA,SAAS,gBAAgB,EAAE,SAAS,SAA6E;CAC/G,IAAI,SAAS,OAAO;CACpB,IAAI,UAAU,GAAG,OAAO;CACxB,OAAO;AACT;;;;;;;;;;;;AAaA,MAAM,qBAAqB;AAE3B,SAAgB,kBAAkB,EAAE,QAAQ,WAAW,WAAW,GAAG,SAAiC;CACpG,MAAM,EACJ,SACA,YACA,mBACA,kBACA,gBACA,cACA,eACA,gBACA,SACA,SACA,yBACE,WAAW;CAKf,MAAM,EAAE,sBAAsB,sBAAsB,cAC5C,iBAAiB;EAAE;EAAS;CAAW,CAAC,GAC9C,CAAC,SAAS,UAAU,CACtB;CAGA,MAAM,kBAAkB,cAAc,+BAA+B,OAAO,GAAG,CAAC,OAAO,CAAC;CACxF,MAAM,uBAAuB,4BAA4B;EAAE;EAAsB;CAAkB,CAAC;CAUpG,MAAM,qBACJ,qBAAqB,SAAS,MAC7B,sBAAsB,QACrB,qBAAqB,MAAM,WAAW,OAAO,OAAO,gBAAgB,KACpE;CA0BJ,MAAM,cAAc,MAAM;CAC1B,sBAAsB;EACpB,qBAAqB,aAAa,oBAAoB;EACtD,aAAa,qBAAqB,aAAa,KAAK;CACtD,GAAG;EAAC;EAAa;EAAsB;CAAoB,CAAC;CAM5D,SAAS,kBAAkB,OAAe;EACxC,IAAI,UAAU,oBAAoB;GAChC,IAAI,oBAAoB,MACtB,aAAa,EAAE,UAAU,gBAAgB,CAAC;QAE1C,cAAc;GAEhB;EACF;EACA,aAAa,EAAE,UAAU,MAAM,CAAC;CAClC;CAEA,OACE,qBAAC,OAAD;EACE,GAAI;EACO;EACX,OAAO;EACP,iBAAe,WAAW,KAAA;EAC1B,+BAA4B;EAC5B,qCAAmC,gBAAgB;GAAE;GAAS,OAAO,QAAQ;EAAO,CAAC;EACrF,GAAK,UAAU,EAAE,uCAAuC,GAAG,IAAI,CAAC;EAPlE,UAAA,CASG,iBACC,oBAAC,OAAD;GAAK,MAAK;GAAQ,qCAAkC;GACjD,UAAA;EACE,CAAA,IACH,MACH,QAAQ,SAAS,IAIhB,qBAAC,SAAS,MAAV;GAAe,wCAAqC;GAApD,UAAA,CACE,oBAAC,SAAS,QAAV,EAAA,UAAkB,oBAAoB,QAAQ,gBAAgB,EAAmB,CAAA,GACjF,qBAAC,YAAD;IACE,OAAO,qBAAqB,qBAAsB,oBAAoB;IACtE,eAAe;IAFjB,UAAA,CAIG,qBAAqB,SAAS,IAI7B,qBAAC,WAAD;KAAW,OAAO;KAAlB,UAAA,CACE,oBAAC,gBAAD,EAAgB,oCAAiC,GAAI,CAAA,GACrD,oBAAC,QAAD;MAAM,+BAA4B;MAC/B,UAAA,oBAAoB,WAAW,8BAA8B;KAC1D,CAAA,CACG;IACT,CAAA,IAAA,MACH,kBAAkB,KAAK,WACtB,qBAAC,WAAD;KAA2B,OAAO,OAAO;KAAzC,UAAA,CACE,oBAAC,gBAAD,EAAgB,oCAAiC,GAAI,CAAA,GACrD,oBAAC,QAAD;MAAM,+BAA4B;MAAI,UAAA,OAAO;KAAW,CAAA,CAC/C;IAHK,GAAA,OAAO,EAGZ,CACZ,CACS;GACC,CAAA,CAAA;EACb,CAAA,IAAA,IACD;;AAET"}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { PaymentMethodSelectInput } from "../../sdk/payment/operations.js";
|
|
2
|
+
import "../../sdk/actions/types.js";
|
|
3
|
+
import { PaymentSubmissionPath } from "../../sdk/payment/submission-registry.js";
|
|
4
|
+
import { PlaceOrderAttempt, SetPlaceOrderAttempt } from "../../sdk/payment/use-place-order.js";
|
|
5
|
+
//#region src/matter/payment/use-payment-method-selection.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* The payment section's selection engine (specs/payment-section-root.md, T5 of the Credit Card epic).
|
|
8
|
+
* Single source of selection behavior behind both providers of the `payment` context —
|
|
9
|
+
* `PaymentSectionRoot` (always-visible) and `PaymentSectionController` (collapsed overlay) — so the two
|
|
10
|
+
* surfaces cannot drift (NFR-4), mirroring `useDeliveryOptionSelection`.
|
|
11
|
+
*
|
|
12
|
+
* It owns four things: group scoping of the method list (Decision 4), the client-only draft facts
|
|
13
|
+
* (Decision 3; widened by T7 with three absent-tolerant facts, specs/card-entry.md Decision 4), the
|
|
14
|
+
* request-sequenced `payment.setMethod` dispatch (ADR-0010), and the inline error state. It
|
|
15
|
+
* deliberately owns *no* mirror of `selectedMethodId` / `selectedAccountId` — both are read from
|
|
16
|
+
* `$data` on every render, which is `delivery-options`' "no local selection state" rule kept
|
|
17
|
+
* verbatim (FR-6).
|
|
18
|
+
*/
|
|
19
|
+
export type PaymentSectionValidator = Readonly<{
|
|
20
|
+
numberMask: string | null;
|
|
21
|
+
numberRegex: string | null;
|
|
22
|
+
cvvMask: string | null;
|
|
23
|
+
cvvRegex: string | null;
|
|
24
|
+
cvvRequired: boolean;
|
|
25
|
+
expirationRequired: boolean;
|
|
26
|
+
holderNameRequired: boolean;
|
|
27
|
+
billingAddressRequired: boolean;
|
|
28
|
+
documentDisplayable: boolean;
|
|
29
|
+
documentRequired: boolean;
|
|
30
|
+
}>;
|
|
31
|
+
export type PaymentSectionMethod = Readonly<{
|
|
32
|
+
id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
groupName: string;
|
|
35
|
+
validator: PaymentSectionValidator;
|
|
36
|
+
}>;
|
|
37
|
+
export type PaymentSectionData = {
|
|
38
|
+
methods?: readonly PaymentSectionMethod[];
|
|
39
|
+
selectedMethodId?: string | null;
|
|
40
|
+
selectedAccountId?: string | null;
|
|
41
|
+
/** Persisted BIN off `payments[0]`. `undefined` while the entity resolves or when a hand-written slice omits it. */
|
|
42
|
+
selectedBin?: string | null;
|
|
43
|
+
} | null;
|
|
44
|
+
/**
|
|
45
|
+
* Which entry path the shopper is on. Aliases the submission channel's key type (T13), which the
|
|
46
|
+
* place-order sequence resolves a continuation by — one definition, so the draft fact and the
|
|
47
|
+
* registry key cannot drift apart.
|
|
48
|
+
*/
|
|
49
|
+
export type PaymentPath = PaymentSubmissionPath;
|
|
50
|
+
/**
|
|
51
|
+
* The client-only half of the pending selection — the facts `Query.payment` cannot carry
|
|
52
|
+
* (Decision 3). Never the card number, CVV, expiration, or holder name: those live only in the card
|
|
53
|
+
* entry's local state (RFC D13).
|
|
54
|
+
*
|
|
55
|
+
* `saveCard`, `cardLabel`, and `document` are T7's widening (specs/card-entry.md, Decision 4): three
|
|
56
|
+
* non-sensitive facts T10 (save-card + label) and T8 (cardholder document) will eventually collect,
|
|
57
|
+
* which T7's submission continuation folds into the gateway card object without collecting them
|
|
58
|
+
* itself. Nullable rather than optional (`?:`), matching `path`/`bin`'s existing "always present,
|
|
59
|
+
* `null` means absent" idiom instead of introducing a second absence convention in the same type.
|
|
60
|
+
* T8/T10 have not landed, so all three are `null` in practice today — every reader must tolerate that.
|
|
61
|
+
*/
|
|
62
|
+
export type PaymentDraft = Readonly<{
|
|
63
|
+
/**
|
|
64
|
+
* `'new-card'` once the shopper leaves the saved-card path — a choice that precedes any server
|
|
65
|
+
* state, since there is no method to select until card entry detects one from the BIN. Falls back
|
|
66
|
+
* to `'saved-card'` while a saved card is the persisted selection, else `null`.
|
|
67
|
+
*/
|
|
68
|
+
path: PaymentPath | null;
|
|
69
|
+
/** Detected leading digits, for installment lookup — never the full number. A successful plain pick clears it. */
|
|
70
|
+
bin: string | null;
|
|
71
|
+
/** Save-this-card flag (T10). `null` until T10 lands and calls `setDraftSaveCard`. */
|
|
72
|
+
saveCard: boolean | null;
|
|
73
|
+
/** Shopper-assigned saved-card label (T10). `null` until T10 lands and calls `setDraftCardLabel`. */
|
|
74
|
+
cardLabel: string | null;
|
|
75
|
+
/** Cardholder document, by country (T8). `null` until T8 lands and calls `setDraftDocument`. */
|
|
76
|
+
document: string | null;
|
|
77
|
+
/**
|
|
78
|
+
* Whether the billing address differs from delivery (T11). Mirrors the gateway transaction
|
|
79
|
+
* descriptor's own `billingAddressDifferent` field name (`sdk/payment/gateway.ts:81`) so the fact
|
|
80
|
+
* cannot drift between this publisher and its only consumer, T13's `usePlaceOrder`. `null` until
|
|
81
|
+
* T11's `BillingAddress` lands and calls `setDraftBillingAddressDifferent`.
|
|
82
|
+
*/
|
|
83
|
+
billingAddressDifferent: boolean | null;
|
|
84
|
+
/**
|
|
85
|
+
* True while `BillingAddress` is on the custom-address path with nothing persisted — the exact
|
|
86
|
+
* window its own `role="status"` notice renders in (T24). Distinct from `billingAddressDifferent`,
|
|
87
|
+
* which says a different address *is* persisted (PR #544 N1) and is what the gateway descriptor
|
|
88
|
+
* reads. `null` and `false` are equivalent to the submit gate; `null` is the never-touched state, so
|
|
89
|
+
* an absent `BillingAddress` never blocks placement. Nullable, not optional — every `PaymentDraft`
|
|
90
|
+
* literal must carry it.
|
|
91
|
+
*/
|
|
92
|
+
billingAddressUnsaved: boolean | null;
|
|
93
|
+
}>;
|
|
94
|
+
/**
|
|
95
|
+
* Whether the BIN the server has persisted agrees with the client's current `draft.bin`.
|
|
96
|
+
*
|
|
97
|
+
* - `'confirmed'` — safe to dispatch a BIN-matched write. Either the two agree with no
|
|
98
|
+
* selection in flight, or `draft.bin` is null so the client asserts no BIN of its own.
|
|
99
|
+
* - `'pending'` — a `payment.setMethod` is in flight, or the two disagree. A consumer
|
|
100
|
+
* must hold any dispatch whose pricing depends on the BIN.
|
|
101
|
+
* - `'unknown'` — the persisted BIN is not readable (the entity has not resolved, or the
|
|
102
|
+
* slice omits the field). Consumers must NOT hold: absence is not evidence of a mismatch.
|
|
103
|
+
*/
|
|
104
|
+
export type PaymentBinConfirmation = 'unknown' | 'pending' | 'confirmed';
|
|
105
|
+
/**
|
|
106
|
+
* Resolves the BIN confirmation signal from the persisted BIN, the client draft BIN,
|
|
107
|
+
* and whether a `payment.setMethod` is in flight. Exported for unit testing without
|
|
108
|
+
* rendering, mirroring how `findInstallmentOption` / `resolveInstallmentTiers` are
|
|
109
|
+
* exported from `installments.tsx`.
|
|
110
|
+
*/
|
|
111
|
+
export declare function resolveBinConfirmation({ selectedBin, draftBin, selectMethodPending }: {
|
|
112
|
+
selectedBin: string | null | undefined;
|
|
113
|
+
draftBin: string | null;
|
|
114
|
+
selectMethodPending: boolean;
|
|
115
|
+
}): PaymentBinConfirmation;
|
|
116
|
+
export type PaymentMethodSelection = {
|
|
117
|
+
/** Methods from the read model, scoped by `group`. Empty until the entity resolves. */
|
|
118
|
+
methods: readonly PaymentSectionMethod[];
|
|
119
|
+
/**
|
|
120
|
+
* Every method from the read model, unfiltered by `group` (fix pass, finding S1). Exposed so
|
|
121
|
+
* `usePaymentSubmitGate()` (`matter/payment/use-payment-submit-gate.ts`) can resolve
|
|
122
|
+
* `persistedMethodId` the same *unscoped* way `usePlaceOrder`'s own pre-flight does
|
|
123
|
+
* (`sdk/payment/use-place-order.ts`, off its own independent fragment slice — neither hook may read
|
|
124
|
+
* the other's module, Decision 2) — a method whose `groupName` sits outside this root's configured
|
|
125
|
+
* `group` must still gate a required document if `usePlaceOrder` is about to treat it as selected.
|
|
126
|
+
* `methods` remains the one every picker and rendering leaf reads from — with one documented
|
|
127
|
+
* exception: `PaymentMethodBadges` (`payment-method-badges.tsx`, T75) reads this unfiltered list
|
|
128
|
+
* instead, because its accepted-method strip is a statement about the store rather than the section,
|
|
129
|
+
* and a wallet method's `groupName` would otherwise never reach it. It detects nothing and renders
|
|
130
|
+
* from no single shared array the way `CardEntry` does, so it carries none of the detection-set
|
|
131
|
+
* risk `methods` exists to guard against (T71, Decision 1).
|
|
132
|
+
*/
|
|
133
|
+
allMethods: readonly PaymentSectionMethod[];
|
|
134
|
+
/**
|
|
135
|
+
* The raw persisted method id, unfiltered by `group` — the exact value `$data.selectedMethodId`
|
|
136
|
+
* carries. Unscoped for the same reason `allMethods` is: `usePaymentSubmitGate` must resolve the
|
|
137
|
+
* method `usePlaceOrder`'s own independent by-id lookup would. Rendering leaves want
|
|
138
|
+
* `selectedMethodId` (T74, Decision 4/7).
|
|
139
|
+
*/
|
|
140
|
+
persistedMethodId: string | null;
|
|
141
|
+
/**
|
|
142
|
+
* The persisted selection **as this section serves it** — `selectedMethod?.id ?? null`. `null` both
|
|
143
|
+
* when nothing is persisted and when what is persisted sits outside `group`; `persistedMethodId`
|
|
144
|
+
* tells those two apart (T74, Decision 4/7). Read straight off `$data` — never mirrored locally.
|
|
145
|
+
*/
|
|
146
|
+
selectedMethodId: string | null;
|
|
147
|
+
/**
|
|
148
|
+
* The persisted selection is one this section does not serve. One of the two selection facts behind
|
|
149
|
+
* `cardPaymentCollapsed` (T74, FR-7; T191, FR-5). `false` whenever `persistedMethodId` is `null`
|
|
150
|
+
* (INV-5), and `false` whenever `methods` could not be read at all rather than genuinely resolving
|
|
151
|
+
* without the persisted id in it (fix pass, finding R1).
|
|
152
|
+
*/
|
|
153
|
+
selectionOutOfScope: boolean;
|
|
154
|
+
/**
|
|
155
|
+
* Whether a mounted `PaymentMethodList` can bring the card form back (fix pass, finding R2; re-derived
|
|
156
|
+
* by T171, FR-6, and by T191, FR-4/Decision 5). `cardPaymentCollapsed` requires it: hiding the card
|
|
157
|
+
* form is only safe when something on the page can un-hide it. With no picker composed, the only way
|
|
158
|
+
* back is typing a new card number, which needs `CardEntry` to stay visible and interactive.
|
|
159
|
+
*
|
|
160
|
+
* The name is historical: it stood for "a picker is mounted" while every in-scope method had its own
|
|
161
|
+
* row, and for "a control can dispatch an in-scope id" from T171 on. `PaymentMethodList` registers the
|
|
162
|
+
* property itself rather than its own presence (`resolveCanRevealCardPayment`): a rendered card option
|
|
163
|
+
* always can, with or without a method it can name, and without one only a card row can.
|
|
164
|
+
*
|
|
165
|
+
* Ref-counted per instance key mirroring `placing`/`billingAddressUnsaved`, though in practice at
|
|
166
|
+
* most one `PaymentMethodList` is ever composed per root.
|
|
167
|
+
*/
|
|
168
|
+
methodListMounted: boolean;
|
|
169
|
+
/**
|
|
170
|
+
* Registers (or retracts) one `PaymentMethodList` instance's ability to bring the card form back.
|
|
171
|
+
* Called by `PaymentMethodList` itself. Same signature as before T171; the boolean's meaning is what
|
|
172
|
+
* changed — see `methodListMounted` above.
|
|
173
|
+
*/
|
|
174
|
+
setMethodListMounted: (key: string, mounted: boolean) => void;
|
|
175
|
+
/**
|
|
176
|
+
* Card mode is holding the card form open over a persisted selection that is not a card or that this
|
|
177
|
+
* section does not serve (T191, FR-3, Decision 4). `selectNewCard()` turns card mode on, and a plain
|
|
178
|
+
* pick of a method that is not a card turns it off once it succeeds. It counts only over such a
|
|
179
|
+
* selection, so it is `false` whenever nothing or an in-scope card method is persisted (INV-5). Local
|
|
180
|
+
* to this engine: nothing is sent to record it, and a reload forgets it.
|
|
181
|
+
*/
|
|
182
|
+
cardModeActive: boolean;
|
|
183
|
+
/**
|
|
184
|
+
* The card payment is collapsed (T191, FR-5, Decision 2): the persisted selection is one this section
|
|
185
|
+
* does not serve or one that is not a card, `cardModeActive` is `false`, and `methodListMounted` is
|
|
186
|
+
* `true`. The one collapse fact every card leaf reads — `CardEntry` hides itself and stays mounted,
|
|
187
|
+
* the others render nothing — so no two leaves can disagree.
|
|
188
|
+
*/
|
|
189
|
+
cardPaymentCollapsed: boolean;
|
|
190
|
+
/** The persisted saved-card account reference, read straight off `$data`. */
|
|
191
|
+
selectedAccountId: string | null;
|
|
192
|
+
/**
|
|
193
|
+
* The persisted BIN **as this section serves it** — `null` unless the persisted selection resolves
|
|
194
|
+
* inside the scoped `methods` list, mirroring `selectedMethodId` (fix pass, finding R3). Unscoped
|
|
195
|
+
* before this fix, `useInstallmentsSelection`'s own `draft.bin ?? selectedBin` fallback (its only
|
|
196
|
+
* consumer) could still resolve an out-of-scope method's installment tiers by BIN alone even once
|
|
197
|
+
* FR-9's methodId-arm fix landed — the methodId half of that lookup went `null` for an out-of-scope
|
|
198
|
+
* selection, but the BIN half never did. Never mirrored, like its sibling `selectedMethodId`.
|
|
199
|
+
*
|
|
200
|
+
* Carries the same `methodsReadable` guard as `isSelectionOutOfScope` (fix pass round 2): an absent
|
|
201
|
+
* `methods` forces `selectedMethod` to `null` the same way a genuine out-of-scope selection does, so
|
|
202
|
+
* without the guard this read `null` for the hand-written partial `$data` slice `methodsReadable`'s
|
|
203
|
+
* own doc comment describes as explicitly supported — even while `selectionOutOfScope` correctly
|
|
204
|
+
* stayed `false` for that same slice.
|
|
205
|
+
*/
|
|
206
|
+
selectedBin: string | null;
|
|
207
|
+
/** Whether `draft.bin` has actually been persisted. See `PaymentBinConfirmation`. */
|
|
208
|
+
binConfirmation: PaymentBinConfirmation;
|
|
209
|
+
/** `selectedMethodId` resolved against the scoped list; carries the validator config leaves gate on. */
|
|
210
|
+
selectedMethod: PaymentSectionMethod | null;
|
|
211
|
+
/** The client-only facts the read model cannot carry. */
|
|
212
|
+
draft: PaymentDraft;
|
|
213
|
+
/** True until the `Payment` entity resolves. */
|
|
214
|
+
loading: boolean;
|
|
215
|
+
/** Message of the first `userError` from the latest selection, or `null`. */
|
|
216
|
+
selectionError: string | null;
|
|
217
|
+
/**
|
|
218
|
+
* Dispatches `payment.setMethod` with T4's exact input, request-sequenced so only the latest call
|
|
219
|
+
* owns the error state. Records **only the draft facts the input carries**: an `accountId` is the
|
|
220
|
+
* saved-card path, a `bin` is the new-card path, and an input with neither — a plain method pick —
|
|
221
|
+
* leaves both until it succeeds, then releases `draft.bin` and, for a method that is not a card, ends
|
|
222
|
+
* card mode (T191, FR-3). A no-op (no error, no throw) without the action.
|
|
223
|
+
*/
|
|
224
|
+
selectMethod: (input: PaymentMethodSelectInput) => void;
|
|
225
|
+
/**
|
|
226
|
+
* Switches to the new-card path, clears the detected BIN, clears any selection error — the message
|
|
227
|
+
* described a picker the shopper has left — and turns card mode on (T191, FR-3). Dispatches nothing:
|
|
228
|
+
* no method is known until card entry detects one, so there is nothing to select yet (US-4).
|
|
229
|
+
*/
|
|
230
|
+
selectNewCard: () => void;
|
|
231
|
+
/** Publishes or clears the detected BIN with no write, for per-keystroke detection. */
|
|
232
|
+
setDraftBin: (bin: string | null) => void;
|
|
233
|
+
/**
|
|
234
|
+
* Publishes or clears the save-card flag (T10). A bare `useState` passthrough — no validation, no
|
|
235
|
+
* dispatch. Nothing in this round calls it; it exists for T10 to consume.
|
|
236
|
+
*/
|
|
237
|
+
setDraftSaveCard: (saveCard: boolean | null) => void;
|
|
238
|
+
/** Publishes or clears the saved-card label (T10). Bare passthrough, mirroring `setDraftSaveCard`. */
|
|
239
|
+
setDraftCardLabel: (cardLabel: string | null) => void;
|
|
240
|
+
/** Publishes or clears the cardholder document (T8). Bare passthrough, mirroring `setDraftSaveCard`. */
|
|
241
|
+
setDraftDocument: (document: string | null) => void;
|
|
242
|
+
/**
|
|
243
|
+
* Publishes or clears the billing-address-differs flag (T11). Bare passthrough — no validation, no
|
|
244
|
+
* dispatch — mirroring `setDraftSaveCard`/`setDraftCardLabel`/`setDraftDocument`.
|
|
245
|
+
*/
|
|
246
|
+
setDraftBillingAddressDifferent: (billingAddressDifferent: boolean | null) => void;
|
|
247
|
+
/**
|
|
248
|
+
* Publishes or clears whether `BillingAddress` is on the custom-address path with nothing persisted
|
|
249
|
+
* (T24) — **not** a bare passthrough like the setters above (fix pass, findings R2/S2). Takes a
|
|
250
|
+
* caller-owned, per-instance `key` (`billing-address.tsx`'s own `useId()`) because
|
|
251
|
+
* `billingAddressUnsaved` is a single fact shared by every `BillingAddress` mounted under this root —
|
|
252
|
+
* `billing-address.md` documents multiple instances as allowed — and an unconditional
|
|
253
|
+
* `setDraftBillingAddressUnsaved(false)` on one instance's unmount would silently clear a *second*,
|
|
254
|
+
* still-open, still-unsaved instance's own block while its own notice keeps rendering (measured:
|
|
255
|
+
* exactly this reproduction blocks placement in the UI and bills the declined address anyway). Each
|
|
256
|
+
* instance's own key is added to an internal set while `unsaved` is `true` and removed while it is
|
|
257
|
+
* `false` — on every call, not only cleanup — so an unmount only ever retracts *that* instance's own
|
|
258
|
+
* contribution. The published `PaymentDraft.billingAddressUnsaved` is `true` while the set is
|
|
259
|
+
* non-empty, `false` once every instance that has ever called this resolves to nothing outstanding,
|
|
260
|
+
* and `null` only before any instance has ever called it at all.
|
|
261
|
+
*/
|
|
262
|
+
setDraftBillingAddressUnsaved: (key: string, unsaved: boolean) => void;
|
|
263
|
+
/**
|
|
264
|
+
* An order-placement sequence is in flight (T28, specs/place-order-blocking-state.md; REQ-08 s2).
|
|
265
|
+
* A top-level field beside `loading`, deliberately **not** on `draft`: a transition must not change
|
|
266
|
+
* `draft`'s identity, or every keystroke-memoized `draft` consumer would recompute on it (NFR-1).
|
|
267
|
+
* True from the moment the sequence opens a transaction until it settles — never for an attempt
|
|
268
|
+
* refused synchronously, because no sequence runs (FR-3).
|
|
269
|
+
*/
|
|
270
|
+
placing: boolean;
|
|
271
|
+
/**
|
|
272
|
+
* Publishes one `PlaceOrder` instance's in-flight state. Ref-counted per `key`; a retraction only
|
|
273
|
+
* ever removes its own key. Bails out to the same state by reference when membership already
|
|
274
|
+
* matches, so an effect re-running on an unrelated dependency change does not re-render the section
|
|
275
|
+
* (NFR-2).
|
|
276
|
+
*/
|
|
277
|
+
setPlacing: (key: string, placing: boolean) => void;
|
|
278
|
+
/**
|
|
279
|
+
* The current order-placement attempt (T29, specs/make-an-order-attempt-survive-a.md), or `null`
|
|
280
|
+
* when none has started under this root. Sits beside `placing` on this same contract for the
|
|
281
|
+
* identical reason `placing` does (T28): one fact, several leaves, must outlive an overlay's own
|
|
282
|
+
* mount — `PlaceOrder` publishes it, `usePlaceOrder` (sdk/payment) reads and writes it, and a remount
|
|
283
|
+
* reads the survivor rather than starting over.
|
|
284
|
+
*/
|
|
285
|
+
attempt: PlaceOrderAttempt | null;
|
|
286
|
+
/**
|
|
287
|
+
* Records a phase transition. Unlike `setPlacing`, not ref-counted: only one attempt exists at a
|
|
288
|
+
* time under a root (the shared `placing` gate keeps a second sequence from ever opening — T28), so a
|
|
289
|
+
* single last-write-wins `useState` setter is enough. Identity is stable for the root's lifetime, so
|
|
290
|
+
* an in-flight `run()`/`resume()` closure (sdk/payment/use-place-order.ts) keeps writing after the
|
|
291
|
+
* CTA that started it unmounts (NFR-3) — the mechanism that makes the attempt outlive the mount at all.
|
|
292
|
+
*
|
|
293
|
+
* Widened past a plain value (fix pass, High finding in sdk/payment/use-place-order.ts): the raw
|
|
294
|
+
* `useState` setter this field is assigned below has always accepted an updater function too, so
|
|
295
|
+
* accepting `SetPlaceOrderAttempt` here merely lets a caller use a capability the setter already had
|
|
296
|
+
* — `usePlaceOrder`'s own staleness guard needs it to keep a losing racer's late write from
|
|
297
|
+
* clobbering a newer outcome.
|
|
298
|
+
*/
|
|
299
|
+
setAttempt: SetPlaceOrderAttempt;
|
|
300
|
+
};
|
|
301
|
+
//#endregion
|
|
302
|
+
//# sourceMappingURL=use-payment-method-selection.d.ts.map
|