@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.
Files changed (306) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/dist/actions.json +181 -0
  3. package/dist/catalog.json +1595 -481
  4. package/dist/client.d.ts +2 -1
  5. package/dist/client.js +2 -1
  6. package/dist/components.d.ts +31 -7
  7. package/dist/components.js +35 -13
  8. package/dist/content-types.json +73 -0
  9. package/dist/docs/access-code-sign-in.md +1 -1
  10. package/dist/docs/account-access-gate.md +153 -0
  11. package/dist/docs/account-entry-point.md +1 -2
  12. package/dist/docs/account-menu.md +164 -0
  13. package/dist/docs/address-summary.md +24 -3
  14. package/dist/docs/alert-dialog.md +204 -0
  15. package/dist/docs/billing-address.md +284 -0
  16. package/dist/docs/buy-button.md +1 -1
  17. package/dist/docs/card-entry.md +250 -0
  18. package/dist/docs/cardholder-document.md +142 -0
  19. package/dist/docs/cart.md +1 -1
  20. package/dist/docs/client-preferences-fieldset.md +148 -0
  21. package/dist/docs/client-preferences.md +2 -1
  22. package/dist/docs/client-profile-fieldset.md +184 -0
  23. package/dist/docs/client-profile.md +1 -1
  24. package/dist/docs/document-field.md +58 -4
  25. package/dist/docs/field.md +317 -0
  26. package/dist/docs/form.md +307 -0
  27. package/dist/docs/identity-edit.md +6 -6
  28. package/dist/docs/identity-summary.md +5 -5
  29. package/dist/docs/installments.md +248 -0
  30. package/dist/docs/my-account-profile.md +1 -0
  31. package/dist/docs/order-placed-payment-summary.md +138 -0
  32. package/dist/docs/payment-method-badges.md +232 -0
  33. package/dist/docs/payment-methods.md +1 -0
  34. package/dist/docs/payment.md +218 -0
  35. package/dist/docs/phone-field.md +58 -4
  36. package/dist/docs/place-order.md +157 -0
  37. package/dist/docs/save-card.md +186 -0
  38. package/dist/docs/saved-cards.md +218 -0
  39. package/dist/docs/select.md +465 -0
  40. package/dist/docs/session.md +1 -1
  41. package/dist/docs/shipping-address.md +110 -258
  42. package/dist/docs/sign-in-redirect.md +1 -1
  43. package/dist/index.d.ts +4 -1
  44. package/dist/internal/client.d.ts +3 -1
  45. package/dist/internal/client.js +3 -1
  46. package/dist/matter/access-code-sign-in/access-code-sign-in.js +9 -8
  47. package/dist/matter/access-code-sign-in/access-code-sign-in.js.map +1 -1
  48. package/dist/matter/account-access-gate/account-access-gate.d.ts +86 -0
  49. package/dist/matter/account-access-gate/account-access-gate.js +283 -0
  50. package/dist/matter/account-access-gate/account-access-gate.js.map +1 -0
  51. package/dist/matter/account-access-gate/sign-out-intent.d.ts +17 -0
  52. package/dist/matter/account-access-gate/sign-out-intent.js +62 -0
  53. package/dist/matter/account-access-gate/sign-out-intent.js.map +1 -0
  54. package/dist/matter/account-entry-point/account-entry-point.js +2 -2
  55. package/dist/matter/account-entry-point/account-entry-point.js.map +1 -1
  56. package/dist/matter/account-menu/account-menu.d.ts +88 -0
  57. package/dist/matter/account-menu/account-menu.js +246 -0
  58. package/dist/matter/account-menu/account-menu.js.map +1 -0
  59. package/dist/matter/billing-address/billing-address.d.ts +165 -0
  60. package/dist/matter/billing-address/billing-address.js +408 -0
  61. package/dist/matter/billing-address/billing-address.js.map +1 -0
  62. package/dist/matter/card-entry/card-capture.d.ts +25 -0
  63. package/dist/matter/card-entry/card-capture.js +25 -0
  64. package/dist/matter/card-entry/card-capture.js.map +1 -0
  65. package/dist/matter/card-entry/card-entry.d.ts +120 -0
  66. package/dist/matter/card-entry/card-entry.js +832 -0
  67. package/dist/matter/card-entry/card-entry.js.map +1 -0
  68. package/dist/matter/cardholder-document/cardholder-document.d.ts +45 -0
  69. package/dist/matter/cardholder-document/cardholder-document.js +70 -0
  70. package/dist/matter/cardholder-document/cardholder-document.js.map +1 -0
  71. package/dist/matter/cart/cart.d.ts +1 -1
  72. package/dist/matter/cart/cart.js +2 -2
  73. package/dist/matter/cart/cart.js.map +1 -1
  74. package/dist/matter/client-preferences/client-preferences-fieldset.d.ts +77 -0
  75. package/dist/matter/client-preferences/client-preferences-fieldset.js +299 -0
  76. package/dist/matter/client-preferences/client-preferences-fieldset.js.map +1 -0
  77. package/dist/matter/client-preferences/client-preferences.d.ts +3 -5
  78. package/dist/matter/client-preferences/client-preferences.js +6 -6
  79. package/dist/matter/client-preferences/client-preferences.js.map +1 -1
  80. package/dist/matter/client-profile/client-profile.d.ts +4 -6
  81. package/dist/matter/client-profile/client-profile.js +9 -6
  82. package/dist/matter/client-profile/client-profile.js.map +1 -1
  83. package/dist/matter/client-profile-form/client-profile-fieldset.d.ts +119 -0
  84. package/dist/matter/client-profile-form/client-profile-fieldset.js +488 -0
  85. package/dist/matter/client-profile-form/client-profile-fieldset.js.map +1 -0
  86. package/dist/matter/delivery-options/delivery-options.js.map +1 -1
  87. package/dist/matter/identity-edit/identity-edit.js +2 -2
  88. package/dist/matter/identity-edit/identity-edit.js.map +1 -1
  89. package/dist/matter/identity-summary/identity-summary.js +2 -2
  90. package/dist/matter/identity-summary/identity-summary.js.map +1 -1
  91. package/dist/matter/installments/installments-select.d.ts +42 -0
  92. package/dist/matter/installments/installments-select.js +167 -0
  93. package/dist/matter/installments/installments-select.js.map +1 -0
  94. package/dist/matter/installments/installments.d.ts +58 -0
  95. package/dist/matter/installments/installments.js +176 -0
  96. package/dist/matter/installments/installments.js.map +1 -0
  97. package/dist/matter/installments/use-installments-selection.d.ts +29 -0
  98. package/dist/matter/installments/use-installments-selection.js +95 -0
  99. package/dist/matter/installments/use-installments-selection.js.map +1 -0
  100. package/dist/matter/my-account-profile/my-account-profile.js +5 -5
  101. package/dist/matter/my-account-profile/my-account-profile.js.map +1 -1
  102. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.d.ts +63 -0
  103. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js +305 -0
  104. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js.map +1 -0
  105. package/dist/matter/payment/payment-card-capture.d.ts +19 -0
  106. package/dist/matter/payment/payment-card-capture.js +129 -0
  107. package/dist/matter/payment/payment-card-capture.js.map +1 -0
  108. package/dist/matter/payment/payment-controller.d.ts +24 -0
  109. package/dist/matter/payment/payment-controller.js +107 -0
  110. package/dist/matter/payment/payment-controller.js.map +1 -0
  111. package/dist/matter/payment/payment-method-badges.d.ts +57 -0
  112. package/dist/matter/payment/payment-method-badges.js +192 -0
  113. package/dist/matter/payment/payment-method-badges.js.map +1 -0
  114. package/dist/matter/payment/payment.d.ts +89 -0
  115. package/dist/matter/payment/payment.js +263 -0
  116. package/dist/matter/payment/payment.js.map +1 -0
  117. package/dist/matter/payment/use-payment-method-selection.d.ts +302 -0
  118. package/dist/matter/payment/use-payment-method-selection.js +458 -0
  119. package/dist/matter/payment/use-payment-method-selection.js.map +1 -0
  120. package/dist/matter/payment/use-payment-submit-gate.d.ts +111 -0
  121. package/dist/matter/payment/use-payment-submit-gate.js +144 -0
  122. package/dist/matter/payment/use-payment-submit-gate.js.map +1 -0
  123. package/dist/matter/place-order/place-order.d.ts +74 -0
  124. package/dist/matter/place-order/place-order.js +379 -0
  125. package/dist/matter/place-order/place-order.js.map +1 -0
  126. package/dist/matter/product-list/product-list.d.ts +1 -0
  127. package/dist/matter/product-list/product-list.js.map +1 -1
  128. package/dist/matter/product-shelf/product-shelf.d.ts +1 -0
  129. package/dist/matter/product-shelf/product-shelf.js +2 -0
  130. package/dist/matter/product-shelf/product-shelf.js.map +1 -1
  131. package/dist/matter/region/region.js +1 -1
  132. package/dist/matter/region/region.js.map +1 -1
  133. package/dist/matter/save-card/save-card.d.ts +49 -0
  134. package/dist/matter/save-card/save-card.js +188 -0
  135. package/dist/matter/save-card/save-card.js.map +1 -0
  136. package/dist/matter/saved-cards/saved-cards.d.ts +79 -0
  137. package/dist/matter/saved-cards/saved-cards.js +387 -0
  138. package/dist/matter/saved-cards/saved-cards.js.map +1 -0
  139. package/dist/matter/shipping-address/country-descriptors.js +20 -10
  140. package/dist/matter/shipping-address/country-descriptors.js.map +1 -1
  141. package/dist/matter/shipping-address/shipping-address.d.ts +113 -174
  142. package/dist/matter/shipping-address/shipping-address.js +481 -792
  143. package/dist/matter/shipping-address/shipping-address.js.map +1 -1
  144. package/dist/matter/shipping-simulator/shipping-simulator.js.map +1 -1
  145. package/dist/matter/variant-selector/variant-selector.js +1 -1
  146. package/dist/particles/address-summary/address-summary.d.ts +24 -0
  147. package/dist/particles/address-summary/address-summary.js +83 -2
  148. package/dist/particles/address-summary/address-summary.js.map +1 -1
  149. package/dist/particles/alert-dialog/alert-dialog.d.ts +68 -0
  150. package/dist/particles/alert-dialog/alert-dialog.js +187 -0
  151. package/dist/particles/alert-dialog/alert-dialog.js.map +1 -0
  152. package/dist/particles/checkbox/checkbox.d.ts +7 -3
  153. package/dist/particles/checkbox/checkbox.js +4 -1
  154. package/dist/particles/checkbox/checkbox.js.map +1 -1
  155. package/dist/particles/document-field/document-field.d.ts +48 -5
  156. package/dist/particles/document-field/document-field.js +189 -32
  157. package/dist/particles/document-field/document-field.js.map +1 -1
  158. package/dist/particles/field/bound-field-registry.js +52 -0
  159. package/dist/particles/field/bound-field-registry.js.map +1 -0
  160. package/dist/particles/field/field.d.ts +116 -0
  161. package/dist/particles/field/field.js +497 -6
  162. package/dist/particles/field/field.js.map +1 -1
  163. package/dist/particles/fieldset/fieldset.js +2 -2
  164. package/dist/particles/fieldset/fieldset.js.map +1 -1
  165. package/dist/particles/footer-social/footer-social.js +1 -1
  166. package/dist/particles/form/form.d.ts +104 -0
  167. package/dist/particles/form/form.js +485 -0
  168. package/dist/particles/form/form.js.map +1 -0
  169. package/dist/particles/incentives/incentives.js +1 -1
  170. package/dist/particles/phone-field/phone-field.d.ts +38 -10
  171. package/dist/particles/phone-field/phone-field.js +183 -39
  172. package/dist/particles/phone-field/phone-field.js.map +1 -1
  173. package/dist/particles/postal-code-field/postal-code-field.js +2 -2
  174. package/dist/particles/postal-code-field/postal-code-field.js.map +1 -1
  175. package/dist/particles/select/field-select.d.ts +43 -0
  176. package/dist/particles/select/field-select.js +150 -0
  177. package/dist/particles/select/field-select.js.map +1 -0
  178. package/dist/particles/select/select.d.ts +2 -0
  179. package/dist/particles/select/select.js.map +1 -1
  180. package/dist/particles/shared/compose-refs.js +21 -0
  181. package/dist/particles/shared/compose-refs.js.map +1 -0
  182. package/dist/schema.graphql +701 -2
  183. package/dist/sdk/actions/analytics-action-provider.d.ts +15 -0
  184. package/dist/sdk/actions/analytics-action-provider.js +19 -0
  185. package/dist/sdk/actions/analytics-action-provider.js.map +1 -0
  186. package/dist/sdk/actions/cart-action-provider.d.ts +1 -1
  187. package/dist/sdk/actions/cart-action-provider.js +1 -1
  188. package/dist/sdk/actions/cart-action-provider.js.map +1 -1
  189. package/dist/sdk/actions/index.d.ts +3 -1
  190. package/dist/sdk/actions/index.js +3 -1
  191. package/dist/sdk/actions/payment-action-provider.d.ts +22 -0
  192. package/dist/sdk/actions/payment-action-provider.js +26 -0
  193. package/dist/sdk/actions/payment-action-provider.js.map +1 -0
  194. package/dist/sdk/actions/session-action-provider.d.ts +1 -1
  195. package/dist/sdk/actions/session-action-provider.js +1 -1
  196. package/dist/sdk/actions/session-action-provider.js.map +1 -1
  197. package/dist/sdk/actions/shipping-action-provider.d.ts +1 -1
  198. package/dist/sdk/actions/shipping-action-provider.js +1 -1
  199. package/dist/sdk/actions/shipping-action-provider.js.map +1 -1
  200. package/dist/sdk/actions/types.d.ts +6 -2
  201. package/dist/sdk/analytics/analytics-destination.d.ts +43 -0
  202. package/dist/sdk/analytics/analytics-destination.js +90 -0
  203. package/dist/sdk/analytics/analytics-destination.js.map +1 -0
  204. package/dist/sdk/analytics/use-analytics-actions.d.ts +57 -0
  205. package/dist/sdk/analytics/use-analytics-actions.js +108 -0
  206. package/dist/sdk/analytics/use-analytics-actions.js.map +1 -0
  207. package/dist/sdk/cache/storefront-cache.js +1 -1
  208. package/dist/sdk/cache/storefront-cache.js.map +1 -1
  209. package/dist/sdk/cart/operations.d.ts +4 -4
  210. package/dist/sdk/cart/operations.js +1 -1
  211. package/dist/sdk/cart/operations.js.map +1 -1
  212. package/dist/sdk/cart/optimistic.js +1 -1
  213. package/dist/sdk/cart/optimistic.js.map +1 -1
  214. package/dist/sdk/cart/use-cart-actions.d.ts +1 -1
  215. package/dist/sdk/cart/use-cart-actions.js +4 -4
  216. package/dist/sdk/cart/use-cart-actions.js.map +1 -1
  217. package/dist/sdk/cart/use-cart-validate-on-open.js +1 -1
  218. package/dist/sdk/cart/use-cart-validate-on-open.js.map +1 -1
  219. package/dist/sdk/cart/write-gate.js +1 -1
  220. package/dist/sdk/cart/write-gate.js.map +1 -1
  221. package/dist/sdk/data/entity-resolution-context.js +31 -0
  222. package/dist/sdk/data/entity-resolution-context.js.map +1 -0
  223. package/dist/sdk/data/entity-subscriber.js +18 -8
  224. package/dist/sdk/data/entity-subscriber.js.map +1 -1
  225. package/dist/sdk/data/index.js +1 -0
  226. package/dist/sdk/data/page-reactive-data-root.d.ts +38 -2
  227. package/dist/sdk/data/page-reactive-data-root.js +4 -2
  228. package/dist/sdk/data/page-reactive-data-root.js.map +1 -1
  229. package/dist/sdk/data/reactive-data-provider.d.ts +1 -1
  230. package/dist/sdk/data/reactive-data-provider.js +8 -4
  231. package/dist/sdk/data/reactive-data-provider.js.map +1 -1
  232. package/dist/sdk/data/reactive-list-item-data-provider.d.ts +1 -1
  233. package/dist/sdk/data/reactive-list-item-data-provider.js +13 -5
  234. package/dist/sdk/data/reactive-list-item-data-provider.js.map +1 -1
  235. package/dist/sdk/data/use-reactive-data-settled.js +39 -0
  236. package/dist/sdk/data/use-reactive-data-settled.js.map +1 -0
  237. package/dist/sdk/data/use-reactive-entity.js +6 -2
  238. package/dist/sdk/data/use-reactive-entity.js.map +1 -1
  239. package/dist/sdk/generated/fragment-types.js +2 -0
  240. package/dist/sdk/generated/fragment-types.js.map +1 -1
  241. package/dist/sdk/generated/gql.js +2 -1
  242. package/dist/sdk/generated/gql.js.map +1 -1
  243. package/dist/sdk/generated/graphql.js +85 -1
  244. package/dist/sdk/generated/graphql.js.map +1 -1
  245. package/dist/sdk/i18n/messages/en-US.js +3 -0
  246. package/dist/sdk/i18n/messages/en-US.json +3 -0
  247. package/dist/sdk/i18n/messages/en-US.json.d.ts +3 -0
  248. package/dist/sdk/i18n/messages/es-ES.js +3 -0
  249. package/dist/sdk/i18n/messages/es-ES.json +3 -0
  250. package/dist/sdk/i18n/messages/pt-BR.js +3 -0
  251. package/dist/sdk/i18n/messages/pt-BR.json +3 -0
  252. package/dist/sdk/identity/operations.js.map +1 -1
  253. package/dist/sdk/identity/use-identity-actions.d.ts +1 -1
  254. package/dist/sdk/identity/use-identity-actions.js +3 -2
  255. package/dist/sdk/identity/use-identity-actions.js.map +1 -1
  256. package/dist/sdk/payment/gateway-origin.js +35 -0
  257. package/dist/sdk/payment/gateway-origin.js.map +1 -0
  258. package/dist/sdk/payment/gateway.d.ts +85 -0
  259. package/dist/sdk/payment/gateway.js +183 -0
  260. package/dist/sdk/payment/gateway.js.map +1 -0
  261. package/dist/sdk/payment/operations.d.ts +52 -0
  262. package/dist/sdk/payment/operations.js +223 -0
  263. package/dist/sdk/payment/operations.js.map +1 -0
  264. package/dist/sdk/payment/security-code-mask.js +71 -0
  265. package/dist/sdk/payment/security-code-mask.js.map +1 -0
  266. package/dist/sdk/payment/submission-registry.d.ts +33 -0
  267. package/dist/sdk/payment/submission-registry.js +58 -0
  268. package/dist/sdk/payment/submission-registry.js.map +1 -0
  269. package/dist/sdk/payment/use-payment-actions.d.ts +78 -0
  270. package/dist/sdk/payment/use-payment-actions.js +276 -0
  271. package/dist/sdk/payment/use-payment-actions.js.map +1 -0
  272. package/dist/sdk/payment/use-place-order.d.ts +65 -0
  273. package/dist/sdk/payment/use-place-order.js +640 -0
  274. package/dist/sdk/payment/use-place-order.js.map +1 -0
  275. package/dist/sdk/session/auth-operations.d.ts +1 -1
  276. package/dist/sdk/session/auth-operations.js.map +1 -1
  277. package/dist/sdk/session/use-session-actions.d.ts +1 -1
  278. package/dist/sdk/session/use-session-actions.js.map +1 -1
  279. package/dist/sdk/session/use-session-revalidation.js +65 -0
  280. package/dist/sdk/session/use-session-revalidation.js.map +1 -0
  281. package/dist/sdk/shipping/option-select-operations.d.ts +1 -1
  282. package/dist/sdk/shipping/option-select-operations.js.map +1 -1
  283. package/dist/sdk/shipping/request-timeout.js +1 -1
  284. package/dist/sdk/shipping/request-timeout.js.map +1 -1
  285. package/dist/sdk/shipping/shipping-operations.d.ts +1 -1
  286. package/dist/sdk/shipping/shipping-operations.js.map +1 -1
  287. package/dist/sdk/shipping/use-shipping-actions.d.ts +2 -2
  288. package/dist/sdk/shipping/use-shipping-actions.js +1 -1
  289. package/dist/sdk/shipping/use-shipping-actions.js.map +1 -1
  290. package/dist/sdk/state/cart.d.ts +3 -4
  291. package/dist/sdk/state/cart.js +6 -7
  292. package/dist/sdk/state/cart.js.map +1 -1
  293. package/dist/utils/loggable-error-name.js +49 -0
  294. package/dist/utils/loggable-error-name.js.map +1 -0
  295. package/dist/utils/resolve-authored-copy.js +18 -0
  296. package/dist/utils/resolve-authored-copy.js.map +1 -0
  297. package/dist/utils/safe-href.js +3 -3
  298. package/dist/utils/safe-href.js.map +1 -1
  299. package/dist/utils/substitute-template.js +33 -0
  300. package/dist/utils/substitute-template.js.map +1 -0
  301. package/package.json +2 -1
  302. package/src/styles/tailwind.css +34 -0
  303. package/dist/docs/client-profile-form.md +0 -192
  304. package/dist/matter/client-profile-form/client-profile-form.d.ts +0 -126
  305. package/dist/matter/client-profile-form/client-profile-form.js +0 -662
  306. 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.
@@ -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** (specs/actions.md, ADR-0010). Drop it on a PDP, a product card, a shelf, or the header alike.
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.