@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,284 @@
|
|
|
1
|
+
# BillingAddress
|
|
2
|
+
|
|
3
|
+
**Category:** commerce
|
|
4
|
+
**Purpose:** Lets a shopper confirm their billing address matches delivery (the default) or provide a different, country-specific billing address for their card. Persists the choice via a dedicated action; publishes only a differs/does-not-differ flag onto the shared payment state, never the address itself.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Visual cues (how to identify in an image)
|
|
9
|
+
|
|
10
|
+
Use these signals to detect that a BillingAddress block is present. Position on the page is irrelevant
|
|
11
|
+
for detection — it can appear anywhere inside a payment section. The image is the source of truth for
|
|
12
|
+
layout.
|
|
13
|
+
|
|
14
|
+
A BillingAddress block contains any combination of:
|
|
15
|
+
|
|
16
|
+
- A short **disclaimer sentence above the checkbox**, stating the billing address must match the one
|
|
17
|
+
on the card or bank statement or the bank may decline the order. It renders by default
|
|
18
|
+
(`billingMatchNoticeVisible: true`) — treat it as part of this block, not a separate `Text` element,
|
|
19
|
+
even when the image shows it as the first line in the region.
|
|
20
|
+
- A single **checkbox with a label reading something like "Billing address same as delivery address"**,
|
|
21
|
+
sitting inside (or directly beside) a credit-card entry form.
|
|
22
|
+
- When shown **checked** in the image: nothing renders below it — no address and no inputs.
|
|
23
|
+
- When shown **unchecked** in the image: a **full address form** appears below it — a country
|
|
24
|
+
dropdown, then a variable set of text fields (street, number, complement, neighborhood/bairro, city,
|
|
25
|
+
state, postal code — the exact set varies by the selected country), plus a save/submit button
|
|
26
|
+
distinct from the page's main "Place order" button.
|
|
27
|
+
- This block always sits **inside a checkout payment section**, near card-number fields, a saved-cards
|
|
28
|
+
list, or a save-card checkbox — never on a product page, cart page, or the delivery/shipping address
|
|
29
|
+
step itself (that is a separate, visually similar but distinct block — see When NOT to use).
|
|
30
|
+
|
|
31
|
+
If the image shows a same-as-delivery-style toggle specifically inside the **payment**/card area with a
|
|
32
|
+
country-adaptive address form gated behind it, use `BillingAddress`. If the toggle or form instead
|
|
33
|
+
governs the **delivery** address (usually appears earlier in the checkout flow, before payment, and
|
|
34
|
+
often includes a saved-addresses list or a "use this saved address" picker), that is
|
|
35
|
+
`ShippingAddress`, not this component.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## When to use
|
|
40
|
+
|
|
41
|
+
- Inside a checkout's payment section, as a sibling of the card-entry fields, the saved-cards list,
|
|
42
|
+
the installment selector, and the save-card control, so a shopper can confirm or override the
|
|
43
|
+
billing address used for their card.
|
|
44
|
+
|
|
45
|
+
## When NOT to use
|
|
46
|
+
|
|
47
|
+
- The delivery/shipping address step, including its own saved-address list and country form → use
|
|
48
|
+
`ShippingAddress` instead. That component owns the address the order ships to; `BillingAddress` never
|
|
49
|
+
writes to it and never reads its saved-addresses list.
|
|
50
|
+
- A general "same address for everything" single toggle spanning both shipping and billing in one
|
|
51
|
+
control → not supported. `BillingAddress` is its own independent toggle scoped to billing only.
|
|
52
|
+
- Collecting the shopper's name/document for the card itself → use `CardEntry` (holder name) or
|
|
53
|
+
`CardholderDocument` (tax document). `BillingAddress` never renders those fields.
|
|
54
|
+
- Anywhere outside a payment section → `BillingAddress` requires a `PaymentSectionRoot`/
|
|
55
|
+
`PaymentSectionController` ancestor (it reads the selected method's validator and the shared payment
|
|
56
|
+
draft). Placing it without one is a build/runtime error, not a silent no-op.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Composition
|
|
61
|
+
|
|
62
|
+
`BillingAddress` is a single leaf — no root, no required children, no sub-components:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
PaymentSectionRoot (or PaymentSectionController)
|
|
66
|
+
├── CardEntry (sibling — new-card fields)
|
|
67
|
+
├── SavedCards (sibling — saved-card picker)
|
|
68
|
+
├── Installments (sibling — installment selector)
|
|
69
|
+
├── SaveCard (sibling — save-this-card toggle)
|
|
70
|
+
├── BillingAddress (sibling — this component)
|
|
71
|
+
└── PlaceOrder (sibling — the submit CTA)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Place it as a **sibling** of `CardEntry`, `SavedCards`, `Installments`, `SaveCard`, and `PlaceOrder`,
|
|
75
|
+
never nested inside any of them — the same convention `Installments`/`SaveCard` already follow. It
|
|
76
|
+
does not require a `SessionRoot` — unlike `SaveCard`, billing address applies to guest and
|
|
77
|
+
authenticated shoppers alike.
|
|
78
|
+
|
|
79
|
+
It renders nothing while the card payment is collapsed — the persisted selection is a method that is
|
|
80
|
+
not a card, or one the section does not serve, and a composed `PaymentMethodList` can bring the card
|
|
81
|
+
form back (see [[payment]]). It stays mounted, so an open form keeps what the shopper typed for when the
|
|
82
|
+
card comes back, and while collapsed it publishes no unsaved address: Place Order is never refused over
|
|
83
|
+
a form the shopper cannot see.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Components
|
|
88
|
+
|
|
89
|
+
### BillingAddress
|
|
90
|
+
|
|
91
|
+
| Prop | Type | Default | Notes |
|
|
92
|
+
|------|------|---------|-------|
|
|
93
|
+
| `sameAsDeliveryLabel` | string | `"Billing address same as delivery address"` | The toggle's own label text. |
|
|
94
|
+
| `defaultCountry` | string | `"BRA"` | Initial country selected when the custom form first mounts. |
|
|
95
|
+
| `countryLabel` | string | `"Country"` | Label for the country selector inside the custom form. |
|
|
96
|
+
| `recipientNameLabel` | string | `"Recipient's name"` | Label for the recipient-name descriptor field. Not rendered by the custom-address form today — the write contract has no matching input slot — kept for parity with the other seven field labels. |
|
|
97
|
+
| `streetLabel` | string | `"Street"` | Label for the street descriptor field. |
|
|
98
|
+
| `numberLabel` | string | `"Number"` | Label for the street-number descriptor field. |
|
|
99
|
+
| `complementLabel` | string | `"Complement (optional)"` | Label for the complement descriptor field. Its "(optional)" suffix is the only signal that the field is not required — the actual `required` attribute is driven independently, so authoring this without the suffix loses the visual cue, not the behavior. |
|
|
100
|
+
| `neighborhoodLabel` | string | `"Neighborhood"` | Label for the neighborhood descriptor field. Unused when the active country's descriptor has no `neighborhood` field (e.g. USA). |
|
|
101
|
+
| `cityLabel` | string | `"City"` | Label for the city descriptor field. |
|
|
102
|
+
| `stateLabel` | string | `"State"` | Label for the state descriptor field. |
|
|
103
|
+
| `postalCodeLabel` | string | `"Postal code"` | Label for the postal-code descriptor field. |
|
|
104
|
+
| `submitLabel` | string | `"Save billing address"` | Label for the button that persists a custom address. |
|
|
105
|
+
| `submitPendingLabel` | string | `"Saving..."` | Submit button's label while the write is in flight. |
|
|
106
|
+
| `requiredNoticeLabel` | string | `"This payment method requires a billing address."` | Shown when the selected method's validator marks a billing address required. |
|
|
107
|
+
| `unsavedAddressNoticeLabel` | string | `"No billing address has been saved yet — the delivery address will be used unless you save one below."` | Shown, `role="status"`, while the custom-address form is open and no address has been persisted yet. |
|
|
108
|
+
| `billingMatchNoticeLabel` | string | `"Your billing address must match the one on your card or bank statement, or your bank may decline the order."` | Always-on billing-match disclaimer. Gated only by this prop and `billingMatchNoticeVisible` — unlike the two notices above, it never changes with the toggle, the form, `billingAddressRequired`, or a saved address. |
|
|
109
|
+
| `billingMatchNoticeVisible` | boolean | `true` | Switches the billing-match disclaimer above on or off. A blank `billingMatchNoticeLabel` also renders nothing, even while this is `true` — the two rules never combine into an empty element. |
|
|
110
|
+
| `fieldRequiredMessage` | string | `"This field is required."` | Shown as an inline field error under a required, blank address field once it has been touched (T38) — see Accessibility Contract below. One prop, not one per field: every rendered field but `complement` shares the same rule. An authored empty or whitespace-only string falls back to this default. |
|
|
111
|
+
| `disabled` | boolean | `false` | Disables the toggle, every form field, the country selector, and the submit button. |
|
|
112
|
+
|
|
113
|
+
**Data:** declares no fragment and no `fetchStrategy`, so it fetches no shopper data. It reads only the
|
|
114
|
+
payment context (`requiresContext: 'payment'`): the selected method's validator and the shared payment
|
|
115
|
+
draft. **Nothing prefills the custom-address form** — there is no server field that echoes back an
|
|
116
|
+
already-persisted billing address, so the custom form always starts blank.
|
|
117
|
+
|
|
118
|
+
**The toggle defaults to checked** (same as delivery) and stays that way until the shopper
|
|
119
|
+
un-checks it. Un-checking reveals a blank, country-specific form, as this component's own local UI
|
|
120
|
+
state — nothing is sent to the server until the shopper fills it in and clicks the submit button.
|
|
121
|
+
**Re-checking the toggle only tells the server to fall back to the delivery address if a custom address
|
|
122
|
+
was actually saved before** (`draft.billingAddressDifferent === true`); otherwise re-checking is a local
|
|
123
|
+
no-op — there is nothing on the server to revert. This is deliberate: the published fact this component
|
|
124
|
+
shares with the rest of the payment section only ever claims "different" once an address has actually
|
|
125
|
+
been persisted, never merely because the shopper opened the form.
|
|
126
|
+
|
|
127
|
+
**⚠ While the form is open and nothing has been saved yet, an inline `role="status"` notice
|
|
128
|
+
(`unsavedAddressNoticeLabel`) warns that the delivery address will be used unless an address is saved.**
|
|
129
|
+
This notice itself is advisory only, not a submit gate: a shopper can still fill the whole form without
|
|
130
|
+
ever pressing this component's own submit button, and the native `required` attribute on the form's own
|
|
131
|
+
fields does not prevent that either — there is no `<form>` element anywhere in this component, so
|
|
132
|
+
`required` is real for assistive technology but not wired to anything that blocks a click on a
|
|
133
|
+
different button in a different component. Since T38 each rendered field (all but `complement`) does
|
|
134
|
+
announce its own validity once touched — see Accessibility Contract below — but that announcement is
|
|
135
|
+
driven by a local blur-tracked state, never by `required`'s own native validity, and it still does not
|
|
136
|
+
gate the submit control; the submit gate is `isBillingAddressFormValid`, read at the submit button's
|
|
137
|
+
own `disabled`.
|
|
138
|
+
|
|
139
|
+
**The hard block itself lives elsewhere.** This component also publishes the exact same fact the notice
|
|
140
|
+
renders from onto the shared payment draft (`billingAddressUnsaved`), and T24's submit gate
|
|
141
|
+
(`usePaymentSubmitGate()`, consumed by `PlaceOrder`) refuses placement client-side while that fact is
|
|
142
|
+
true — so a shopper who declines "same as delivery" and never saves an address cannot place the order,
|
|
143
|
+
even without ever touching this component's own submit button. Neither `required` nor the notice does
|
|
144
|
+
the blocking themselves; both stay exactly as advisory as described above.
|
|
145
|
+
|
|
146
|
+
**A third notice, `billingMatchNoticeLabel`, is configuration-driven, not state-driven like the two
|
|
147
|
+
above.** The required-note and the unsaved-notice each appear and disappear by runtime state (the
|
|
148
|
+
selected method's validator; the form being open with nothing saved yet). This one does not: its
|
|
149
|
+
presence is a pure function of `billingMatchNoticeLabel` and `billingMatchNoticeVisible` alone, and it
|
|
150
|
+
renders before the toggle field on every render — including the default state, where "same as delivery"
|
|
151
|
+
stays checked and the shopper never opens the form. It never reacts to the toggle, to
|
|
152
|
+
`billingAddressRequired`, to a saved address, or to order placement; a merchant switches it off with
|
|
153
|
+
`billingMatchNoticeVisible: false`, never by anything happening at runtime. It carries its own hook,
|
|
154
|
+
`data-fs-billing-address-match-notice`, and renders as a plain paragraph with no ARIA role — it is a
|
|
155
|
+
static disclaimer, not a live region reacting to an event.
|
|
156
|
+
|
|
157
|
+
**Country adaptivity is entirely data-driven**, reusing the same country descriptors the delivery
|
|
158
|
+
address step uses: Brazil shows a neighborhood/bairro field, the United States does not, and adding a
|
|
159
|
+
new country anywhere in this codebase is a data-only change that this component picks up automatically
|
|
160
|
+
— never a code change here.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Canonical schema example
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{
|
|
168
|
+
"payment-section": {
|
|
169
|
+
"type": "PaymentSectionRoot",
|
|
170
|
+
"children": ["card-entry", "saved-cards", "installments", "save-card", "billing-address", "place-order"]
|
|
171
|
+
},
|
|
172
|
+
"billing-address": {
|
|
173
|
+
"type": "BillingAddress",
|
|
174
|
+
"props": { "sameAsDeliveryLabel": "Billing address same as delivery address" },
|
|
175
|
+
"style": ["flex", "flex-col", "gap-3"]
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Common mistakes
|
|
183
|
+
|
|
184
|
+
- **Inventing `BillingAddressToggle` or `SameAsDeliveryControl`.** The component is `BillingAddress`.
|
|
185
|
+
- **Nesting `BillingAddress` inside `ShippingAddress`, `CardEntry`, or `SavedCards`.** It must be a
|
|
186
|
+
sibling of all of them under the same `PaymentSectionRoot`, exactly like `Installments`/`SaveCard`.
|
|
187
|
+
- **Expecting the custom form to pre-fill from the delivery address.** It always starts blank — there
|
|
188
|
+
is no read model that echoes a previously persisted billing address (see the `.tsx` doc comment,
|
|
189
|
+
Decision 3, for the measured reason).
|
|
190
|
+
- **Expecting the toggle to dispatch on every keystroke.** It dispatches at, at most, two points: the
|
|
191
|
+
toggle's transition back to "same as delivery" (only when an address was actually saved — otherwise
|
|
192
|
+
it's a local no-op), and the custom form's explicit submit button — never per field edit.
|
|
193
|
+
- **Reaching for a `recipientName` field in the custom form.** Deliberately absent — the billing-address
|
|
194
|
+
write action has no such field, unlike the delivery address's own form.
|
|
195
|
+
- **Requiring a `SessionRoot` ancestor.** Not needed — unlike `SaveCard`, this component has no
|
|
196
|
+
authentication gate.
|
|
197
|
+
- **Assuming the `required` attribute or the unsaved-address notice itself blocks Place Order.** Neither
|
|
198
|
+
does — there is no `<form>` in this component and no submission gate wired to either directly. Place
|
|
199
|
+
Order *is* refused while the notice's exact condition holds, but that block runs in T24's submit gate
|
|
200
|
+
(`usePaymentSubmitGate()`, consumed by `PlaceOrder`), which reads the fact this component publishes
|
|
201
|
+
(`billingAddressUnsaved`) rather than either of those two elements.
|
|
202
|
+
- **Rendering more than one `BillingAddress` under the same `PaymentSectionRoot`/`PaymentSectionController`.**
|
|
203
|
+
Not forbidden mechanically. Every instance re-syncs its own toggle state whenever the one shared
|
|
204
|
+
draft's `billingAddressDifferent` changes, so all instances converge once any of them actually persists
|
|
205
|
+
or reverts an address — but a purely local transition (unchecking, or re-checking with nothing yet
|
|
206
|
+
persisted) never touches the draft, so two instances can briefly disagree on-screen without
|
|
207
|
+
misinforming the gateway (nothing is sent to the server until a submit succeeds either way). The
|
|
208
|
+
submit gate's own fact (`billingAddressUnsaved`) is tracked per instance internally (each instance's
|
|
209
|
+
own `useId()`), specifically so one instance unmounting — an accordion collapsing, a tab switching, a
|
|
210
|
+
responsive layout branch — never clears a *second*, still-open, still-unsaved instance's own block
|
|
211
|
+
(fix pass, findings R2/S2). Nothing in this codebase composes it that way regardless — treat it as a
|
|
212
|
+
single, sibling-placed leaf per payment section, matching every other sibling in the Composition
|
|
213
|
+
diagram above.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Styling
|
|
218
|
+
|
|
219
|
+
Single DOM root wrapping the toggle field, an always-mounted panel that holds the country-specific form
|
|
220
|
+
while the box is unchecked, and an optional inline error.
|
|
221
|
+
|
|
222
|
+
| Component | What you style | Plausible classes |
|
|
223
|
+
|---|---|---|
|
|
224
|
+
| `BillingAddress` (root `div`) | Stacks the toggle, the panel, and the error | `flex flex-col gap-[12px]` |
|
|
225
|
+
| Same as delivery (`data-fs-billing-address-same-as-delivery`) | Present on the root while the box is checked, absent while it is unchecked. It sits on the node the style array styles, so target it as `data-[fs-billing-address-same-as-delivery]:…`, never with a descendant variant | `data-[fs-billing-address-same-as-delivery]:gap-[8px]` |
|
|
226
|
+
| Panel (`data-fs-billing-address-panel`) | Always present, right after the toggle field, and the toggle's `aria-controls` target. `hidden` and empty while the box is checked; holds the custom-form wrapper while unchecked. Never give it a display utility (`flex`, `grid`, `block`, …): one defeats `hidden` | none — style the form inside it |
|
|
227
|
+
| Custom-form wrapper (`data-fs-billing-address-form`), inside the panel | Field grid | `grid grid-cols-2 gap-[8px]` |
|
|
228
|
+
| Required-billing-address note (`data-fs-billing-address-required-note`) | De-emphasized helper text | `text-[12px] text-[#666666]` |
|
|
229
|
+
| Unsaved-address notice (`data-fs-billing-address-unsaved-notice`) | De-emphasized, non-error helper text | `text-[12px] text-[#666666]` |
|
|
230
|
+
| Billing-match disclaimer (`data-fs-billing-address-match-notice`) | De-emphasized helper text, first child of the root | `text-[12px] text-[#666666]` |
|
|
231
|
+
| Field error (`data-fs-field-error`) | Per-field validation message, shown once a required field is touched and left blank (T38) | `text-[12px] font-medium text-[#B00020]` |
|
|
232
|
+
| Inline error (`data-fs-billing-address-error`) | Warning-toned alert text | `text-[12px] font-medium text-[#B00020]` |
|
|
233
|
+
| Placing (`data-fs-billing-address-placing`) | Present while an order-placement sequence is in flight (T28) — distinct from `data-fs-billing-address-pending`, which this component already emits for its own local submit dispatch; the root is also `inert` and `aria-disabled` while placing | `opacity-60 cursor-not-allowed` |
|
|
234
|
+
|
|
235
|
+
Styled example:
|
|
236
|
+
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"billing-address": {
|
|
240
|
+
"type": "BillingAddress",
|
|
241
|
+
"style": ["flex", "flex-col", "gap-3"]
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
There is no per-field styling hook beyond the particles' own `data-fs-checkbox-*` / `data-fs-field-*` /
|
|
247
|
+
`data-fs-select-*` attributes — style the checked state via Base UI's own `data-checked` on
|
|
248
|
+
`Checkbox.Root`, per the `style-components` skill.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Accessibility Contract
|
|
253
|
+
|
|
254
|
+
**What the platform promises:** every rendered custom-address field (all but `complement`) announces
|
|
255
|
+
its own validity once the shopper has touched it — focus, type nothing, and blur a required field, and
|
|
256
|
+
it carries `aria-invalid="true"` plus an error message (`fieldRequiredMessage`) associated through
|
|
257
|
+
`aria-describedby`; nothing is marked invalid before it is touched, and `complement` — the one optional
|
|
258
|
+
field — is never marked invalid. This mirrors `CardEntry`'s own field-level validity mechanism exactly.
|
|
259
|
+
Each field also carries its own `Field.Label`, authorable per field. A default `:focus-visible` outline
|
|
260
|
+
is painted from the platform's own stylesheet on every field in this component.
|
|
261
|
+
|
|
262
|
+
The "same as delivery" toggle references the panel below it through `aria-controls` in both states. The
|
|
263
|
+
panel is always mounted, so the reference never dangles: it is `hidden` and empty while the box is
|
|
264
|
+
checked, and it holds the custom-address form while the box is unchecked.
|
|
265
|
+
|
|
266
|
+
**What the platform does not promise:** colour contrast. `BillingAddress` (and every other payment
|
|
267
|
+
component) exposes `data-fs-*` hooks so a merchant can author contrast-compliant swatches, but no
|
|
268
|
+
component in this package measures or guarantees any contrast ratio — that is the merchant's theme, not
|
|
269
|
+
this platform.
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Related
|
|
274
|
+
|
|
275
|
+
- [[shipping-address]] — the delivery-address step this component's own country descriptors are
|
|
276
|
+
imported from; a separate composition entirely, never a parent or child of `BillingAddress`.
|
|
277
|
+
- [[save-card]] — the closest structural sibling authored the same way: a single leaf requiring
|
|
278
|
+
`PaymentSectionRoot`, action-dispatch-with-pending-state. Unlike this component, it also declares its
|
|
279
|
+
own fragment.
|
|
280
|
+
- [[cardholder-document]] — the closest sibling for reading a per-method validator flag
|
|
281
|
+
(`documentRequired`/`documentDisplayable`) the same way this component reads
|
|
282
|
+
`billingAddressRequired`.
|
|
283
|
+
- [[installments]] — another single-leaf sibling requiring only `PaymentSectionRoot`, no Root/leaf
|
|
284
|
+
split, sibling placement under the same section.
|
package/dist/docs/buy-button.md
CHANGED
|
@@ -35,7 +35,7 @@ BuyButtonRoot
|
|
|
35
35
|
└── BuyButtonButton
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
`BuyButtonButton` dispatches the cart `addItem` action through the compiler-injected `$actions.cart.addItem` prop. The add-to-cart write is **context-free** — it reads and writes the Apollo cache directly — so `BuyButton` needs **no `CartRoot` ancestor** (
|
|
38
|
+
`BuyButtonButton` dispatches the cart `addItem` action through the compiler-injected `$actions.cart.addItem` prop. The add-to-cart write is **context-free** — it reads and writes the Apollo cache directly — so `BuyButton` needs **no `CartRoot` ancestor** (docs/storefront-core/reference-free-particle-model.md § Write side — actions, ADR-0010). Drop it on a PDP, a product card, a shelf, or the header alike.
|
|
39
39
|
|
|
40
40
|
Child order reflects visual order.
|
|
41
41
|
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# CardEntry
|
|
2
|
+
|
|
3
|
+
**Category:** commerce
|
|
4
|
+
**Purpose:** New-card entry for checkout payment: a masked card number with live network detection, holder name, expiration, and CVV. Masks, presence, and required-ness are driven by the selected payment method's server-side validator config, never hardcoded. Owns and answers the new-card submission itself — it never hands the card number or CVV to any other component.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Visual cues (how to identify in an image)
|
|
9
|
+
|
|
10
|
+
Use these signals to detect that a card-entry block is present. Position on the page is irrelevant for detection. The image is the source of truth for layout.
|
|
11
|
+
|
|
12
|
+
A card-entry block contains any combination of:
|
|
13
|
+
|
|
14
|
+
- A **card number input**, often showing a placeholder like `0000 0000 0000 0000` and grouped in blocks of four digits.
|
|
15
|
+
- A **single recognized-network mark** (Visa, Mastercard, Amex, Elo, Diners, …) sitting **inside** the number field's own box, usually at its trailing edge. It is absent until the typed digits identify exactly one network, and there is never more than one at a time — this is "the network I just typed", not "the networks this store accepts". A network whose name has no mapped icon shows its name as text in the same slot. For "every method this store accepts", shown before anything is typed, the component is `AcceptedPaymentMethodBadges`, not this one.
|
|
16
|
+
- A **cardholder name input**, usually labeled "Nome no cartão" / "Name on card".
|
|
17
|
+
- An **expiration input**, labeled "Validade" / "Expiry", showing `MM/AA` or `MM/YY`.
|
|
18
|
+
- A **CVV / security-code input**, labeled "CVV" / "Código de segurança", usually 3–4 digits, often placed beside the expiration field, usually with a short helper line underneath it explaining where to find the code. Its digits render **obscured** (dots), same visual family as a password field, even though it is not one under the hood (see Composition for why).
|
|
19
|
+
- The four fields are **grouped visually** (a bordered fieldset, a card-shaped panel, or simple vertical/grid stacking) as one unit, distinct from the payment-method picker above it and the installment selector below it.
|
|
20
|
+
|
|
21
|
+
If the image shows a checkout payment step with these four fields together, use `CardEntry`. If it shows only a masked recap like *"Visa •••• 4242"* with no editable inputs, that is a read-only summary, not this component.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## When to use
|
|
26
|
+
|
|
27
|
+
- The "enter a new card" step of a credit-card checkout payment section.
|
|
28
|
+
- Anywhere a shopper types a fresh card's number, holder name, expiration, and CVV.
|
|
29
|
+
|
|
30
|
+
## When NOT to use
|
|
31
|
+
|
|
32
|
+
- A read-only recap of an already-selected card (*"Visa •••• 4242"*) → use a payment summary component; this component always renders editable inputs.
|
|
33
|
+
- Re-entering only the CVV for a previously saved card → a distinct saved-cards component; do not reuse `CardEntry` for that flow.
|
|
34
|
+
- The cardholder's tax document (CPF, etc.) → a separate document field, not part of this component.
|
|
35
|
+
- Choosing an installment plan → a separate installments component.
|
|
36
|
+
- The "save this card" toggle → a separate save-card control.
|
|
37
|
+
- A billing address form → a separate billing-address component.
|
|
38
|
+
- The "place order" button → a separate CTA component; `CardEntry` never renders a submit button.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Composition
|
|
43
|
+
|
|
44
|
+
`CardEntry` is a single self-contained leaf — it has no sub-component family. It must be placed inside a provider of the `card-capture` context, which hands it the accepted networks and decides where the card goes. At checkout that provider is the payment section root (`PaymentSectionRoot` or `PaymentSectionController`), which also feeds it the current selection and sends the card through `sendPayments`. The account's add-card root will provide the same context with its own target.
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
PaymentSectionRoot (or PaymentSectionController)
|
|
48
|
+
├── PaymentMethodList (optional; picks a method by hand)
|
|
49
|
+
├── CardEntry (this component — always a direct or nested child, never a root)
|
|
50
|
+
├── … other payment leaves (installments, save-card, billing, place-order)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`CardEntry` renders its own four fields internally (number, holder name, expiration, CVV) — none of them are separately placeable schema elements. Holder name, expiration, and CVV each render conditionally, based on the detected payment method's validator config; the number field always renders. The CVV field is `type="text"`, same as the others, but carries an inline `-webkit-text-security: disc` style that renders its digits as dots — from the keystroke that completes the code, not from the first one. "Complete" is the detected method's own `cvvMask` length: three digits for most networks, four for Amex. Where that mask is pipe-delimited — one method id covering a 3-and-4-digit family — it is the *shortest* alternative, not the longest the field accepts: a shopper holding the 3-digit card of such a family never reaches a fourth digit, so the longer length would leave their complete, valid code legible for the rest of the flow. Below it the digits render in the clear, so a shopper can read back what they just typed and catch a typo; at it, the mask engages on that same render, never on blur and never after a delay, so a paste that delivers every digit in one event opens no legible window. The mask is derived from the value on every render rather than latched once, so backspacing below the length reveals what is left, and a network switch that widens the expectation (a 3-digit code against a newly detected Amex) reveals the code again until its fourth digit. `type="password"` was tried first and reverted: a real Chrome browser offered to save the security code to its password manager (synced to the signed-in account) the instant the field carried that type — pairing the expiration field as a fake "username" — which PCI-DSS forbids for a value that must not be retained past authorization. The CSS property obscures the digits without tripping that heuristic, since it never touches `type`. It is supported in Chromium and WebKit (and, per T70's spec, Firefox 114+ — unverified on this branch); an engine without it renders the digits in the clear, exactly what shipped before this mechanism existed — not a regression. The declaration is an inline style, which outranks a class-based rule from a compound swatch at normal importance — but not one that reaches for Tailwind's `!` important modifier: swatch tokens reach `className` verbatim with no validation, so a store that authors an arbitrary-property utility ending in `!` can still unmask the field. Nothing in this package closes that hole; it is accepted rather than fixed (masking spec, OQ-1). A helper line (`cvvDescription`) renders under the field by default, telling the shopper where to find the code; an empty string suppresses it (see Components below).
|
|
54
|
+
|
|
55
|
+
**Composition rules:**
|
|
56
|
+
|
|
57
|
+
- Must be a descendant of a `card-capture` provider — `PaymentSectionRoot` or `PaymentSectionController` at checkout. Placed anywhere else, it throws at render time.
|
|
58
|
+
- Never nest another `CardEntry` inside it.
|
|
59
|
+
- Does not require `PaymentMethodList` as a sibling — network detection resolves the method on its own from the typed number.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Reveal and collapse
|
|
64
|
+
|
|
65
|
+
`CardEntry` reads the section's card-payment collapse and hides itself — never unmounts — while it holds (T191): a method is persisted that this section does not serve (a selection outside the section's configured `group`) **or** that is not a card (in neither card group, with no card-number pattern — e.g. Promissory or Customer Credit admitted through `group`), card mode is not holding the form open, **and** a `PaymentMethodList` composed under the same root can bring the form back (fix pass, finding R2). The last condition matters: collapsing only when something else on the page can un-hide the form. A composition with no picker at all — e.g. `SavedCards` + `CardEntry` — has no such escape hatch once collapsed, since typing a new card number is the one thing a hidden, inert form cannot do. In that composition the form stays visible instead, and typing a new number is itself the way back. A composed picker can always bring the form back when it renders the card option: it dispatches the method its candidate rule names (the sole in-scope method, else the sole credit-group one), and when it names none — a store with Visa and Mastercard — it puts the section in card mode instead, which reveals the form without sending anything. Without a card option, only a row bound to a card method can bring it back. Collapsed, the styled root carries `inert` and `data-fs-card-entry-collapsed=""`; the actual hide is a `hidden` attribute one level up, on a plain wrapper this component never assigns a `className` to (see Styling below) — out of the accessibility tree and out of the tab order, not merely visually hidden. It stays visible whenever nothing is persisted at all — the shopper still needs to be able to type a number for this component's own network detection to produce a selection in the first place.
|
|
66
|
+
|
|
67
|
+
When card mode is what brings the form back, a card number already in the field is sent again, once — `{ methodId, bin }` for its detected network, with its eight-digit BIN — so the selection follows the card the shopper sees without a retype. With fewer than eight digits nothing is sent until the eighth. When the form comes back because a method was dispatched instead (the card option's candidate, or a card row), the next keystroke re-sends the card, as before.
|
|
68
|
+
|
|
69
|
+
While collapsed, `CardEntry` still answers a place-order attempt: it keeps its submission registration and refuses immediately with a client-synthesized `CARD_ENTRY_COLLAPSED` code, silently — no field error, no focus move, since there is no visible field to point at. A visible form refuses the same way, once its card is valid, while the persisted selection is a method this section does not serve or one that is not a card — in card mode before the card's own selection reaches the order form, or with no picker composed — and an incomplete card still gets its field errors and focus first. It refuses the same way while the persisted selection is a card of another network than the one detected from the typed number: the card's own selection was refused or failed, or the card option or a row picked another network after the card was typed. The next edit of the number sends the card's own selection again. So a card the shopper already typed is never posted for a method the section is not on, nor under another network's method, in any composition. The collapsed fieldset is also natively `disabled`, so a store that ever wraps these fields in a real `<form>` does not get them serialized from a form the shopper cannot see (fix pass, finding S-F6) — this is separate from the authored `disabled` prop below and carries no styling hook of its own.
|
|
70
|
+
|
|
71
|
+
Collapsing clears the security code field, and only the security code. The card number, holder name, and expiration survive, so a shopper who switches method and switches back finds what they typed — no credit-card component in this package retains a security code across a visibility change.
|
|
72
|
+
|
|
73
|
+
The authored `disabled` prop is independent of this: a store that freezes the form with `disabled` sees no change, and a collapsed form may also be `disabled` — neither implies the other.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Store setup: network icon declaration
|
|
78
|
+
|
|
79
|
+
`CardEntry` renders the detected card network's icon at runtime, once the typed number matches a configured payment method's `validator.numberRegex` — the icon name is computed in component code, not read from a schema prop, so the compiler's prop-type-driven icon collection never sees it.
|
|
80
|
+
|
|
81
|
+
**Any store composing `CardEntry` (or `SavedCards` / `SavedCardOption`) must declare the five network icon names in `global.icons.include`:**
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"icons": {
|
|
86
|
+
"include": ["amex", "diners", "elo-card", "mastercard", "visa"]
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**If the store's `global.json` already has an `icons` object, extend it — do not replace it.** A store that already sets `"collection"` (or `"aliases"` / `"collections"`) must keep that key alongside the new `include`:
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"icons": {
|
|
96
|
+
"collection": "ph",
|
|
97
|
+
"include": ["amex", "diners", "elo-card", "mastercard", "visa"]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Copying the first snippet verbatim over an existing `icons` object silently drops `collection`, and every other icon in that store re-resolves through the fallback default instead — with no build error and no console error to flag it.
|
|
103
|
+
|
|
104
|
+
Omitting `include` entirely is silent too. The network icon renders as an empty 24×24 box instead of the card mark.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Components
|
|
109
|
+
|
|
110
|
+
### CardEntry
|
|
111
|
+
|
|
112
|
+
The only component. No internal sub-parts are separately schema-addressable.
|
|
113
|
+
|
|
114
|
+
| Prop | Type | Default | Notes |
|
|
115
|
+
|------|------|---------|-------|
|
|
116
|
+
| `legend` | string | `"Card details"` | Fieldset legend/title. |
|
|
117
|
+
| `numberLabel` | string | `"Card number"` | Card-number field label. |
|
|
118
|
+
| `numberPlaceholder` | string | — | Card-number field placeholder. |
|
|
119
|
+
| `holderNameLabel` | string | `"Cardholder name"` | Holder-name field label. Same noun as `holderNameRequiredMessage`, deliberately. |
|
|
120
|
+
| `holderNamePlaceholder` | string | — | Holder-name field placeholder. |
|
|
121
|
+
| `expirationLabel` | string | `"Expiration date"` | Expiration field label. |
|
|
122
|
+
| `expirationPlaceholder` | string | `"MM/YY"` | Expiration field placeholder. |
|
|
123
|
+
| `cvvLabel` | string | `"Security code"` | CVV field label. |
|
|
124
|
+
| `cvvPlaceholder` | string | — | CVV field placeholder. |
|
|
125
|
+
| `cvvDescription` | string | `"The 3-4 digit number on your card"` | Helper text under the CVV field. Empty string renders nothing. |
|
|
126
|
+
| `numberRequiredMessage` | string | `"Card number is required"` | Shown when the card-number field is left blank. |
|
|
127
|
+
| `numberInvalidMessage` | string | `"Enter a valid card number"` | Shown for a Luhn-invalid number, or one that matches no configured method — the same string covers both. |
|
|
128
|
+
| `holderNameRequiredMessage` | string | `"Cardholder name is required"` | Shown when the holder-name field is left blank and the detected method requires it. |
|
|
129
|
+
| `expirationRequiredMessage` | string | `"Expiration date is required"` | Shown when the expiration field is left blank and the detected method requires it. |
|
|
130
|
+
| `expirationInvalidMessage` | string | `"Enter a valid expiration date"` | Shown for a structurally malformed `MM/YY`. |
|
|
131
|
+
| `expirationExpiredMessage` | string | `"This card has expired"` | Shown for a structurally valid `MM/YY` that has already passed. |
|
|
132
|
+
| `cvvRequiredMessage` | string | `"Security code is required"` | Shown when the CVV field is left blank and the detected method requires it. |
|
|
133
|
+
| `cvvInvalidMessage` | string | `"Enter a valid security code"` | Shown for a CVV that fails the detected method's format. |
|
|
134
|
+
| `disabled` | boolean | `false` | Disables every field. |
|
|
135
|
+
|
|
136
|
+
**Data:** declares no fragment and no actions. Card masks, required-ness, and network regexes come from the `card-capture` context — at checkout, the payment section's own methods. It reads no cart, so it works where no purchase is underway; the `add_payment_info` event and the cart total it needs belong to the payment section root.
|
|
137
|
+
|
|
138
|
+
**Never a schema prop:** the card number and CVV. They exist only in this component's local state, for the lifetime of the mounted instance, and are never exposed as a bindable prop, a fragment field, or a value another component can read.
|
|
139
|
+
|
|
140
|
+
**Validation timing:** the draft is validated before the place-order sequence opens a transaction, not only when the order is submitted — an invalid number, holder name, expiration, or CVV refuses with an inline field message before anything is charged or opened. The submission itself re-validates the same way, so the two can never disagree.
|
|
141
|
+
|
|
142
|
+
**Selection timing:** network detection, the network mark, and the number's grouping follow every keystroke, but the detected method and the card's BIN (its first eight digits) reach the server only once the number field holds eight digits — never as a shorter prefix. Below eight digits nothing is sent, so the installments control and the persisted selection keep whatever was last recorded. The first digit typed off the new-card path does record that path, locally and with nothing sent, so a saved card that was checked stops being the selection as soon as the shopper starts a new number.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Canonical schema example
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"payment-section": {
|
|
151
|
+
"type": "PaymentSectionRoot",
|
|
152
|
+
"props": { "group": "creditCardPaymentGroup" },
|
|
153
|
+
"children": ["method-list", "card-entry"]
|
|
154
|
+
},
|
|
155
|
+
"method-list": { "type": "PaymentMethodList" },
|
|
156
|
+
"card-entry": {
|
|
157
|
+
"type": "CardEntry",
|
|
158
|
+
"props": {
|
|
159
|
+
"legend": "Card details",
|
|
160
|
+
"numberLabel": "Card number",
|
|
161
|
+
"holderNameLabel": "Cardholder name",
|
|
162
|
+
"expirationLabel": "Expiration date",
|
|
163
|
+
"cvvLabel": "Security code"
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Minimal variant (defaults only)
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"payment-section": {
|
|
174
|
+
"type": "PaymentSectionRoot",
|
|
175
|
+
"children": ["card-entry"]
|
|
176
|
+
},
|
|
177
|
+
"card-entry": { "type": "CardEntry" }
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Common mistakes
|
|
184
|
+
|
|
185
|
+
- **Inventing `CreditCardForm` or `CardForm`.** The component is `CardEntry`.
|
|
186
|
+
- **Splitting the four fields into separate schema elements** (`CardNumberField`, `CardCvvField`, …). They do not exist as separate components — `CardEntry` renders all four fields itself as one unit.
|
|
187
|
+
- **Passing `cardNumber` or `cvv` as props.** `CardEntry` reads no such prop; the card number and CVV live only in its own local state, never bound from the schema.
|
|
188
|
+
- **Placing `CardEntry` outside a `card-capture` provider** (`PaymentSectionRoot` / `PaymentSectionController` at checkout). It requires that context and throws otherwise.
|
|
189
|
+
- **Adding a submit/place-order button inside `CardEntry`.** Placing the order is a separate CTA component; `CardEntry` never renders one.
|
|
190
|
+
- **Reusing `CardEntry` for a saved card's CVV-only re-entry.** That is a distinct component; `CardEntry` always shows all four new-card fields.
|
|
191
|
+
- **Expecting a `network` or `cardType` prop.** The network is detected automatically from the typed number against the payment section's configured methods — there is no prop to preset it.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Styling
|
|
196
|
+
|
|
197
|
+
Style `CardEntry`'s root as a vertical stack of fields, matching a typical card form. The recognized-network mark belongs **inside** the number input's own box rather than beside it: make the number field's wrapper the positioning context, take the mark out of flow at the input's trailing edge, and pad the input on that side so the digits never run underneath it. The wrapper is a plain ancestor of both the `<input>` and the mark, so this is a pure CSS arrangement — the field particle exposes no adornment slot.
|
|
198
|
+
|
|
199
|
+
| Component | What you style | Plausible classes |
|
|
200
|
+
|---|---|---|
|
|
201
|
+
| `CardEntry` root | Stacks the four fields | `flex flex-col gap-[16px] w-full` |
|
|
202
|
+
| Number field wrapper (`data-fs-card-entry-number-control`) | The positioning context the mark is placed against; also holds the `<input>` | `relative` |
|
|
203
|
+
| Network mark slot (`data-fs-card-entry-networks`) | Positions the single mark inside the input's box; absent entirely until a network is recognized | `absolute right-[12px] top-1/2 -translate-y-1/2 flex items-center` |
|
|
204
|
+
| Network mark (`data-fs-card-entry-network`) | The recognized network itself — **at most one**, never a row. Carries `data-fs-card-entry-network-id` with the detected method's id | `flex items-center justify-center` |
|
|
205
|
+
| Network icon (`data-fs-card-entry-network-icon`) | The icon, when the recognized network's name maps to one | `w-[20px] h-[20px]` |
|
|
206
|
+
| Network fallback text (`data-fs-card-entry-network-fallback`) | The recognized network's name as text, when it maps to no icon; mutually exclusive with the icon | `text-[13px] font-medium text-[#333333]` |
|
|
207
|
+
| Field labels (`data-fs-field-label`) | Small, muted label above each input | `text-[13px] font-medium text-[#333333] mb-[4px] block` |
|
|
208
|
+
| Field inputs (`data-fs-field-control`) | The bordered text input | `border border-[#CCCCCC] rounded-[6px] px-[12px] py-[10px] text-[15px] w-full` |
|
|
209
|
+
| Field errors (`data-fs-field-error`) | Inline validation message, small and red | `text-[12px] text-[#C8102E] mt-[4px] block` |
|
|
210
|
+
| CVV helper text (`data-fs-card-entry-cvv-description`) | Small muted line under the CVV field | `text-[12px] text-[#666666] mt-[4px] block` |
|
|
211
|
+
| Placing (`data-fs-card-entry-placing`) | Present while an order-placement sequence is in flight (T28); the whole root is also `inert` and `aria-disabled` at that point, so style this hook rather than relying on pointer events alone | `opacity-60 cursor-not-allowed` |
|
|
212
|
+
| Collapsed (`data-fs-card-entry-collapsed`) | Present on the styled root while the card payment is collapsed — the persisted selection is a method this section does not serve or one that is not a card, and a picker is composed to un-hide it (see Reveal and collapse); the root is also `inert`. **No transition or animation can run on it**: the actual hide is a `hidden` attribute one level up, on an unstyled wrapper this node cannot reach with any class, and an ancestor's `display:none` removes the whole subtree from rendering the instant it applies, before any transition on a descendant could ever paint. Use this hook only for one-shot conditional styling of the (already-unrendered) subtree, never for an entrance/exit animation | `opacity-60` |
|
|
213
|
+
|
|
214
|
+
Styled minimal example:
|
|
215
|
+
|
|
216
|
+
```json
|
|
217
|
+
{
|
|
218
|
+
"card-entry": {
|
|
219
|
+
"type": "CardEntry",
|
|
220
|
+
"style": ["flex", "flex-col", "gap-[16px]", "w-full"]
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`CardEntry` renders no separately-styleable schema elements below the root — its internal fields are not independently placeable, so per-field styling is done via the `data-fs-*` hooks above rather than a `style` array on a sub-element.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Accessibility Contract
|
|
230
|
+
|
|
231
|
+
**What the platform promises:** the four fields sit inside a native `<fieldset>`/`<legend>` pair
|
|
232
|
+
(`legend`, default `"Card details"`) — the pattern every other grouped payment control now follows
|
|
233
|
+
(T38, Decision 1). Each field carries its own `Field.Label`, `aria-invalid`, and an error message
|
|
234
|
+
associated through `aria-describedby` once it fails validation; the CVV field's helper text
|
|
235
|
+
(`cvvDescription`) is linked the same way. There is no composite keyboard widget here — every rendered
|
|
236
|
+
field keeps its own Tab stop, reachable in DOM order. A default `:focus-visible` outline is painted from
|
|
237
|
+
the platform's own stylesheet on every field.
|
|
238
|
+
|
|
239
|
+
**What the platform does not promise:** colour contrast. `CardEntry` (and every other payment
|
|
240
|
+
component) exposes `data-fs-*` hooks so a merchant can author contrast-compliant swatches, but no
|
|
241
|
+
component in this package measures or guarantees any contrast ratio — that is the merchant's theme, not
|
|
242
|
+
this platform.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Related
|
|
247
|
+
|
|
248
|
+
- [[document-field]] — the particle whose progressive-mask engine `CardEntry`'s own masking generalizes; a sibling leaf for the cardholder's tax document, not part of this component.
|
|
249
|
+
- [[client-profile-form]] — the batched-form pattern (`validateDraft` + first-invalid focus) `CardEntry`'s validation reuses.
|
|
250
|
+
- [[pricing]] — shows *advertised* installment plans on a PDP; unrelated to entering a card at checkout.
|