@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
|
@@ -19,7 +19,7 @@ IdentityEditController // owns the summary/form swap;
|
|
|
19
19
|
│ ├─ IdentitySummary.SignOutButton // dispatches session.logout
|
|
20
20
|
│ └─ IdentitySummary.Error // inline sign-out error
|
|
21
21
|
└─ IdentityEditForm
|
|
22
|
-
├─
|
|
22
|
+
├─ FormRoot → ClientProfileFieldset // the edit surface (batched write on submit)
|
|
23
23
|
└─ IdentityEditCancelButton // Cancel → back to the summary
|
|
24
24
|
```
|
|
25
25
|
|
|
@@ -43,7 +43,7 @@ anonymous state, so anything that must survive that state — the section headin
|
|
|
43
43
|
- **Summary/form swap** — `IdentityEditController` with its guards and triggers. The edit and cancel
|
|
44
44
|
buttons are `IdentityEditButton` and `IdentityEditCancelButton`; a leading icon composes as their children.
|
|
45
45
|
The controller moves only on those buttons — saving does not collapse the section — so neither this
|
|
46
|
-
compound nor
|
|
46
|
+
compound nor the edit form learns anything about a section around it (ADR-0006).
|
|
47
47
|
|
|
48
48
|
## The summary is composed, not fixed
|
|
49
49
|
|
|
@@ -94,13 +94,13 @@ IdentitySummaryRoot
|
|
|
94
94
|
and `Error` all require it — the compiler enforces the ancestor. The `IdentityEditController` and its
|
|
95
95
|
guards/triggers require only each other.
|
|
96
96
|
- Fragment ownership: `Root` reads only `clientProfile` (email, firstName, lastName, phone, document).
|
|
97
|
-
The edit surface is a separately-composed `
|
|
98
|
-
`identity.clientProfileUpdate` / `clientPreferencesUpdate` actions.
|
|
97
|
+
The edit surface is a separately-composed `FormRoot` over `ClientProfileFieldset`, which owns its own
|
|
98
|
+
fragment and the `identity.clientProfileUpdate` / `clientPreferencesUpdate` actions.
|
|
99
99
|
- Actions: `Root` declares only `session.logout`.
|
|
100
100
|
|
|
101
101
|
## Do not
|
|
102
102
|
|
|
103
|
-
- Do not place two `
|
|
103
|
+
- Do not place two `ClientProfileFieldset`s for the same fields; the edit guard hosts the single one.
|
|
104
104
|
- Do not use this compound for an anonymous shopper; the Root renders nothing and has no anonymous fallback.
|
|
105
105
|
- Do not expect the section to close after a save — nothing here does that. Where a finished section
|
|
106
106
|
leads is left to the flow-of-steps RFC (#611).
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# Installments
|
|
2
|
+
|
|
3
|
+
**Category:** commerce
|
|
4
|
+
**Purpose:** Installment selection for checkout — lets a shopper pick how many installments to pay a card in, showing each option's count, per-installment value, and interest (or an interest-free label). Two registered components share one engine and one data shape: `Installments` renders every tier as a stacked radio list; `InstallmentsSelect` collapses the same tiers into a single closed dropdown. Both serve a freshly typed new card (matched by BIN) and a selected saved card (matched by the persisted BIN, falling back to payment method), rendering exactly the tiers the matched option provides (the API guarantees a 1x tier is present in every option, so one is always shown and selectable). Pick whichever control fits the layout — never both on the same instance, though composing one of each on a page is harmless (they stay in sync through the shared cache).
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Visual cues (how to identify in an image)
|
|
9
|
+
|
|
10
|
+
Use these signals to detect that an installment picker is present, and which of the two components it is. Position on the page is irrelevant for detection — it can appear anywhere, in any orientation. The image is the source of truth for layout.
|
|
11
|
+
|
|
12
|
+
**`Installments` (radio list)** — any combination of:
|
|
13
|
+
|
|
14
|
+
- A **vertical list of selectable rows**, each a radio button or radio-styled row.
|
|
15
|
+
- Each row reading something like **"1x de R$ 100,00 sem juros"** or **"3x de R$ 35,00 2,5% de juros"** — a count, a currency-formatted value, and an interest note.
|
|
16
|
+
- One row visibly marked as the current selection (a filled radio dot, a highlighted background, or a checkmark).
|
|
17
|
+
|
|
18
|
+
**`InstallmentsSelect` (collapsed dropdown)** — any combination of:
|
|
19
|
+
|
|
20
|
+
- A **single closed control** (a bordered box or button) showing one line of text in the same "count de value interest" shape, with a dropdown chevron/arrow glyph.
|
|
21
|
+
- No option rows visible until the control is opened — the closed state never lists every tier, unlike the radio surface.
|
|
22
|
+
- An optional label above the control (e.g. "Installments" / "Parcelamento") — the accessible name, always present even when visually suppressed.
|
|
23
|
+
|
|
24
|
+
Both always sit **inside a checkout payment section** (near a card-number field or a saved-card list) — never on a product page. A similar-looking installment list on a PDP is `PricingInstallments` / `PricingInstallmentPlans`, not either of these components (see [When NOT to use](#when-not-to-use)).
|
|
25
|
+
|
|
26
|
+
If the image shows a selectable list of installment counts inside a checkout/payment flow, use `Installments` for a stacked list of rows, or `InstallmentsSelect` for a single closed control that expands on interaction. If the list is read-only (no radio, no dropdown, no visible interactivity) and sits next to a recap of the chosen card, it is neither component — no component renders a read-only payment recap in checkout.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## When to use
|
|
31
|
+
|
|
32
|
+
- Inside a checkout's payment section, as a sibling of the card-entry fields and/or the saved-cards list, so the shopper can choose an installment count for the card they are paying with.
|
|
33
|
+
- Choose `Installments` when the layout has room for a stacked list of rows (every tier visible at once, no extra click).
|
|
34
|
+
- Choose `InstallmentsSelect` when the layout is narrow — a slim column, a drawer, a mobile checkout — where a dozen stacked rows would not fit; it collapses the same tiers into one control that expands on interaction.
|
|
35
|
+
|
|
36
|
+
## When NOT to use
|
|
37
|
+
|
|
38
|
+
- A PDP's advertised installment plans (display-only, no selection) → use `Pricing`'s `PricingInstallments` / `PricingInstallmentPlans` instead. Different data source (`productBySlug` offer, not `Query.payment`), no write, not reusable here.
|
|
39
|
+
- A read-only recap of the installment already chosen (e.g. at Order Review) — no component renders one in checkout. Both installment components always show every available option and let the shopper change the selection.
|
|
40
|
+
- Outside a payment section → both components require a `PaymentSectionRoot` or `PaymentSectionController` ancestor (they read the shopper's in-progress card draft from that context). Placing either anywhere else is a build/runtime error, not a silent no-op.
|
|
41
|
+
- A saved-card picker itself (network icon, last four, label) → use `SavedCards`, not either installment component.
|
|
42
|
+
- A page needing both a stacked list AND a collapsed control at once → compose two separate instances (one of each). There is no single component with a prop that switches between the two looks (see [Common mistakes](#common-mistakes)).
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Composition
|
|
47
|
+
|
|
48
|
+
Both components are leaves — no root, no required children, no sub-components. A page composes exactly one of them per payment section (composing one of each is harmless but redundant — they read the same data and stay in sync):
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
PaymentSectionRoot (or PaymentSectionController)
|
|
52
|
+
├── CardEntry (sibling — new-card fields)
|
|
53
|
+
├── SavedCards (sibling — saved-card picker)
|
|
54
|
+
└── Installments (sibling — stacked radio list)
|
|
55
|
+
…or…
|
|
56
|
+
└── InstallmentsSelect (sibling — collapsed dropdown)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Either component must be a descendant of `PaymentSectionRoot` or `PaymentSectionController` — each reads the shopper's in-progress card draft (`draft.bin`) and the currently selected method from that context. Place it as a **sibling** of `CardEntry` and `SavedCards`, never nested inside either: one instance serves both the new-card and saved-card paths, matched automatically from the same context data.
|
|
60
|
+
|
|
61
|
+
Both self-gate to an empty state when nothing is selected yet, and to a loading state before their data resolves — safe to include speculatively inside any payment section, but for image-to-schema work only include one when the image actually shows a selectable installment control, and pick the variant (list vs. dropdown) the image actually shows.
|
|
62
|
+
|
|
63
|
+
Both render nothing while the card payment is collapsed — the persisted selection is a method that is not a card, or one the section does not serve, and a composed `PaymentMethodList` can bring the card form back (see [[payment]]) — and come back with it. A method's own installment options apply only when it is a card: over a persisted non-card method, card mode included, both read `empty` until a card is recorded, never that method's own tiers.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Components
|
|
68
|
+
|
|
69
|
+
### Installments
|
|
70
|
+
|
|
71
|
+
| Prop | Type | Default | Notes |
|
|
72
|
+
|------|------|---------|-------|
|
|
73
|
+
| `installmentTemplate` | string | `"{count}x of {value}"` | Placeholders `{count}` / `{value}`, matching `PricingInstallment`'s convention. |
|
|
74
|
+
| `interestFreeLabel` | string | `"interest-free"` | Appended when a row's interest rate is `0`. |
|
|
75
|
+
| `interestTemplate` | string | `"{rate}% interest"` | Appended instead of `interestFreeLabel` when a row's interest rate is above `0`. `{rate}` is the order form's `interestRate`, which is in hundredths of a percent, divided by 100 and locale-formatted: `100` renders `1`. |
|
|
76
|
+
| `legend` | string | `"Installments"` | The radio group's accessible name, rendered as a visible fieldset legend above the rows (T38). An authored empty or whitespace-only string falls back to this default rather than rendering a nameless group. |
|
|
77
|
+
|
|
78
|
+
**Data:** declares its own `fragment InstallmentsData on Query` (selects `Query.payment.installmentOptions` and `Query.payment.selectedInstallment` — the compiler merges it into the page query alongside `PaymentSectionRoot`'s own, smaller `payment` selection; no explicit `dataSources` entry needed). `fetchStrategy: 'reactive'` — the option list updates live from the cache, no refetch.
|
|
79
|
+
|
|
80
|
+
**Matching:** resolves one installment option to show by preferring a match on `draft.bin ?? selectedBin` — the client's detected BIN, or the persisted BIN when the client asserts none (e.g. after a page reload with a card already selected). Falls back to a match on the currently selected payment method when neither BIN match exists. This is the same precedence the backend itself uses. The `binConfirmation` signal from `usePaymentMethodSelection` gates dispatch while the persisted BIN and the client draft disagree or a method selection is in flight, so the rows a shopper sees always agree with what a selection would actually validate against.
|
|
81
|
+
|
|
82
|
+
**The 1x option:** the component renders exactly the tiers the matched option's own data carries. The API guarantees a `count: 1` tier is present in every projected `installmentOptions` entry — passed through untouched (including its interest rate) when upstream supplies one, synthesized from the option's own `value` with no interest when it does not — so a 1x row is always shown and selectable.
|
|
83
|
+
|
|
84
|
+
**Selecting a row** dispatches the installment write action; on success the order total (interest-inclusive) updates elsewhere on the page with no further action from this component. On rejection, the first error message renders inline.
|
|
85
|
+
|
|
86
|
+
### InstallmentsSelect
|
|
87
|
+
|
|
88
|
+
The collapsed sibling: same tiers, same matching, same write path, same `installmentTemplate`/`interestFreeLabel`/`interestTemplate` defaults as `Installments`, and the same formatting locale — the storefront context's, since neither component takes a `locale` prop — only the control itself differs (a Base UI Select instead of a radio group), plus two extra copy props for the closed control's own text.
|
|
89
|
+
|
|
90
|
+
| Prop | Type | Default | Notes |
|
|
91
|
+
|------|------|---------|-------|
|
|
92
|
+
| `installmentTemplate` | string | `"{count}x of {value}"` | Same as `Installments`. |
|
|
93
|
+
| `interestFreeLabel` | string | `"interest-free"` | Same as `Installments`. |
|
|
94
|
+
| `interestTemplate` | string | `"{rate}% interest"` | Same as `Installments`: `{rate}` is the order form's hundredths-of-a-percent `interestRate` divided by 100. |
|
|
95
|
+
| `label` | string | `"Installments"` | Accessible name for the control, rendered as the particle's own label element. Always renders — there is no mode that hides it; a store that already has its own heading suppresses it visually via its own `data-fs-installments-label` hook. An authored empty or whitespace-only string falls back to this default (T38) rather than rendering a nameless combobox. |
|
|
96
|
+
| `placeholder` | string | `"Select an installment plan"` | Shown on the closed control when the bound count matches no tier in the currently matched option (a stale selection — see below). As of T31, every copy prop across both `Installments` and `InstallmentsSelect` defaults to en-US, while values and interest rates format in the storefront context's locale, which no prop overrides. A pt-BR store now authors every copy prop explicitly, not just these two. |
|
|
97
|
+
|
|
98
|
+
**Data:** declares its own `fragment InstallmentsSelectData on Query`, with a selection set identical to `InstallmentsData` under a distinct name (two registered components on one page must not share a fragment name). Same `fetchStrategy: 'reactive'`.
|
|
99
|
+
|
|
100
|
+
**Matching, the 1x option, and dispatch** are identical to `Installments` — both components read and write through the same shared engine, so they cannot drift.
|
|
101
|
+
|
|
102
|
+
**The closed control** always shows the persisted selection's own label — resolved from the component's own tier data, correct even before the popup is ever opened — or the `placeholder` text when the persisted count matches no tier in the currently matched option (e.g. the shopper switched to a card whose options offer fewer tiers). It never invents a label.
|
|
103
|
+
|
|
104
|
+
**No plan count.** The closed control never shows how many tiers exist — only the current selection or the placeholder, mirroring `DeliveryOptionsSummary`'s "echo the selection, not the inventory" precedent.
|
|
105
|
+
|
|
106
|
+
**The popup is portalled to `<body>`.** This avoids clipping when the component sits inside a dialog or drawer, but it also means a store's `style` array cannot reach the option list's internals (padding, hover, the selected-row treatment) the way it can reach a radio row — see [Styling](#styling).
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Canonical schema example
|
|
111
|
+
|
|
112
|
+
Radio list:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"payment-section": {
|
|
117
|
+
"type": "PaymentSectionRoot",
|
|
118
|
+
"children": ["card-entry", "saved-cards", "installments"]
|
|
119
|
+
},
|
|
120
|
+
"installments": {
|
|
121
|
+
"type": "Installments",
|
|
122
|
+
"style": ["flex", "flex-col", "gap-2"]
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Collapsed dropdown — same payment section, `InstallmentsSelect` in place of `Installments`:
|
|
128
|
+
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"payment-section": {
|
|
132
|
+
"type": "PaymentSectionRoot",
|
|
133
|
+
"children": ["card-entry", "saved-cards", "installments-select"]
|
|
134
|
+
},
|
|
135
|
+
"installments-select": {
|
|
136
|
+
"type": "InstallmentsSelect",
|
|
137
|
+
"props": { "label": "Parcelamento", "placeholder": "Selecione o parcelamento" }
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Common mistakes
|
|
145
|
+
|
|
146
|
+
- **Inventing `InstallmentSelector` or `PaymentInstallments`.** The two shipped components are `Installments` (radio list) and `InstallmentsSelect` (collapsed dropdown).
|
|
147
|
+
- **Adding a `control` / `variant` prop to switch `Installments` between a list and a dropdown.** There is no such prop, and none should be authored — the look is chosen by which component the page composes, never by a prop that bakes a visual decision into one component. Compose `InstallmentsSelect` instead.
|
|
148
|
+
- **Nesting either component inside `CardEntry` or `SavedCards`.** Each must be a sibling of both under the same `PaymentSectionRoot` — nesting it inside either would tie the selector to only one entry path, which is exactly the gap these components close.
|
|
149
|
+
- **Using either component on a PDP.** A PDP's advertised plans are `Pricing`'s `PricingInstallments` / `PricingInstallmentPlans` — different data source, display-only, not reusable here.
|
|
150
|
+
- **Using either component for a read-only recap.** A recap of the already-chosen installment (e.g. Order Review) is not `Installments` or `InstallmentsSelect`; both are selectors.
|
|
151
|
+
- **Placing either component outside a payment section.** Both require a `PaymentSectionRoot`/`PaymentSectionController` ancestor and throw without one.
|
|
152
|
+
- **Expecting a rendered order total.** Neither component ever shows a total — selecting an option updates the cart total elsewhere on the page through the normalized cache.
|
|
153
|
+
- **Expecting a full card number or CVV field.** Both fragments structurally select neither — only installment counts, values, and interest rates.
|
|
154
|
+
- **Expecting `InstallmentsSelect`'s option list to be styleable from the page's `style` array.** Its popup is portalled to `<body>` — see [Styling](#styling).
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Styling
|
|
159
|
+
|
|
160
|
+
### Installments
|
|
161
|
+
|
|
162
|
+
Single DOM root wrapping a list of selectable rows.
|
|
163
|
+
|
|
164
|
+
| Component | What you style | Plausible classes |
|
|
165
|
+
|---|---|---|
|
|
166
|
+
| `Installments` (root `div`) | Stacks the option rows | `flex flex-col gap-2` |
|
|
167
|
+
| Fieldset (`data-fs-installments-fieldset`, native `<fieldset>`) | Wraps the legend and the radio rows (T38) — gives the group its visible, accessible name | usually left unstyled; a merchant border/padding choice |
|
|
168
|
+
| Legend (`data-fs-fieldset-legend`, native `<legend>`) | The group's visible name, from `legend` | `text-[14px] font-medium text-[#1A1A1A]` |
|
|
169
|
+
| Empty state (`data-fs-installments-state="empty"`) | Neutral placeholder sizing | `min-h-12 border border-dashed border-[#CCCCCC]` |
|
|
170
|
+
| Loading state (`data-fs-installments-state="loading"`) | Skeleton affordance | `min-h-12 animate-pulse bg-[#F5F5F5]` |
|
|
171
|
+
| Pending state (`data-fs-installments-state="pending"`) | Rows visible but disabled while the persisted BIN settles | `opacity-50 pointer-events-none` |
|
|
172
|
+
| Inline error (`data-fs-installments-error`) | Warning-toned alert text | `text-[12px] font-medium text-[#B00020]` |
|
|
173
|
+
| Option row label (`data-fs-installments-option-label`) | Row text | `text-[14px] text-[#1A1A1A]` |
|
|
174
|
+
| Placing (`data-fs-installments-placing`) | Present while an order-placement sequence is in flight (T28) — distinct from the `pending` state above, which is this component's own BIN-confirmation wait; the root is also `inert` and `aria-disabled` at that point | `opacity-60 cursor-not-allowed` |
|
|
175
|
+
|
|
176
|
+
Styled example:
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
{
|
|
180
|
+
"installments": {
|
|
181
|
+
"type": "Installments",
|
|
182
|
+
"style": ["flex", "flex-col", "gap-2"]
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
There is no per-row styling hook exposed as a separate schema element — `Installments` renders its own rows directly (no Root/particle split); style the checked/unchecked row state via the `Radio` particle's own `data-checked` attribute, and the row text via `data-fs-installments-option-label`, per the `style-components` skill.
|
|
188
|
+
|
|
189
|
+
### InstallmentsSelect
|
|
190
|
+
|
|
191
|
+
Root `div` plus a Base UI Select. Only the parts that stay inside the component root are reachable from a store's own `style` array by descendant variants (the same mechanism the radio surface's own rows use); the popup subtree — teleported to `<body>` — is not.
|
|
192
|
+
|
|
193
|
+
| Component | Reachable from `style`? | What you style | Plausible classes |
|
|
194
|
+
|---|---|---|---|
|
|
195
|
+
| `InstallmentsSelect` (root `div`, `data-fs-installments`) | Yes | Positions the label + trigger | `flex flex-col gap-1` |
|
|
196
|
+
| `data-fs-installments-control="select"` | Yes | Discriminates this surface from the radio one when a store styles both from one shared selector | — |
|
|
197
|
+
| Empty / loading state (`data-fs-installments-state`) | Yes | Same neutral/skeleton treatment as `Installments` | `min-h-12 border border-dashed border-[#CCCCCC]` / `min-h-12 animate-pulse bg-[#F5F5F5]` |
|
|
198
|
+
| Pending state (`data-fs-installments-state="pending"`) | Yes | Trigger visible but disabled while the persisted BIN settles | `opacity-50 pointer-events-none` |
|
|
199
|
+
| Inline error (`data-fs-installments-error`) | Yes | Warning-toned alert text | `text-[12px] font-medium text-[#B00020]` |
|
|
200
|
+
| Label (`data-fs-installments-label`) | Yes | The accessible-name text above the trigger | `text-[12px] text-[#666666]`, or `sr-only` to hide it visually |
|
|
201
|
+
| Trigger (`data-fs-installments-trigger`) | Yes | The closed control's own box | `flex items-center justify-between border border-[#CCCCCC] rounded px-3 py-2` |
|
|
202
|
+
| Value (`data-fs-installments-value`) | Yes | The selected label / placeholder text | `text-[14px] text-[#1A1A1A]` |
|
|
203
|
+
| Icon (`data-fs-installments-icon`) | Yes | The `▾` glyph | `text-[#666666]` |
|
|
204
|
+
| Option (`data-fs-installments-option`) | **No — portalled** | One row inside the open popup | not authorable from this page's `style` array today |
|
|
205
|
+
| Option label (`data-fs-installments-option-label`) | **No — portalled** | An option's text | not authorable from this page's `style` array today |
|
|
206
|
+
| Placing (`data-fs-installments-placing`) | Yes | Present while an order-placement sequence is in flight (T28) — the same hook and presence attribute name `Installments` uses (both surfaces read the shared engine's own `placing`); the root is also `inert` and `aria-disabled` at that point | `opacity-60 cursor-not-allowed` |
|
|
207
|
+
|
|
208
|
+
Styled example (only the reachable parts):
|
|
209
|
+
|
|
210
|
+
```json
|
|
211
|
+
{
|
|
212
|
+
"installments-select": {
|
|
213
|
+
"type": "InstallmentsSelect",
|
|
214
|
+
"style": ["flex", "flex-col", "gap-1"]
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The popup's own visual defaults (spacing, hover, the selected-row treatment) come from the particle's own baseline styling, not from this page's `style` array — there is currently no platform mechanism for a store to restyle a portalled subtree's internals.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Accessibility Contract
|
|
224
|
+
|
|
225
|
+
**What the platform promises:** `Installments`' radio group and `InstallmentsSelect`'s combobox each
|
|
226
|
+
carry a visible, programmatic accessible name (`legend`/`label`, both defaulting to `"Installments"`) —
|
|
227
|
+
a screen-reader user hears what the rows are a choice *between*. An authored empty or whitespace-only
|
|
228
|
+
value on either falls back to the default rather than shipping a nameless control. Each tier keeps its
|
|
229
|
+
own accessible name/text from `installmentLabel`. `Installments`' group is a native `<fieldset>`/
|
|
230
|
+
`<legend>` pair, visible to sighted and low-vision shoppers, not only `aria-label`. Keyboard:
|
|
231
|
+
`Installments`' group is one Tab stop — Tab lands on the checked row, and the arrow keys move the
|
|
232
|
+
checked row and change the selection. A default `:focus-visible` outline is painted from the
|
|
233
|
+
platform's own stylesheet on every element these two components render directly — `Installments`'
|
|
234
|
+
radio rows and `InstallmentsSelect`'s own trigger. `InstallmentsSelect`'s *portalled* option list
|
|
235
|
+
carries the same selector defensively (`[data-fs-installments-option]:focus-visible`), but whether a
|
|
236
|
+
browser ever gives a portalled option real focus, rather than driving the list with
|
|
237
|
+
`aria-activedescendant` (which this selector would never match), is unverified — no jsdom test can
|
|
238
|
+
answer it (open question, resolvable with one Storybook keyboard pass).
|
|
239
|
+
|
|
240
|
+
**What the platform does not promise:** colour contrast. Both components expose `data-fs-*` hooks so a
|
|
241
|
+
merchant can author contrast-compliant swatches, but neither measures or guarantees any contrast
|
|
242
|
+
ratio — that is the merchant's theme, not this platform.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Related
|
|
247
|
+
|
|
248
|
+
- [[pricing]] — `PricingInstallments` / `PricingInstallmentPlans` render a PDP's *advertised* installment plans, never a *selection*; distinct data source, distinct concern, explicitly not reusable for either installment component.
|
|
@@ -173,6 +173,7 @@ Add each field Control id to `profile-root`'s `children` array for the fields th
|
|
|
173
173
|
- **Rendering a frame part or field Control outside `MyAccountProfileRoot`.** Every part except `Skeleton` `requiresContext: 'my-account-profile'` and throws otherwise.
|
|
174
174
|
- **Giving a required Control's native input a `required` HTML attribute.** Requiredness is expressed only through the registered `validate` function (Decision 2 of SFS-3288/3289/3290); a native `required` attribute makes the browser block the form's `submit` event before the Root's own validation and focus logic ever runs.
|
|
175
175
|
- **Hiding the `TaxIdControl` in the edit state when a CPF is already saved.** It must render a disabled, locked input instead — a field disappearing between view and edit reads as a bug (product-confirmed, 2026-09-03).
|
|
176
|
+
- **Composing only the `mobilePhone` Control on a store whose shoppers check out.** The checkout reads its phone from `homePhone`, and it treats a profile without one as incomplete: a new cart imports only the email and drops the shopper's saved addresses. Compose a `PhoneControl` with `field: "homePhone"` whenever the store sells, alongside the mobile one if the page shows both.
|
|
176
177
|
- **Adding a `showPhone`/`visible` boolean prop to a Control.** Presence is composition; a boolean visibility prop contradicts compose-or-omit and is out of scope for every Control in this group.
|
|
177
178
|
|
|
178
179
|
---
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# OrderPlacedPaymentSummary
|
|
2
|
+
|
|
3
|
+
**Category:** commerce
|
|
4
|
+
**Purpose:** Read-only recap, on a post-purchase confirmation surface, of the card the shopper paid with — sourced from the settled order (`Query.order`) rather than the live order form. The only payment recap that keeps resolving after the order is placed.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Visual cues (how to identify in an image)
|
|
9
|
+
|
|
10
|
+
Use these signals to detect that an OrderPlacedPaymentSummary block is present. Position on the page is irrelevant for detection — it can appear anywhere, in any orientation. The image is the source of truth for layout.
|
|
11
|
+
|
|
12
|
+
An OrderPlacedPaymentSummary block, on an order-confirmation / "thank you" screen, contains any ONE of:
|
|
13
|
+
|
|
14
|
+
- A short **line naming a card network** (e.g. "Visa", "Mastercard") optionally followed by **masked last four digits** (e.g. "•••• 1111"), optionally with a **custom card label/nickname** line below it (e.g. "Meu cartão pessoal").
|
|
15
|
+
- A **non-card method's own name** (e.g. "Boleto Bancário"), with no masked digits at all.
|
|
16
|
+
- An **inline alert/notice line** (visually a warning or error banner) saying the payment details could not be loaded, or that the order was split across sellers.
|
|
17
|
+
- An **informational line** (visually plain, not a warning) saying no payment record is available.
|
|
18
|
+
|
|
19
|
+
No copy is baked into this block beyond those lines — any surrounding "Payment" heading is host-page chrome, not part of this component.
|
|
20
|
+
|
|
21
|
+
If the image shows this recap on an **in-checkout** surface (Order Review, still-live cart) instead, this is not the component — see [When NOT to use](#when-not-to-use).
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## When to use
|
|
26
|
+
|
|
27
|
+
- A post-purchase order-confirmation ("thank you") screen, to show the shopper the card/method their order was charged with.
|
|
28
|
+
- Anywhere else that reads the settled order's payment recap by order group, after the order form itself may no longer exist.
|
|
29
|
+
|
|
30
|
+
## When NOT to use
|
|
31
|
+
|
|
32
|
+
- **The in-checkout Order Review step**, or any surface where the cart is still live → no component renders a payment recap in checkout. `Query.order` (this component's source) requires a settled `orderGroup`; it does not exist yet before the order is placed.
|
|
33
|
+
- Letting the shopper pick or enter a card, or choose an installment → this component has no write affordance at all; see `PaymentSection` (card entry, installments, saved-card picker).
|
|
34
|
+
- A settled order's shipping/billing address, items, or totals → out of scope; this component recaps only the payment method.
|
|
35
|
+
- A **per-installment amount or interest rate** on the placed order → not deliverable from this data source; VTEX's settled-order payment record carries only the installment **count** and the settled **total**, never a per-installment amount or a rate (unlike the live order form). This component shows the count and total; it does not, and structurally cannot, show either of the other two.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Composition
|
|
40
|
+
|
|
41
|
+
`OrderPlacedPaymentSummary` is a single leaf component — no root, no required children, no sub-components:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
OrderPlacedPaymentSummary
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
It requires no ancestor context and provides none — composable standalone on a confirmation page. For image-to-schema work, only include it when the image actually shows a payment recap on a confirmation-style screen.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Components
|
|
52
|
+
|
|
53
|
+
### OrderPlacedPaymentSummary
|
|
54
|
+
|
|
55
|
+
| Prop | Type | Default | Notes |
|
|
56
|
+
|------|------|---------|-------|
|
|
57
|
+
| `lastFourTemplate` | string | `"•••• {lastFour}"` | Rendered whenever the projection carries a last four digit — for both a saved and a freshly-entered card. |
|
|
58
|
+
| `noPaymentRecordLabel` | string | `"Payment method not available"` | Shown when the order resolves but carries no projectable payment record (all of network/last-four/label are null). Informational — not an alert. |
|
|
59
|
+
| `unresolvedOrderLabel` | string | `"Could not load the payment details"` | Shown, as an inline `role="alert"`, when the order group does not resolve to a settled order. |
|
|
60
|
+
| `multiSellerNotice` | string | `"This order was split across {count} sellers; the payment summary does not cover split orders"` | Shown, as an inline `role="alert"`, for a split (multi-seller) order; `{count}` placeholder, omitted (token dropped, no literal "undefined"/"NaN") when the seller count is unknown. |
|
|
61
|
+
| `installmentTemplate` | string | `"{count}x of {total}"` | Rendered whenever the projection carries an `installment` (count always present when it does); `{count}`/`{total}` placeholders — when the source carries the count but not enough to build a settled total from, everything between `{count}`'s own glued suffix (if any) and `{total}` is dropped together with the token itself (e.g. " of " in the shipped default), so the line reads "6x" rather than ending mid-sentence. A run glued directly to `{count}`, with no separating whitespace, is that count's own suffix rather than a connector, and is kept: `"{count}x {total}"` and `"{count}x{total}"` both degrade to "6x", never to a bare "6" or an empty line. This applies to the WHOLE run between the glued suffix and `{total}`, not just one word — a self-contained unit noun with no glued suffix (e.g. `"{count} parcelas de {total}"`) degrades to a bare "6", the same as a dangling preposition would, because no algorithm can tell the two apart (T31). Never a per-installment amount or interest rate — the source carries neither (see [When NOT to use](#when-not-to-use)). |
|
|
62
|
+
|
|
63
|
+
**Data:** declares its own `fragment OrderPlacedPaymentSummaryData on Query` selecting `Query.order(orderGroup: $orderGroup)` — `network`, `lastFourDigits`, `label`, `installment { count total { amount currencyCode } }`. `$orderGroup` is resolved by the compiler from a route segment or page search param, never passed as a prop. `fetchStrategy: 'reactive'` — required so the compiler hoists the `PageReactiveDataRoot` this component reads the multi-seller signal through.
|
|
64
|
+
|
|
65
|
+
**Resolution:** exactly one of five states renders, in this fixed precedence:
|
|
66
|
+
|
|
67
|
+
1. **Multi-seller** — the reactive root's query error carries `extensions.code === 'UNSUPPORTED_MULTI_SELLER_ORDER'`. Wins over everything else, including a resolved recap, because a split order's one-seller card would otherwise be shown as if it were the whole payment.
|
|
68
|
+
2. **In flight** — `loading` is true and no data has resolved yet. Renders nothing; never flashes an alert on first paint.
|
|
69
|
+
3. **Unresolved** — the order group does not resolve (`order: null`) and the query is no longer in flight. Inline `role="alert"`.
|
|
70
|
+
4. **No payment record** — the order resolves but `network`, `lastFourDigits`, and `label` are all null. Informational line, not an alert — `Query.order` treats this as a legitimate resolved state. Unchanged by the installment widening: an order carrying only an installment still renders this state, never a recap with just the installment line.
|
|
71
|
+
5. **Recap** — the network (and last four, whenever present, and the saved-card label, whenever set, and the installment count + settled total, whenever the settled record reports one).
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Canonical schema example
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"order-placed-payment-summary": {
|
|
80
|
+
"type": "OrderPlacedPaymentSummary",
|
|
81
|
+
"props": {}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
There is no surface-specific prop or variant — the same element renders the same recap wherever `Query.order` resolves for the page's `$orderGroup`.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Common mistakes
|
|
91
|
+
|
|
92
|
+
- **Using this component on an in-checkout surface (Order Review).** Its data source is the settled order, keyed on an order group that does not exist until the order is placed. No component renders a payment recap in checkout.
|
|
93
|
+
- **Adding a per-installment amount or interest rate.** The settled-order source carries neither; only the installment count and settled total are shown.
|
|
94
|
+
- **Treating "no payment record" the same as "unresolved".** They are visually and semantically distinct: the former is informational (the order is real, it just has nothing to project), the latter is an alert (the order group itself did not resolve).
|
|
95
|
+
- **Aggregating a multi-seller order's cards.** A split order always renders the notice, never a per-seller breakdown.
|
|
96
|
+
- **Expecting a full card number or CVV field.** The fragment structurally selects neither.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Styling
|
|
101
|
+
|
|
102
|
+
Single DOM root, with up to three text lines beneath it for the recap state (method, label, installment), or a single text line for every other visible state.
|
|
103
|
+
|
|
104
|
+
| Component | What you style | Plausible classes |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| `OrderPlacedPaymentSummary` (root `div`) | Stacks the recap lines | `flex flex-col gap-[2px]` |
|
|
107
|
+
| Method line (`data-fs-order-placed-payment-summary-method`) | The primary recap line — network + masked last four | `text-[14px] font-medium text-[#1A1A1A]` |
|
|
108
|
+
| Label line (`data-fs-order-placed-payment-summary-label`) | Shopper-assigned card nickname — quieter, secondary | `text-[12px] text-[#666666]` |
|
|
109
|
+
| Installment line (`data-fs-order-placed-payment-summary-installment`) | Installment count + settled total, whenever the settled record reports one | `text-[12px] text-[#666666]` |
|
|
110
|
+
| No-record line (`data-fs-order-placed-payment-summary-no-record`) | Informational, not a warning | `text-[13px] text-[#666666]` |
|
|
111
|
+
| Unresolved notice (`data-fs-order-placed-payment-summary-unresolved`, `role="alert"`) | Warning-toned notice | `text-[13px] font-medium text-[#B00020]` |
|
|
112
|
+
| Multi-seller notice (`data-fs-order-placed-payment-summary-multi-seller`, `role="alert"`) | Warning-toned notice | `text-[13px] font-medium text-[#B00020]` |
|
|
113
|
+
|
|
114
|
+
Styled example:
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"order-placed-payment-summary": {
|
|
119
|
+
"type": "OrderPlacedPaymentSummary",
|
|
120
|
+
"props": {},
|
|
121
|
+
"style": ["flex", "flex-col", "gap-[2px]"]
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
There is no per-line styling hook exposed as separate schema elements — `OrderPlacedPaymentSummary` renders its own markup directly (no Root/particle split); style via the `data-fs-order-placed-payment-summary*` attribute hooks with compound swatches, per the `style-components` skill.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Testing
|
|
131
|
+
|
|
132
|
+
The correspondence between what `buildGatewayPaymentsRequest` submits to the gateway and what this recap renders — which submitted fields are shown, which are deliberately not, and why — is pinned as a correspondence-contract test in `order-placed-recap-parity.test.tsx` (same directory; not linked, since `build-docs.mjs` ships only `.md` files into the published `dist/docs/`), alongside this component's own `order-placed-payment-summary.test.tsx`.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Related
|
|
137
|
+
|
|
138
|
+
- [[identity-summary]] — closest structural precedent for a single-file, self-contained, reactive, self-gating summary that owns its own fragment rather than an ancestor's context.
|