@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 @@
1
+ {"version":3,"file":"billing-address.js","names":[],"sources":["../../../src/matter/billing-address/billing-address.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport type { ComponentPropsWithRef } from 'react';\nimport { useEffect, useId, useState } from 'react';\nimport { Checkbox } from '../../particles/checkbox/checkbox.js';\nimport { Field } from '../../particles/field/field.js';\nimport { Icon } from '../../particles/icon/icon.js';\nimport { Select } from '../../particles/select/select.js';\nimport { useActionsOverride } from '../../sdk/actions/actions-override-context.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport { resolveAuthoredCopy } from '../../utils/resolve-authored-copy.js';\nimport { usePayment } from '../payment/payment.js';\nimport { getAvailableCountries, getCountryDescriptor } from '../shipping-address/country-descriptors.js';\n\n/**\n * The `BillingAddress` matter (specs/billing-address.md, T11 of the Credit Card epic;\n * docs/features/credit-card/rfc.md § D6). A \"same as delivery\" toggle (default on) plus, when off, a\n * country-specific address form that persists via `$actions.payment.setBillingAddress`\n * (already shipped by T4, zero consumers before this task).\n *\n * The shared `Address` composition RFC D6 names (shipping and billing as two control matters over\n * one set of reusable particles) is DEFERRED — an implementation-time call the engineer resolved\n * narrow (specs/billing-address.md Decision 1). This component builds its own controls and imports\n * `countryDescriptors` from `../shipping-address/country-descriptors.js`; `matter/shipping-address/`\n * is read, never modified — zero bytes of that directory change here.\n *\n * There is NO read-back path for a persisted billing address (specs/billing-address.md Decision 3):\n * `type Payment` carries no `billingAddress` field, and `setBillingAddress` itself resolves only\n * `userErrors`, discarding the mutation's own echo. This component declares no fragment and renders\n * nothing below the toggle while \"same as delivery\" is checked, not even the delivery address\n * (specs/stop-re-rendering-the-full-delivery.md, T175). The custom-address form always starts blank,\n * and its own local state is the sole source of truth for what was typed.\n *\n * Only a boolean rides the shared `draft` (`billingAddressDifferent`, widened onto `PaymentDraft` by\n * this task under sole ownership of `use-payment-method-selection.ts` — specs/billing-address.md\n * Decision 5); the address itself is persisted server-side by this component's own action call, never\n * passed through the draft. `setBillingAddress` is dispatched at exactly two points (Decision 9): the\n * toggle's transition back to \"same as delivery\", and the custom form's explicit submit control —\n * never on mount, never per keystroke.\n *\n * **`draft.billingAddressDifferent` only ever becomes `true` once `setBillingAddress` has actually\n * persisted a different address (PR #544 N1).** Unchecking \"same as delivery\" opens the form\n * (`formOpen`, local-only state below) but does not touch the draft — `gateway.ts`'s own doc comment\n * (\"`setBillingAddress` already persisted it … so the gateway reads it from there\") is the contract\n * this must not break: publishing `true` before anything is persisted would point the gateway at an\n * `invoiceData` address nothing ever wrote. The engineer's own resolution for this (session of\n * 2026-07-31): the new-address fields become required whenever \"different\" is selected, so this\n * component's own submit blocks on an incomplete address (`isBillingAddressFormValid`) — but that\n * alone cannot stop a shopper from declining \"same as delivery\" and clicking Place Order without ever\n * opening the form's own submit control, which is why the draft write itself is gated on success, not\n * merely the submit button.\n *\n * **The native `required` attribute below is cosmetic here, not an enforcement mechanism (PR #544\n * N7).** This file renders no `aria-required` at all — only `required` (native HTML). There is no\n * `<form>` in this render tree and the submit control is `type=\"button\"`, so the browser's own\n * constraint validation never runs — nothing in this file or its tests may claim this attribute itself\n * prevents a shopper from placing an order with an unsaved address; the actual submit gate is\n * `isBillingAddressFormValid`, read at the submit control's own `disabled` below. Since T38\n * (specs/harden-and-verify-accessibility-across-the.md FR-6) each rendered field does carry a\n * `Field.Error`, but it is driven by a local blur-tracked `touched` set and this same non-blank rule,\n * never by `required`'s own native `ValidityState` — so this paragraph's point is unchanged: `required`\n * itself still enforces nothing here. What this component owes, and\n * does: make the gap **visible** — an inline `role=\"status\"` notice while the form is open and nothing\n * has been persisted yet — and publish that same fact (`setDraftBillingAddressUnsaved`) onto the\n * shared draft, from the identical expression the notice renders from (T24,\n * specs/cardholder-document-submit-gate.md FR-6). **The actual hard block lives elsewhere**: T24's\n * `usePaymentSubmitGate()` reads that published fact and refuses Place Order client-side — this\n * component's own job stops at publishing the fact and showing the notice, never at gating the CTA\n * itself. See `specs/billing-address.md`'s Documentation Gaps #1 for the deferral this closes.\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Local-only values for the \"different\" address form, keyed by the country descriptor's own\n * canonical field names — a plain string index signature, not a closed set of properties, so a future\n * country descriptor introducing a new field name needs no type change here (mirrors\n * `FieldDescriptor.name: string`'s own deliberately untyped choice; specs/billing-address.md\n * Decision 7). `postalCode` and `country` are named explicitly because this component's own logic\n * reads both directly. Never carries a `recipientName` key: `PaymentBillingAddressUpdateInput` has no\n * such field (Decision 4) — the render loop filters it out of whatever the descriptor lists.\n */\nexport type BillingAddressFormDraft = Readonly<{\n postalCode: string;\n country: string;\n [fieldName: string]: string;\n}>;\n\nexport type BillingAddressProps = Omit<ComponentPropsWithRef<'div'>, 'children'> & {\n /** The \"same as delivery\" toggle's own accessible label. */\n sameAsDeliveryLabel?: string;\n /** ISO 3166-1 alpha-3 default country for the custom-address form's country selector. */\n defaultCountry?: string;\n /** Label for the country selector inside the custom-address form. */\n countryLabel?: string;\n /** Label for the control that persists a custom billing address. */\n submitLabel?: string;\n /** Label on the submit control while the mutation is in flight. */\n submitPendingLabel?: string;\n /** Shown when the selected method's validator marks a billing address required. */\n requiredNoticeLabel?: string;\n /** Label for the recipient-name descriptor field. Not rendered by the custom-address form today\n * (`OMITTED_FORM_FIELD_NAMES` — the write contract has no matching input slot, Decision 4); kept\n * for parity with the other seven field labels this same map covers. */\n recipientNameLabel?: string;\n /** Label for the street descriptor field. */\n streetLabel?: string;\n /** Label for the street-number descriptor field. */\n numberLabel?: string;\n /** Label for the complement descriptor field. Its \"(optional)\" suffix is the only signal that the\n * field is not required — `OPTIONAL_FORM_FIELD_NAMES` drives the actual `required` attribute\n * independently, so authoring this without the suffix loses the optionality cue, not the behavior\n * (R7). */\n complementLabel?: string;\n /** Label for the neighborhood descriptor field. Unused when the active country's descriptor has no\n * `neighborhood` field (e.g. USA). */\n neighborhoodLabel?: string;\n /** Label for the city descriptor field. */\n cityLabel?: string;\n /** Label for the state descriptor field. */\n stateLabel?: string;\n /** Label for the postal-code descriptor field. */\n postalCodeLabel?: string;\n /** Shown while the form is open and no address has been persisted yet (PR #544 N7) — the shopper\n * declined \"same as delivery\" but has not (or not yet again) had a submit succeed, so Place Order\n * will bill the delivery address unless they save one first. Rendered `role=\"status\"`, not\n * `role=\"alert\"`: it is a persistent, advisory condition of the form being open, not a one-off\n * announcement reacting to a rejected action (see this file's own existing `-error` alert for that\n * shape) — see `specs/billing-address.md`'s Accessibility Contract for the site count this choice is\n * measured against. */\n unsavedAddressNoticeLabel?: string;\n /** Always-on billing-match disclaimer (requirement 12.20): tells the shopper their billing address\n * must match the one their card issuer or bank statement holds, so an AVS mismatch does not get the\n * order declined with no explanation on screen. Gated on this prop and `billingMatchNoticeVisible`\n * only — reads no `usePayment()` value, no local state, no `$data` (FR-4), unlike the two notices\n * above. */\n billingMatchNoticeLabel?: string;\n /** Switches the billing-match disclaimer above on or off. Defaults to `true` (Decision 5 — the\n * requirement's own intent); subordinate to `billingMatchNoticeLabel.trim()`, which alone can still\n * suppress an empty notice (FR-3) regardless of this prop's value. */\n billingMatchNoticeVisible?: boolean;\n /**\n * Shown as a `Field.Error` under a required, blank, touched field (T38, FR-6/FR-7) — the same\n * non-blank rule `isBillingAddressFormValid` already applies, except `complement`\n * (`OPTIONAL_FORM_FIELD_NAMES`), which is never marked invalid. One prop, not one per field: every\n * rendered field shares the same rule, so seven props would be seven ways to say the same sentence.\n * Resolved through `resolveAuthoredCopy`.\n */\n fieldRequiredMessage?: string;\n /** Disables the toggle, every form field, the country selector, and the submit control. */\n disabled?: boolean;\n /** Compiler-injected action surface; carries `payment.setBillingAddress`. */\n $actions?: ComponentActions;\n};\n\n/** Canonical field names the custom-address form never renders, even when a country descriptor\n * lists them — because the write contract has no matching input slot (Decision 4). */\nconst OMITTED_FORM_FIELD_NAMES = new Set(['recipientName']);\n\n/** Descriptor field names the custom-address form renders but never marks required, even while\n * \"different\" is selected (PR #544 N1 item 1 — the engineer's own rule, verbatim: \"if the person says\n * the address is different, the new address fields must be required\"). `complement` is the one\n * exception: `complementLabel`'s own default already labels it optional (\"Complement (optional)\"),\n * and neither a real-world address nor the server's own write contract treats it as load-bearing.\n * Shared between the `required` attribute below and `isBillingAddressFormValid`, so the two never\n * drift apart. An authored `complementLabel` that drops the \"(optional)\" suffix loses that visual cue\n * without changing this set — the two are independent (R7, billing-address.md). */\nconst OPTIONAL_FORM_FIELD_NAMES = new Set(['complement']);\n\n/**\n * English override for the descriptor's own country display names (`'Brasil'`, `'Estados Unidos'`) —\n * `country-descriptors.ts` is shared with `shipping-address` and out of this task's write scope\n * (`matter/shipping-address/` is read-only here), and its labels are Portuguese regardless of country.\n * T31 (specs/make-the-payment-components-authored-copy.md, Decision 5) turned the eight *field* labels\n * this same reasoning used to justify (`FIELD_LABELS_EN`) into schema props instead, but deliberately\n * left this one a plain map: a prop per ISO code does not scale the way eight named field-label props\n * do, and the epic block's own wording covers field labels only. Keyed by the descriptor's own ISO\n * code.\n */\nconst COUNTRY_LABELS_EN: Readonly<Record<string, string>> = {\n BRA: 'Brazil',\n USA: 'United States',\n};\n\n/** Resolves a country's English display name, falling back to the descriptor's own label (then the\n * raw ISO code) for a country this map does not yet cover. */\nfunction getCountryLabel(iso: string): string {\n return COUNTRY_LABELS_EN[iso] ?? getCountryDescriptor(iso)?.label ?? iso;\n}\n\n// ---------------------------------------------------------------------------\n// Pure decision functions — exported so both are unit-testable independent of any rendering\n// (specs/billing-address.md § Resolution logic).\n// ---------------------------------------------------------------------------\n\n/**\n * Whether the toggle reads as \"same as delivery\" — `true` whenever the published flag is `null`\n * (never touched, the default) or `false` (explicitly reverted); only an explicit `true` means the\n * shopper is on the custom-address path. Takes no closure over component state, so it is safe to call\n * fresh on every render.\n */\nexport function resolveSameAsDelivery(billingAddressDifferent: boolean | null | undefined): boolean {\n return billingAddressDifferent !== true;\n}\n\n/**\n * Whether the custom-address form may be submitted. Stricter than the server's own\n * `MISSING_BILLING_ADDRESS_FIELDS` floor (\"sameAsDelivery is false but postalCode **or country** was\n * omitted\" [source: `core.graphql:2857-2858`]) — every field the active country's descriptor renders,\n * except `complement` (`OPTIONAL_FORM_FIELD_NAMES`), must be non-blank (PR #544 N1 item 1: the\n * engineer's own rule that the new-address fields are required once \"different\" is selected). Resolves\n * the descriptor from `formDraft.country` itself rather than taking a second parameter, so this stays a\n * single self-contained fact about one `BillingAddressFormDraft` value. `country` itself is not part of\n * `descriptor.fields` (Decision 7) and is never checked here — it always carries a non-blank value from\n * `defaultCountry` or the selector's own current value (review F12), so it can never be the field this\n * gate would fail on.\n */\nexport function isBillingAddressFormValid(formDraft: BillingAddressFormDraft): boolean {\n const descriptor = getCountryDescriptor(formDraft.country);\n // No descriptor means no known required-field list to satisfy — vacuous `true` would be exactly\n // wrong here. Not reachable through the rendered component today (review F7's fallback already\n // resolves `formDraft.country` to a real descriptor before the form ever mounts), but this function\n // is exported and directly unit-tested on its own, so it must not depend on that caller's behavior.\n if (!descriptor) return false;\n const requiredFields = descriptor.fields.filter(\n (field) => !OMITTED_FORM_FIELD_NAMES.has(field.name) && !OPTIONAL_FORM_FIELD_NAMES.has(field.name),\n );\n return requiredFields.every((field) => (formDraft[field.name] ?? '').trim().length > 0);\n}\n\n// ---------------------------------------------------------------------------\n// Definition\n// ---------------------------------------------------------------------------\n\nexport const BillingAddressDefinition = defineComponent<BillingAddressProps>({\n name: 'BillingAddress',\n importPath: '@vtex/faststore/components',\n component: BillingAddress,\n requiresContext: 'payment',\n actions: { payment: ['setBillingAddress'] },\n editor: { label: 'Billing Address', category: 'commerce' },\n schema: {\n sameAsDeliveryLabel: {\n type: 'string',\n default: 'Billing address same as delivery address',\n label: 'Same-as-delivery toggle label',\n },\n defaultCountry: { type: 'string', default: 'BRA', label: 'Default country (ISO-3)' },\n countryLabel: { type: 'string', default: 'Country', label: 'Country field label' },\n submitLabel: { type: 'string', default: 'Save billing address', label: 'Submit button label' },\n submitPendingLabel: { type: 'string', default: 'Saving...', label: 'Submit button pending label' },\n requiredNoticeLabel: {\n type: 'string',\n default: 'This payment method requires a billing address.',\n label: 'Required-billing-address notice label',\n },\n unsavedAddressNoticeLabel: {\n type: 'string',\n default: 'No billing address has been saved yet — the delivery address will be used unless you save one below.',\n label: 'Unsaved-address notice label',\n },\n // T73 (specs/render-the-billing-match-disclaimer-beside.md) — requirement 12.20. Always on unless\n // switched off; gated on these two props alone, never on usePayment()/local state/$data (FR-4).\n // Both defaults are INLINE LITERALS (FR-7): the native catalog builder extracts `schema`\n // statically, so an identifier-valued default is dropped from `dist/catalog.json` with no error.\n billingMatchNoticeLabel: {\n type: 'string',\n default:\n 'Your billing address must match the one on your card or bank statement, or your bank may decline the order.',\n label: 'Billing-match disclaimer',\n },\n billingMatchNoticeVisible: { type: 'boolean', default: true, label: 'Show the billing-match disclaimer' },\n // T31 (specs/make-the-payment-components-authored-copy.md, FR-7, Decision 5) — the eight canonical\n // descriptor field names `FIELD_LABELS_EN` used to hardcode, now schema props defaulting to that\n // same map's own values. `recipientNameLabel` is not rendered by the custom-address form today\n // (OMITTED_FORM_FIELD_NAMES) — kept for parity with the other seven.\n recipientNameLabel: { type: 'string', default: \"Recipient's name\", label: 'Recipient name field label' },\n streetLabel: { type: 'string', default: 'Street', label: 'Street field label' },\n numberLabel: { type: 'string', default: 'Number', label: 'Number field label' },\n complementLabel: { type: 'string', default: 'Complement (optional)', label: 'Complement field label' },\n neighborhoodLabel: { type: 'string', default: 'Neighborhood', label: 'Neighborhood field label' },\n cityLabel: { type: 'string', default: 'City', label: 'City field label' },\n stateLabel: { type: 'string', default: 'State', label: 'State field label' },\n postalCodeLabel: { type: 'string', default: 'Postal code', label: 'Postal code field label' },\n // T38: an inline literal, never an identifier — the native catalog builder extracts `schema`\n // statically and silently drops an identifier-valued default (INV-8).\n fieldRequiredMessage: {\n type: 'string',\n default: 'This field is required.',\n label: 'Field required message',\n },\n disabled: { type: 'boolean', default: false, label: 'Disabled' },\n },\n guideline:\n \"Same-as-delivery toggle (default on) for the billing address; when off, mounts a country-specific address form (reusing shipping-address's country descriptors) and persists it via $actions.payment.setBillingAddress on an explicit submit control. Re-checking the toggle dispatches sameAsDelivery: true only once an address was actually persisted (otherwise it's a local no-op — there's nothing on the server to revert). Publishes only a boolean (never the address) onto the shared payment draft for usePlaceOrder to read. While the form is open and nothing has been saved yet, renders an inline status notice — the required attribute on the form's own fields is not a submit gate; Place Order can still complete billed to the delivery address if the shopper never saves. Must be inside a PaymentSectionRoot or PaymentSectionController; place as a sibling of CardEntry/SavedCards/Installments/SaveCard/PlaceOrder, never nested inside any of them. See billing-address.md.\",\n});\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\nexport function BillingAddress({\n sameAsDeliveryLabel = 'Billing address same as delivery address',\n defaultCountry = 'BRA',\n countryLabel = 'Country',\n submitLabel = 'Save billing address',\n submitPendingLabel = 'Saving...',\n requiredNoticeLabel = 'This payment method requires a billing address.',\n unsavedAddressNoticeLabel = 'No billing address has been saved yet — the delivery address will be used unless you save one below.',\n billingMatchNoticeLabel = 'Your billing address must match the one on your card or bank statement, or your bank may decline the order.',\n billingMatchNoticeVisible = true,\n recipientNameLabel = \"Recipient's name\",\n streetLabel = 'Street',\n numberLabel = 'Number',\n complementLabel = 'Complement (optional)',\n neighborhoodLabel = 'Neighborhood',\n cityLabel = 'City',\n stateLabel = 'State',\n postalCodeLabel = 'Postal code',\n fieldRequiredMessage,\n disabled = false,\n className,\n $actions: $actionsProp,\n ...props\n}: BillingAddressProps) {\n const $actions = useActionsOverride($actionsProp);\n // T31 (FR-7) — replaces the hardcoded `FIELD_LABELS_EN` map: keyed by the descriptor's own canonical\n // field name, same resolution as before (`renderableFields.map` below falls back to the descriptor's\n // own — Portuguese — label when a field name has no matching prop; none does today).\n const fieldLabels: Readonly<Record<string, string>> = {\n recipientName: recipientNameLabel,\n street: streetLabel,\n number: numberLabel,\n complement: complementLabel,\n neighborhood: neighborhoodLabel,\n city: cityLabel,\n state: stateLabel,\n postalCode: postalCodeLabel,\n };\n // Also enforces the PaymentSectionRoot/PaymentSectionController ancestor: usePayment() throws\n // when rendered outside one.\n const {\n draft,\n selectedMethod,\n setDraftBillingAddressDifferent,\n setDraftBillingAddressUnsaved,\n placing,\n cardPaymentCollapsed,\n } = usePayment();\n // Fix pass, findings R2/S2: a stable identity for *this* mounted instance, so its cleanup below\n // retracts only its own contribution to the shared `billingAddressUnsaved` fact — never a second\n // instance's still-open, still-unsaved one (billing-address.md documents multiple instances under one\n // root as allowed). `useId()` is stable for the component's lifetime and distinct per instance, which\n // is exactly what a per-instance set key needs; it is never rendered or sent anywhere.\n const instanceKey = useId();\n // T175 (Decision 3): the id the toggle's `aria-controls` names in every state, rendered on the\n // always-mounted panel below it. Its own `useId()`, so `instanceKey` above stays unrendered.\n const panelId = useId();\n\n const [formDraft, setFormDraft] = useState<BillingAddressFormDraft>(() => ({\n postalCode: '',\n // A `defaultCountry` with no descriptor (a store-editor typo) falls back to the first available\n // country rather than shipping a dead form with no fields and a permanently-disabled submit\n // (review F7) — `getAvailableCountries()` is never empty in practice (Decision 7), but the raw\n // prop is the last-resort fallback if it ever were, matching today's behavior for that edge case.\n country: getCountryDescriptor(defaultCountry) ? defaultCountry : (getAvailableCountries()[0] ?? defaultCountry),\n street: '',\n number: '',\n complement: '',\n neighborhood: '',\n city: '',\n state: '',\n }));\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<string | null>(null);\n // T38 (FR-6, Decision 8): blur-driven per-field validity, mirroring card-entry.tsx:1115/:1150.\n // Gated on this touched set so a field is never marked invalid before the shopper reaches it (US-3,\n // Risk R5) — wiring `invalid` off `isBillingAddressFormValid` alone would mark every field invalid\n // the instant the form opens.\n const [touched, setTouched] = useState<ReadonlySet<string>>(() => new Set());\n // Local-only: whether the custom-address form is showing. Decoupled from `draft.billingAddressDifferent`\n // on purpose (PR #544 N1) — that published flag is what `gateway.ts` reads to decide whose address to\n // bill, so it must say \"different\" only once an address has actually been persisted differently, never\n // merely because the shopper opened this form. Initialized from the draft (via the same pure\n // `resolveSameAsDelivery` the checkbox used to read directly) so the two never start out of sync.\n const [formOpen, setFormOpen] = useState(() => !resolveSameAsDelivery(draft.billingAddressDifferent));\n\n // Re-syncs `formOpen` whenever the shared draft's `billingAddressDifferent` changes for a reason\n // outside THIS instance's own clicks (PR #544 N8) — e.g. a sibling `BillingAddress` mounted under the\n // same `PaymentSectionRoot` persists or reverts an address through its own copy of this same shared\n // context. A bare local `useState` only ever reflects the instance that called its own setter; without\n // this effect, a second mounted instance keeps showing whatever `formOpen` it computed at its own\n // mount forever, even after `draft.billingAddressDifferent` — the one fact the gateway actually reads\n // — has moved on. Deliberately keyed on the primitive `draft.billingAddressDifferent`, not the `draft`\n // object itself: `draft` is a stable `useMemo` over seven facts (`use-payment-method-selection.ts`),\n // so its identity changes whenever ANY of them does — including `draftBin`, which `CardEntry`\n // publishes on every keystroke of the card number. Keying this effect on the whole object instead\n // would refire it on each of those keystrokes while this form is open and nothing is yet persisted,\n // recomputing `formOpen` from an unchanged `billingAddressDifferent` and closing the form mid-input\n // (review #544 N15 — a dedicated regression test in `billing-address.test.tsx` pins this). This\n // differs from `shipping-address.tsx:645`'s own snapshot-keyed effect, whose reason is Apollo\n // returning a new-but-equal object reference every render (its own `biome-ignore` comment says so) —\n // `draft` here is a local `useMemo`, already stable; the hazard is refiring on an unrelated fact\n // change, not on render churn. Does NOT reintroduce N1: this component's own uncheck transition (below) sets\n // `formOpen` without ever touching the draft, so that transition alone never changes this effect's\n // dependency and never fires it. Deleting this effect is exactly the mutant PR #544's re-verification\n // named V-P2/N8 — the two-instance regression test below fails without it.\n useEffect(() => {\n setFormOpen(!resolveSameAsDelivery(draft.billingAddressDifferent));\n }, [draft.billingAddressDifferent]);\n\n const sameAsDelivery = !formOpen;\n // PR #544 N7: visible only while the shopper is on the custom-address path AND nothing has actually\n // been persisted differently yet — the exact window in which Place Order would silently bill the\n // delivery address. Mirrors the same `draft.billingAddressDifferent !== true` check\n // `handleSameAsDeliveryChange`'s local-no-op branch already uses, so the notice and the no-op branch\n // can never disagree about what \"nothing persisted\" means. T191 (FR-8): never while the card payment\n // is collapsed, when this component renders nothing — the gate below must not refuse Place order over\n // a form the shopper cannot see, and the notice and the gate keep reading one expression.\n const unsavedAddressNoticeVisible = !cardPaymentCollapsed && formOpen && draft.billingAddressDifferent !== true;\n\n // T24's submit gate (specs/cardholder-document-submit-gate.md FR-6): publishes the EXACT same fact\n // the notice above renders from, so the gate and the notice can never disagree (Risk R5). Keyed by\n // this instance's own `instanceKey` (fix pass, findings R2/S2): `billingAddressUnsaved` is one fact\n // shared by every `BillingAddress` mounted under this root, so the cleanup must retract only *this*\n // instance's own contribution on unmount — never a second, still-open, still-unsaved instance's own\n // block (measured: an unconditional publish here left the CTA proceeding while that instance's own\n // notice kept rendering, billing the declined delivery address anyway). Not cosmetics either way: an\n // unmounted `BillingAddress` must not leave the CTA blocked with no form on screen left to fix it\n // (Risk R2) when it was the *only* instance.\n useEffect(() => {\n setDraftBillingAddressUnsaved(instanceKey, unsavedAddressNoticeVisible);\n return () => setDraftBillingAddressUnsaved(instanceKey, false);\n }, [instanceKey, unsavedAddressNoticeVisible, setDraftBillingAddressUnsaved]);\n\n const billingAddressRequired = selectedMethod?.validator.billingAddressRequired ?? false;\n const fieldsDisabled = disabled || pending;\n const requiredMessage = resolveAuthoredCopy(fieldRequiredMessage, 'This field is required.');\n const descriptor = getCountryDescriptor(formDraft.country);\n const renderableFields = (descriptor?.fields ?? []).filter((field) => !OMITTED_FORM_FIELD_NAMES.has(field.name));\n\n async function handleSameAsDeliveryChange(nextSameAsDelivery: boolean) {\n setError(null);\n\n if (!nextSameAsDelivery) {\n // Opens the custom-address form locally. Nothing is dispatched and the shared draft is left\n // untouched here — the shopper has typed nothing yet, and publishing `billingAddressDifferent:\n // true` before an address is actually persisted is exactly the false claim review #544's N1\n // measured reaching the gateway in two clicks. Only a successful submit (handleSubmit below) may\n // ever publish `true` — see this file's own top-level doc comment.\n setFormOpen(true);\n return;\n }\n\n if (draft.billingAddressDifferent !== true) {\n // Nothing was ever persisted differently — either the shopper never submitted, or an earlier\n // submit failed — so there is nothing on the server to revert. Closing the form is a local-only\n // action: dispatching `setBillingAddress({ sameAsDelivery: true })` here would be a wasted round\n // trip reverting an address the server was never asked to set in the first place.\n setFormOpen(false);\n return;\n }\n\n const setBillingAddress = $actions?.payment?.setBillingAddress;\n // Silent no-op when the action namespace is not injected, mirroring `SaveCard`'s / `selectMethod`'s\n // \"no-op (no error, no throw)\" convention.\n if (!setBillingAddress) return;\n\n setPending(true);\n try {\n const userErrors = await setBillingAddress({ sameAsDelivery: true });\n const [firstError] = userErrors;\n if (firstError) {\n setError(firstError.message);\n // A refused revert (review F3): the persisted address is still the custom one the shopper had\n // before, so the form stays open (`formOpen` untouched) and the draft keeps claiming\n // \"different\" — neither claims \"same as delivery\" the server never accepted. There is nothing\n // to roll back here, unlike before this pass: the draft is only ever written `false` a few\n // lines below, after `setBillingAddress` has already resolved with no error.\n } else {\n setDraftBillingAddressDifferent(false);\n // Redundant with the resync effect above once this draft write lands (the effect closes the\n // form on its own whenever `draft.billingAddressDifferent` moves to `false` — review #544 N18)\n // — kept anyway so this instance closes synchronously with its own click rather than waiting on\n // a re-render, which costs nothing since both agree on the outcome.\n setFormOpen(false);\n }\n } finally {\n setPending(false);\n }\n }\n\n function handleFieldChange(name: string, value: string) {\n setFormDraft((prev) => ({ ...prev, [name]: value }));\n }\n\n /** Marks a field touched on its first blur (T38, FR-6). Idempotent on every later blur — a `Set`\n * already containing `name` is returned unchanged, so this never triggers a redundant re-render. */\n function handleFieldBlur(name: string) {\n setTouched((prev) => (prev.has(name) ? prev : new Set(prev).add(name)));\n }\n\n /**\n * A rendered field is invalid once the shopper has touched it, it is blank, and it is not\n * `complement` (T38, FR-6, INV-3/INV-4). Mirrors `isBillingAddressFormValid`'s own per-field rule —\n * the two are never allowed to disagree about which fields are required, since both read\n * `OPTIONAL_FORM_FIELD_NAMES`.\n */\n function isFieldInvalid(name: string): boolean {\n return touched.has(name) && !OPTIONAL_FORM_FIELD_NAMES.has(name) && (formDraft[name] ?? '').trim().length === 0;\n }\n\n function handleCountryChange(value: string | null) {\n // Base UI's `SelectRoot` (`select/root/SelectRoot.js`) only ever calls `onValueChange` with one of\n // `Select.Item`'s own `value`s — all descriptor-backed, non-null ISO codes here (verified by\n // reading its source, not assumed) — so `value` is `null` on no path this component's own `Select`\n // below can reach. The `?? prev.country` fallback exists only to satisfy\n // `BillingAddressFormDraft.country: string`'s type, not as a runtime branch pretending to be tested\n // (review F10's residual, R5′): dropping the fallback (`country: value`) is a `pnpm typecheck`\n // failure, not merely an unproven `if` — a stronger, mechanical guarantee than a guard clause no\n // real interaction could ever exercise.\n //\n // Preserves every other field's value across the switch (Decision 7) — matches\n // `shipping-address`'s own documented behavior: a value typed under one country resurfaces if the\n // shopper switches away and back, even while the field is hidden for the currently-selected one.\n setFormDraft((prev) => ({ ...prev, country: value ?? prev.country }));\n }\n\n async function handleSubmit() {\n const setBillingAddress = $actions?.payment?.setBillingAddress;\n setError(null);\n if (!setBillingAddress) return;\n\n // Only include a field the active country's descriptor actually renders (mirrors\n // `shipping-address.tsx`'s own `isFieldActive` guard at its submit handler) — otherwise a value\n // typed under a previously-selected country (e.g. `neighborhood` retained in the draft after\n // switching BR -> USA) would leak into the submitted payload even though the field was never\n // rendered, editable, or clearable for the country actually being submitted (review F2).\n // `postalCode` and `country` are unconditional: every descriptor declares both.\n const isFieldActive = (name: string) => renderableFields.some((field) => field.name === name);\n\n setPending(true);\n try {\n const userErrors = await setBillingAddress({\n sameAsDelivery: false,\n postalCode: formDraft.postalCode,\n country: formDraft.country,\n ...(isFieldActive('street') ? { street: formDraft.street } : {}),\n ...(isFieldActive('number') ? { number: formDraft.number } : {}),\n ...(isFieldActive('complement') ? { complement: formDraft.complement } : {}),\n ...(isFieldActive('neighborhood') ? { neighborhood: formDraft.neighborhood } : {}),\n ...(isFieldActive('city') ? { city: formDraft.city } : {}),\n ...(isFieldActive('state') ? { state: formDraft.state } : {}),\n });\n const [firstError] = userErrors;\n if (firstError) {\n setError(firstError.message);\n } else {\n // The one place `billingAddressDifferent` may ever become `true` (PR #544 N1) — only once the\n // server actually holds a different address, so the flag means exactly what `gateway.ts`'s own\n // doc comment already claims: \"`setBillingAddress` already persisted it … so the gateway reads\n // it from there.\"\n setDraftBillingAddressDifferent(true);\n }\n } finally {\n setPending(false);\n }\n }\n\n // T191 (FR-7, Decision 3): nothing renders while the card payment is collapsed. Returned after the\n // last hook, so this instance stays mounted and keeps what the shopper typed for when the card is back.\n if (cardPaymentCollapsed) return null;\n\n return (\n <div\n {...props}\n className={className}\n inert={placing}\n aria-disabled={placing || undefined}\n data-fs-billing-address=\"\"\n {...(placing ? { 'data-fs-billing-address-placing': '' } : {})}\n {...(pending ? { 'data-fs-billing-address-pending': '' } : {})}\n {...(sameAsDelivery ? { 'data-fs-billing-address-same-as-delivery': '' } : {})}\n >\n {billingMatchNoticeVisible && billingMatchNoticeLabel.trim() ? (\n <p data-fs-billing-address-match-notice=\"\">{billingMatchNoticeLabel}</p>\n ) : null}\n <div data-fs-billing-address-toggle-field=\"\">\n <Field.Root disabled={fieldsDisabled} data-fs-field-root=\"\">\n <Field.Label>\n <Checkbox.Root\n checked={sameAsDelivery}\n onCheckedChange={handleSameAsDeliveryChange}\n disabled={fieldsDisabled}\n aria-controls={panelId}\n >\n <Checkbox.Indicator>\n <Icon name=\"checked\" />\n </Checkbox.Indicator>\n </Checkbox.Root>\n {sameAsDeliveryLabel}\n </Field.Label>\n </Field.Root>\n {billingAddressRequired ? <p data-fs-billing-address-required-note=\"\">{requiredNoticeLabel}</p> : null}\n </div>\n\n {/* T175 (Decision 3): always mounted, so the toggle's `aria-controls` resolves in every state. It\n takes no className or props, and it is `hidden` only while empty (INV-2): a store's display\n utility can beat `[hidden]`, but then it shows an empty box, never content. */}\n <div id={panelId} data-fs-billing-address-panel=\"\" hidden={sameAsDelivery}>\n {sameAsDelivery ? null : (\n <div data-fs-billing-address-form=\"\">\n {unsavedAddressNoticeVisible ? (\n <p role=\"status\" data-fs-billing-address-unsaved-notice=\"\">\n {unsavedAddressNoticeLabel}\n </p>\n ) : null}\n <Field.Root disabled={fieldsDisabled} data-fs-field-root=\"\" data-fs-billing-address-field=\"country\">\n <Field.Label>{countryLabel}</Field.Label>\n <Select.Root\n value={formDraft.country}\n onValueChange={handleCountryChange}\n disabled={fieldsDisabled}\n required\n >\n <Select.Trigger>\n {/* Resolves the label directly from the country descriptor rather than relying on\n Base UI's automatic item-label lookup, which only reflects a mounted Select.Item —\n mirrors `ShippingAddressCountryControl`'s identical reasoning. English override per\n `getCountryLabel` (review F5) — the descriptor's own label is Portuguese. */}\n <Select.Value>{(value) => (value ? getCountryLabel(value) : value)}</Select.Value>\n <Select.Icon>▾</Select.Icon>\n </Select.Trigger>\n <Select.Portal>\n <Select.Positioner>\n <Select.Popup>\n <Select.List>\n {getAvailableCountries().map((iso) => {\n const label = getCountryLabel(iso);\n return (\n <Select.Item key={iso} value={iso} label={label}>\n {label}\n </Select.Item>\n );\n })}\n </Select.List>\n </Select.Popup>\n </Select.Positioner>\n </Select.Portal>\n </Select.Root>\n </Field.Root>\n\n {renderableFields.map((field) => {\n // T38 (FR-6): blur-driven, gated on `touched` so nothing is marked invalid before the\n // shopper reaches it (US-3, Risk R5) — see `isFieldInvalid`'s own doc comment.\n const invalid = isFieldInvalid(field.name);\n return (\n <Field.Root\n key={field.name}\n disabled={fieldsDisabled}\n invalid={invalid}\n data-fs-field-root=\"\"\n data-fs-billing-address-field={field.name}\n >\n {/* Authored field-label prop per `fieldLabels` (T31, FR-7) — the descriptor's own label\n is Portuguese for every country, including its own USA entries. `||`, not `??`\n (fix pass round 2, finding N1 — same defect class as place-order.tsx's finding 7 from\n round 1): a store author can write `\"\"` for a field's authored label — a valid\n `type: 'string'` prop value — and `??` only falls through on `null`/`undefined`, so an\n authored empty string would render an empty accessible name on a control that is\n required for every field but `complement`. Pre-existing hazard, not fixed here: `||`\n still renders blank for a whitespace-only authored label — inside T31's ownership,\n reported not fixed (T38 § 5). */}\n <Field.Label>{fieldLabels[field.name] || field.label}</Field.Label>\n <Field.Control\n value={formDraft[field.name] ?? ''}\n onChange={(event) => handleFieldChange(field.name, event.currentTarget.value)}\n onBlur={() => handleFieldBlur(field.name)}\n disabled={fieldsDisabled}\n // Every rendered field is required once \"different\" is selected, except `complement`\n // (`OPTIONAL_FORM_FIELD_NAMES`) — PR #544 N1 item 1. Not a like-for-like precedent with\n // `CardholderDocument`'s own `required={documentRequired}`: that one forwards to a\n // particle with its own declared prop, a root state hook, and a paired `Field.Error`\n // driven by a server-sourced validator flag, where this sets `required` directly off a\n // locally hardcoded set instead (full comparison: specs/billing-address.md Accessibility\n // Contract).\n required={!OPTIONAL_FORM_FIELD_NAMES.has(field.name)}\n />\n {invalid ? <Field.Error match>{requiredMessage}</Field.Error> : null}\n </Field.Root>\n );\n })}\n\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={fieldsDisabled || !isBillingAddressFormValid(formDraft)}\n data-fs-billing-address-submit=\"\"\n >\n {pending ? submitPendingLabel : submitLabel}\n </button>\n </div>\n )}\n </div>\n\n {error ? (\n <div role=\"alert\" data-fs-billing-address-error=\"\">\n {error}\n </div>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import "../../sdk/payment/submission-registry.js";
2
+ //#region src/matter/card-entry/card-capture.d.ts
3
+ /**
4
+ * The `card-capture` context (specs/sfs-3457-card-entry-card-capture-context.md): everything
5
+ * `CardEntry` needs from whichever surface composes it. The fields own the card values; the provider
6
+ * owns the accepted networks, when a capture runs, and where the card goes. This is the PCI context:
7
+ * `CardEntry` is its only consumer.
8
+ */
9
+ export type CardCaptureValidator = Readonly<{
10
+ numberMask: string | null;
11
+ numberRegex: string | null;
12
+ cvvMask: string | null;
13
+ cvvRegex: string | null;
14
+ cvvRequired: boolean;
15
+ expirationRequired: boolean;
16
+ holderNameRequired: boolean;
17
+ }>;
18
+ /** One accepted network and its validation rules. `PaymentSectionMethod` is assignable to it. */
19
+ export type CardCaptureNetwork = Readonly<{
20
+ id: string;
21
+ name: string;
22
+ validator: CardCaptureValidator;
23
+ }>;
24
+ //#endregion
25
+ //# sourceMappingURL=card-capture.d.ts.map
@@ -0,0 +1,25 @@
1
+ "use client";
2
+ import { createContext, useContext, useEffect } from "react";
3
+ //#region src/matter/card-entry/card-capture.tsx
4
+ const CARD_CAPTURE_CONTEXT_UNSET = Symbol("CardCaptureContextUnset");
5
+ const CardCaptureContextInternal = createContext(CARD_CAPTURE_CONTEXT_UNSET);
6
+ const CardCaptureContext = CardCaptureContextInternal;
7
+ /** Throws outside a provider, mirroring `usePayment()`: a misplaced `CardEntry` is a composition bug. */
8
+ function useCardCapture() {
9
+ const ctx = useContext(CardCaptureContextInternal);
10
+ if (ctx === CARD_CAPTURE_CONTEXT_UNSET) throw new Error("useCardCapture() must be used inside a card-capture provider (PaymentSectionRoot, PaymentSectionController).");
11
+ return ctx;
12
+ }
13
+ /** Registers on mount and on any argument change, so the provider always holds the latest closure. */
14
+ function useRegisterCardCapture(capture, preflight) {
15
+ const { registerCapture } = useCardCapture();
16
+ useEffect(() => registerCapture(capture, preflight), [
17
+ registerCapture,
18
+ capture,
19
+ preflight
20
+ ]);
21
+ }
22
+ //#endregion
23
+ export { CardCaptureContext, useCardCapture, useRegisterCardCapture };
24
+
25
+ //# sourceMappingURL=card-capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"card-capture.js","names":[],"sources":["../../../src/matter/card-entry/card-capture.tsx"],"sourcesContent":["'use client';\n\nimport { createContext, useContext, useEffect } from 'react';\nimport type { PaymentSubmissionPath } from '../../sdk/payment/submission-registry.js';\n\n/**\n * The `card-capture` context (specs/sfs-3457-card-entry-card-capture-context.md): everything\n * `CardEntry` needs from whichever surface composes it. The fields own the card values; the provider\n * owns the accepted networks, when a capture runs, and where the card goes. This is the PCI context:\n * `CardEntry` is its only consumer.\n */\n\nexport type CardCaptureValidator = Readonly<{\n numberMask: string | null;\n numberRegex: string | null;\n cvvMask: string | null;\n cvvRegex: string | null;\n cvvRequired: boolean;\n expirationRequired: boolean;\n holderNameRequired: boolean;\n}>;\n\n/** One accepted network and its validation rules. `PaymentSectionMethod` is assignable to it. */\nexport type CardCaptureNetwork = Readonly<{\n id: string;\n name: string;\n validator: CardCaptureValidator;\n}>;\n\n/** The card as the fields hold it. Exists only as the argument of one `send` call. */\nexport type CapturedCard = Readonly<{\n number: string;\n securityCode: string;\n expirationDate: string;\n holderName: string;\n}>;\n\nexport type CardCaptureError = Readonly<{ code: string; message: string }>;\n\nexport type CardCaptureTarget = (card: CapturedCard) => Promise<readonly CardCaptureError[]>;\n\n/** Registered by `CardEntry`; the provider calls it with the target for this one submission. */\nexport type CardCapture = (send: CardCaptureTarget) => Promise<readonly CardCaptureError[]>;\n\n/** Synchronous, I/O-free verdict on the draft, with the same contract as `PaymentPreflightCheck`. */\nexport type CardCapturePreflight = () => readonly CardCaptureError[];\n\n/** The checkout facts `CardEntry` reacts to. Absent outside the checkout. */\nexport type CardCaptureSelection = Readonly<{\n selectMethod: (input: { methodId: string; bin: string }) => void;\n selectNewCard: () => void;\n setDraftBin: (bin: string | null) => void;\n path: PaymentSubmissionPath | null;\n bin: string | null;\n collapsed: boolean;\n cardModeActive: boolean;\n selectedMethodId: string | null;\n selectionOutOfScope: boolean;\n selectionNonCard: boolean;\n}>;\n\nexport type CardCaptureContextValue = Readonly<{\n networks: readonly CardCaptureNetwork[];\n busy: boolean;\n /** When `CardEntry` clears its fields after the target settles. A pre-flight refusal never clears. */\n clearOn: 'success' | 'any-outcome';\n /** Registers the capture and its pre-flight check. Returns the unregister function. */\n registerCapture: (capture: CardCapture, preflight: CardCapturePreflight) => () => void;\n /** Called with the resolved network's id, or `null`, whenever that resolution changes. */\n onNetworkChange?: (networkId: string | null) => void;\n selection?: CardCaptureSelection;\n}>;\n\nconst CARD_CAPTURE_CONTEXT_UNSET = Symbol('CardCaptureContextUnset');\n\nconst CardCaptureContextInternal = createContext<CardCaptureContextValue | typeof CARD_CAPTURE_CONTEXT_UNSET>(\n CARD_CAPTURE_CONTEXT_UNSET,\n);\n\nexport const CardCaptureContext = CardCaptureContextInternal as React.Context<CardCaptureContextValue>;\n\n/** Throws outside a provider, mirroring `usePayment()`: a misplaced `CardEntry` is a composition bug. */\nexport function useCardCapture(): CardCaptureContextValue {\n const ctx = useContext(CardCaptureContextInternal);\n if (ctx === CARD_CAPTURE_CONTEXT_UNSET) {\n throw new Error(\n 'useCardCapture() must be used inside a card-capture provider (PaymentSectionRoot, PaymentSectionController).',\n );\n }\n return ctx;\n}\n\n/** Registers on mount and on any argument change, so the provider always holds the latest closure. */\nexport function useRegisterCardCapture(capture: CardCapture, preflight: CardCapturePreflight): void {\n const { registerCapture } = useCardCapture();\n\n useEffect(() => registerCapture(capture, preflight), [registerCapture, capture, preflight]);\n}\n"],"mappings":";;;AAyEA,MAAM,6BAA6B,OAAO,yBAAyB;AAEnE,MAAM,6BAA6B,cACjC,0BACF;AAEA,MAAa,qBAAqB;;AAGlC,SAAgB,iBAA0C;CACxD,MAAM,MAAM,WAAW,0BAA0B;CACjD,IAAI,QAAQ,4BACV,MAAM,IAAI,MACR,8GACF;CAEF,OAAO;AACT;;AAGA,SAAgB,uBAAuB,SAAsB,WAAuC;CAClG,MAAM,EAAE,oBAAoB,eAAe;CAE3C,gBAAgB,gBAAgB,SAAS,SAAS,GAAG;EAAC;EAAiB;EAAS;CAAS,CAAC;AAC5F"}
@@ -0,0 +1,120 @@
1
+ import { ComponentDefinition } from "../../core/dist/define-component.js";
2
+ import "../../core/dist/index.js";
3
+ import "../../sdk/payment/submission-registry.js";
4
+ import { CardCaptureNetwork } from "./card-capture.js";
5
+ //#region src/matter/card-entry/card-entry.d.ts
6
+ /**
7
+ * The new-card entry matter (specs/card-entry.md, T7 of the Credit Card epic;
8
+ * docs/features/credit-card/rfc.md § D2). Composes the `Field`/`Fieldset` particles and reuses
9
+ * `document-field`'s `applyMask` progressive mask engine to capture a card number, holder name,
10
+ * expiration, and CVV, with masks/required-ness driven by the network's server-driven validator
11
+ * config (T1) rather than hardcoded per network.
12
+ *
13
+ * This is the PCI-critical core of the credit-card feature: it is the **only** component that ever
14
+ * holds a full card number or CVV, and both live only in this component's local state — never a
15
+ * schema prop, a fragment field, an action payload, or a value handed to another component. It reads
16
+ * only the `card-capture` context (card-capture.tsx), so the checkout and the account compose it
17
+ * alike: it registers a capture, builds the card *inside* that closure, and hands it to the target
18
+ * the provider passes in — `sendPayments` at checkout, `savedCard.tokenize` in the account.
19
+ *
20
+ * Network detection is a pure, local computation (never waits on a server round-trip): the typed
21
+ * digits are tested against every method's `validator.numberRegex` on every keystroke, first match
22
+ * wins. The selection waits for a complete BIN (T190): only once the field holds eight digits does a
23
+ * match — on a genuine transition, or once the engine has released the BIN (T174) — publish those
24
+ * eight digits as the BIN (`setDraftBin`) and dispatch `selectMethod` to persist the selection
25
+ * server-side — both through `card-capture`'s `selection` member, so only at checkout. Below eight digits nothing is published or sent — see specs/card-entry.md
26
+ * Decisions 1 and 3, and specs/send-the-typed-bin-to-the.md, which adds the gate — except that the
27
+ * first keystroke off the new-card path records that path locally with `selectNewCard()`
28
+ * (specs/collapse-the-card-form-when-a.md, FR-10).
29
+ */
30
+ export type CardEntryProps = {
31
+ /** `Fieldset.Legend` text. */
32
+ legend?: string;
33
+ numberLabel?: string;
34
+ numberPlaceholder?: string;
35
+ holderNameLabel?: string;
36
+ holderNamePlaceholder?: string;
37
+ expirationLabel?: string;
38
+ expirationPlaceholder?: string;
39
+ cvvLabel?: string;
40
+ cvvPlaceholder?: string;
41
+ /** Helper copy under the CVV field, rendered through `Field.Description` and linked to the
42
+ * input via `aria-describedby`. An empty string renders nothing. Copy configuration only —
43
+ * never the security code itself. */
44
+ cvvDescription?: string;
45
+ /** Shown on the number field when it is left blank. */
46
+ numberRequiredMessage?: string;
47
+ /** Shown on the number field for a Luhn-invalid PAN, or a Luhn-valid one that matches no
48
+ * configured method (FR-3 — the same string covers both sites). */
49
+ numberInvalidMessage?: string;
50
+ /** Shown on the holder-name field when it is left blank and the detected method requires it. */
51
+ holderNameRequiredMessage?: string;
52
+ /** Shown on the expiration field when it is left blank and the detected method requires it. */
53
+ expirationRequiredMessage?: string;
54
+ /** Shown on the expiration field for a structurally malformed `MM/YY`. */
55
+ expirationInvalidMessage?: string;
56
+ /** Shown on the expiration field for a structurally valid `MM/YY` that has already passed. */
57
+ expirationExpiredMessage?: string;
58
+ /** Shown on the CVV field when it is left blank and the detected method requires it. */
59
+ cvvRequiredMessage?: string;
60
+ /** Shown on the CVV field for a value that fails the detected method's format. */
61
+ cvvInvalidMessage?: string;
62
+ /** Disables every field. */
63
+ disabled?: boolean;
64
+ className?: string;
65
+ };
66
+ /**
67
+ * First method whose `validator.numberRegex` admits `digits` as a BIN-level prefix, or `null`.
68
+ *
69
+ * N1: relaxing every store regex into a pure prefix test (F1, above) means two networks whose fixed
70
+ * prefixes *nest* — one network's whole discriminating prefix is itself a prefix of a different,
71
+ * longer network's real PAN — both keep "relax-matching" once the shorter network's fixed prefix is
72
+ * satisfied, including at the longer network's own full, syntactically valid length, where the exact,
73
+ * unrelaxed regex alone correctly discriminates by length. Left as pure first-match-wins over the
74
+ * relaxed test, the answer at full PAN length silently depends on which order the store's `methods`
75
+ * happen to arrive in — something this component does not control.
76
+ *
77
+ * The fix tries the **exact** regex first, across every method in `methods` order, and only falls back
78
+ * to the relaxed (prefix) match for input too short to satisfy any exact pattern yet. This restores the
79
+ * exact regex's authority the instant a full PAN is present, which resolves the nesting-prefix case
80
+ * above deterministically regardless of order. **It is not, however, order-independent in general**
81
+ * (T22, V2): two configured networks' exact patterns can both match the very same full-length input —
82
+ * e.g. a real Visa regex and an Elo BIN range both match `4011788888888888` (Elo is issued on
83
+ * Visa-range BINs) — and `exactMatch` below is a plain `methods.find(...)`, so **the first exact match
84
+ * in `methods` order wins the tie**. This is pre-existing (pre-N1 code used the same `find` over exact
85
+ * regexes) and is not introduced or fixed by N1; N1 only shrank how much of the input space stays
86
+ * order-dependent. A caller that needs a specific tie winner must order `methods` by specificity itself
87
+ * before calling — this function does not infer specificity from a regex. See also `resolveNumberMask`
88
+ * below: this function alone cannot help if the field's own mask capacity has already been capped
89
+ * narrower than the digit count an exact match needs to resolve, and see `resolveNetworkMethod` below
90
+ * `resolveNumberMask` (T22, V3/V4) for this component's own order-independent wrapper around the
91
+ * *relaxed*-fallback branch, which this component's own call sites use instead of calling this function
92
+ * directly.
93
+ */
94
+ export declare function detectNetworkMethod(methods: readonly CardCaptureNetwork[], digits: string): CardCaptureNetwork | null;
95
+ /**
96
+ * Standard Luhn checksum. Rejects non-digit strings and anything shorter than 12 digits — no real
97
+ * card network issues a PAN shorter than that.
98
+ */
99
+ export declare function isValidLuhn(digits: string): boolean;
100
+ /** `MM/YY` structural check: month 01–12, two-digit year. */
101
+ export declare function isExpirationFormatValid(value: string): boolean;
102
+ /**
103
+ * True when the printed `MM/YY` has not yet ended — cards are valid through the end of their
104
+ * printed month. `now` is injectable for deterministic tests.
105
+ */
106
+ export declare function isExpirationInFuture(value: string, now?: Date): boolean;
107
+ export declare const CARD_ENTRY_INVALID_DATA_CODE = "INVALID_CARD_DATA";
108
+ /**
109
+ * The submission and pre-flight refusal for a card form the shopper cannot currently see (T74,
110
+ * Decision 5), and for a visible form's valid card under a persisted method that cannot take it: one
111
+ * this section does not serve, one that is not a card (T191, FR-13), or another card network than the
112
+ * typed number's (T191 fix round 1, finding R1). Distinct from `INVALID_CARD_DATA`: the card may be
113
+ * perfectly valid — the section is simply not on a method this form can pay it with. A fourth
114
+ * client-synthesized `CardCaptureError` in `matter/`, beside `CARD_ENTRY_INVALID_DATA_CODE`.
115
+ */
116
+ export declare const CARD_ENTRY_COLLAPSED_CODE = "CARD_ENTRY_COLLAPSED";
117
+ export declare const CardEntryDefinition: ComponentDefinition<CardEntryProps>;
118
+ export declare function CardEntry({ legend, numberLabel, numberPlaceholder, holderNameLabel, holderNamePlaceholder, expirationLabel, expirationPlaceholder, cvvLabel, cvvPlaceholder, cvvDescription, numberRequiredMessage, numberInvalidMessage, holderNameRequiredMessage, expirationRequiredMessage, expirationInvalidMessage, expirationExpiredMessage, cvvRequiredMessage, cvvInvalidMessage, disabled, className }: CardEntryProps): import("react/jsx-runtime").JSX.Element;
119
+ //#endregion
120
+ //# sourceMappingURL=card-entry.d.ts.map