@vtex/faststore 0.0.5 → 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 (232) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/actions.json +181 -0
  3. package/dist/catalog.json +1543 -495
  4. package/dist/client.d.ts +2 -1
  5. package/dist/client.js +2 -1
  6. package/dist/components.d.ts +28 -7
  7. package/dist/components.js +31 -12
  8. package/dist/docs/access-code-sign-in.md +1 -1
  9. package/dist/docs/address-summary.md +24 -3
  10. package/dist/docs/alert-dialog.md +204 -0
  11. package/dist/docs/billing-address.md +284 -0
  12. package/dist/docs/card-entry.md +250 -0
  13. package/dist/docs/cardholder-document.md +142 -0
  14. package/dist/docs/client-preferences-fieldset.md +148 -0
  15. package/dist/docs/client-preferences.md +2 -1
  16. package/dist/docs/client-profile-fieldset.md +184 -0
  17. package/dist/docs/client-profile.md +1 -1
  18. package/dist/docs/document-field.md +58 -4
  19. package/dist/docs/field.md +317 -0
  20. package/dist/docs/form.md +307 -0
  21. package/dist/docs/identity-edit.md +6 -6
  22. package/dist/docs/identity-summary.md +5 -5
  23. package/dist/docs/installments.md +248 -0
  24. package/dist/docs/my-account-profile.md +1 -0
  25. package/dist/docs/order-placed-payment-summary.md +138 -0
  26. package/dist/docs/payment-method-badges.md +232 -0
  27. package/dist/docs/payment-methods.md +1 -0
  28. package/dist/docs/payment.md +218 -0
  29. package/dist/docs/phone-field.md +58 -4
  30. package/dist/docs/place-order.md +157 -0
  31. package/dist/docs/save-card.md +186 -0
  32. package/dist/docs/saved-cards.md +218 -0
  33. package/dist/docs/select.md +465 -0
  34. package/dist/docs/shipping-address.md +110 -258
  35. package/dist/index.d.ts +4 -1
  36. package/dist/internal/client.d.ts +3 -1
  37. package/dist/internal/client.js +3 -1
  38. package/dist/matter/access-code-sign-in/access-code-sign-in.js +9 -8
  39. package/dist/matter/access-code-sign-in/access-code-sign-in.js.map +1 -1
  40. package/dist/matter/billing-address/billing-address.d.ts +165 -0
  41. package/dist/matter/billing-address/billing-address.js +408 -0
  42. package/dist/matter/billing-address/billing-address.js.map +1 -0
  43. package/dist/matter/card-entry/card-capture.d.ts +25 -0
  44. package/dist/matter/card-entry/card-capture.js +25 -0
  45. package/dist/matter/card-entry/card-capture.js.map +1 -0
  46. package/dist/matter/card-entry/card-entry.d.ts +120 -0
  47. package/dist/matter/card-entry/card-entry.js +832 -0
  48. package/dist/matter/card-entry/card-entry.js.map +1 -0
  49. package/dist/matter/cardholder-document/cardholder-document.d.ts +45 -0
  50. package/dist/matter/cardholder-document/cardholder-document.js +70 -0
  51. package/dist/matter/cardholder-document/cardholder-document.js.map +1 -0
  52. package/dist/matter/client-preferences/client-preferences-fieldset.d.ts +77 -0
  53. package/dist/matter/client-preferences/client-preferences-fieldset.js +299 -0
  54. package/dist/matter/client-preferences/client-preferences-fieldset.js.map +1 -0
  55. package/dist/matter/client-preferences/client-preferences.d.ts +3 -5
  56. package/dist/matter/client-preferences/client-preferences.js +6 -6
  57. package/dist/matter/client-preferences/client-preferences.js.map +1 -1
  58. package/dist/matter/client-profile/client-profile.d.ts +4 -6
  59. package/dist/matter/client-profile/client-profile.js +9 -6
  60. package/dist/matter/client-profile/client-profile.js.map +1 -1
  61. package/dist/matter/client-profile-form/client-profile-fieldset.d.ts +119 -0
  62. package/dist/matter/client-profile-form/client-profile-fieldset.js +488 -0
  63. package/dist/matter/client-profile-form/client-profile-fieldset.js.map +1 -0
  64. package/dist/matter/identity-edit/identity-edit.js +2 -2
  65. package/dist/matter/identity-edit/identity-edit.js.map +1 -1
  66. package/dist/matter/identity-summary/identity-summary.js +2 -2
  67. package/dist/matter/identity-summary/identity-summary.js.map +1 -1
  68. package/dist/matter/installments/installments-select.d.ts +42 -0
  69. package/dist/matter/installments/installments-select.js +167 -0
  70. package/dist/matter/installments/installments-select.js.map +1 -0
  71. package/dist/matter/installments/installments.d.ts +58 -0
  72. package/dist/matter/installments/installments.js +176 -0
  73. package/dist/matter/installments/installments.js.map +1 -0
  74. package/dist/matter/installments/use-installments-selection.d.ts +29 -0
  75. package/dist/matter/installments/use-installments-selection.js +95 -0
  76. package/dist/matter/installments/use-installments-selection.js.map +1 -0
  77. package/dist/matter/my-account-profile/my-account-profile.js +4 -4
  78. package/dist/matter/my-account-profile/my-account-profile.js.map +1 -1
  79. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.d.ts +63 -0
  80. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js +305 -0
  81. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js.map +1 -0
  82. package/dist/matter/payment/payment-card-capture.d.ts +19 -0
  83. package/dist/matter/payment/payment-card-capture.js +129 -0
  84. package/dist/matter/payment/payment-card-capture.js.map +1 -0
  85. package/dist/matter/payment/payment-controller.d.ts +24 -0
  86. package/dist/matter/payment/payment-controller.js +107 -0
  87. package/dist/matter/payment/payment-controller.js.map +1 -0
  88. package/dist/matter/payment/payment-method-badges.d.ts +57 -0
  89. package/dist/matter/payment/payment-method-badges.js +192 -0
  90. package/dist/matter/payment/payment-method-badges.js.map +1 -0
  91. package/dist/matter/payment/payment.d.ts +89 -0
  92. package/dist/matter/payment/payment.js +263 -0
  93. package/dist/matter/payment/payment.js.map +1 -0
  94. package/dist/matter/payment/use-payment-method-selection.d.ts +302 -0
  95. package/dist/matter/payment/use-payment-method-selection.js +458 -0
  96. package/dist/matter/payment/use-payment-method-selection.js.map +1 -0
  97. package/dist/matter/payment/use-payment-submit-gate.d.ts +111 -0
  98. package/dist/matter/payment/use-payment-submit-gate.js +144 -0
  99. package/dist/matter/payment/use-payment-submit-gate.js.map +1 -0
  100. package/dist/matter/place-order/place-order.d.ts +74 -0
  101. package/dist/matter/place-order/place-order.js +379 -0
  102. package/dist/matter/place-order/place-order.js.map +1 -0
  103. package/dist/matter/product-list/product-list.d.ts +1 -0
  104. package/dist/matter/product-list/product-list.js.map +1 -1
  105. package/dist/matter/product-shelf/product-shelf.d.ts +1 -0
  106. package/dist/matter/product-shelf/product-shelf.js +2 -0
  107. package/dist/matter/product-shelf/product-shelf.js.map +1 -1
  108. package/dist/matter/region/region.js +1 -1
  109. package/dist/matter/region/region.js.map +1 -1
  110. package/dist/matter/save-card/save-card.d.ts +49 -0
  111. package/dist/matter/save-card/save-card.js +188 -0
  112. package/dist/matter/save-card/save-card.js.map +1 -0
  113. package/dist/matter/saved-cards/saved-cards.d.ts +79 -0
  114. package/dist/matter/saved-cards/saved-cards.js +387 -0
  115. package/dist/matter/saved-cards/saved-cards.js.map +1 -0
  116. package/dist/matter/shipping-address/country-descriptors.js +20 -10
  117. package/dist/matter/shipping-address/country-descriptors.js.map +1 -1
  118. package/dist/matter/shipping-address/shipping-address.d.ts +113 -174
  119. package/dist/matter/shipping-address/shipping-address.js +480 -791
  120. package/dist/matter/shipping-address/shipping-address.js.map +1 -1
  121. package/dist/matter/variant-selector/variant-selector.js +1 -1
  122. package/dist/particles/address-summary/address-summary.d.ts +24 -0
  123. package/dist/particles/address-summary/address-summary.js +83 -2
  124. package/dist/particles/address-summary/address-summary.js.map +1 -1
  125. package/dist/particles/alert-dialog/alert-dialog.d.ts +68 -0
  126. package/dist/particles/alert-dialog/alert-dialog.js +187 -0
  127. package/dist/particles/alert-dialog/alert-dialog.js.map +1 -0
  128. package/dist/particles/checkbox/checkbox.d.ts +7 -3
  129. package/dist/particles/checkbox/checkbox.js +4 -1
  130. package/dist/particles/checkbox/checkbox.js.map +1 -1
  131. package/dist/particles/document-field/document-field.d.ts +48 -5
  132. package/dist/particles/document-field/document-field.js +189 -32
  133. package/dist/particles/document-field/document-field.js.map +1 -1
  134. package/dist/particles/field/bound-field-registry.js +52 -0
  135. package/dist/particles/field/bound-field-registry.js.map +1 -0
  136. package/dist/particles/field/field.d.ts +116 -0
  137. package/dist/particles/field/field.js +497 -6
  138. package/dist/particles/field/field.js.map +1 -1
  139. package/dist/particles/fieldset/fieldset.js +2 -2
  140. package/dist/particles/fieldset/fieldset.js.map +1 -1
  141. package/dist/particles/form/form.d.ts +104 -0
  142. package/dist/particles/form/form.js +485 -0
  143. package/dist/particles/form/form.js.map +1 -0
  144. package/dist/particles/phone-field/phone-field.d.ts +38 -10
  145. package/dist/particles/phone-field/phone-field.js +183 -39
  146. package/dist/particles/phone-field/phone-field.js.map +1 -1
  147. package/dist/particles/postal-code-field/postal-code-field.js +2 -2
  148. package/dist/particles/postal-code-field/postal-code-field.js.map +1 -1
  149. package/dist/particles/select/field-select.d.ts +43 -0
  150. package/dist/particles/select/field-select.js +150 -0
  151. package/dist/particles/select/field-select.js.map +1 -0
  152. package/dist/particles/select/select.d.ts +2 -0
  153. package/dist/particles/select/select.js.map +1 -1
  154. package/dist/particles/shared/compose-refs.js +21 -0
  155. package/dist/particles/shared/compose-refs.js.map +1 -0
  156. package/dist/schema.graphql +701 -2
  157. package/dist/sdk/actions/analytics-action-provider.d.ts +15 -0
  158. package/dist/sdk/actions/analytics-action-provider.js +19 -0
  159. package/dist/sdk/actions/analytics-action-provider.js.map +1 -0
  160. package/dist/sdk/actions/index.d.ts +3 -1
  161. package/dist/sdk/actions/index.js +3 -1
  162. package/dist/sdk/actions/payment-action-provider.d.ts +22 -0
  163. package/dist/sdk/actions/payment-action-provider.js +26 -0
  164. package/dist/sdk/actions/payment-action-provider.js.map +1 -0
  165. package/dist/sdk/actions/types.d.ts +6 -2
  166. package/dist/sdk/analytics/analytics-destination.d.ts +43 -0
  167. package/dist/sdk/analytics/analytics-destination.js +90 -0
  168. package/dist/sdk/analytics/analytics-destination.js.map +1 -0
  169. package/dist/sdk/analytics/use-analytics-actions.d.ts +57 -0
  170. package/dist/sdk/analytics/use-analytics-actions.js +108 -0
  171. package/dist/sdk/analytics/use-analytics-actions.js.map +1 -0
  172. package/dist/sdk/cart/use-cart-actions.js +1 -1
  173. package/dist/sdk/data/entity-resolution-context.js +31 -0
  174. package/dist/sdk/data/entity-resolution-context.js.map +1 -0
  175. package/dist/sdk/data/entity-subscriber.js +18 -8
  176. package/dist/sdk/data/entity-subscriber.js.map +1 -1
  177. package/dist/sdk/data/index.js +1 -0
  178. package/dist/sdk/data/page-reactive-data-root.d.ts +36 -0
  179. package/dist/sdk/data/page-reactive-data-root.js +4 -2
  180. package/dist/sdk/data/page-reactive-data-root.js.map +1 -1
  181. package/dist/sdk/data/reactive-data-provider.js +7 -3
  182. package/dist/sdk/data/reactive-data-provider.js.map +1 -1
  183. package/dist/sdk/data/reactive-list-item-data-provider.js +12 -4
  184. package/dist/sdk/data/reactive-list-item-data-provider.js.map +1 -1
  185. package/dist/sdk/data/use-reactive-data-settled.js +39 -0
  186. package/dist/sdk/data/use-reactive-data-settled.js.map +1 -0
  187. package/dist/sdk/data/use-reactive-entity.js +5 -1
  188. package/dist/sdk/data/use-reactive-entity.js.map +1 -1
  189. package/dist/sdk/generated/fragment-types.js +2 -0
  190. package/dist/sdk/generated/fragment-types.js.map +1 -1
  191. package/dist/sdk/generated/graphql.js.map +1 -1
  192. package/dist/sdk/i18n/messages/en-US.js +3 -0
  193. package/dist/sdk/i18n/messages/en-US.json +3 -0
  194. package/dist/sdk/i18n/messages/en-US.json.d.ts +3 -0
  195. package/dist/sdk/i18n/messages/es-ES.js +3 -0
  196. package/dist/sdk/i18n/messages/es-ES.json +3 -0
  197. package/dist/sdk/i18n/messages/pt-BR.js +3 -0
  198. package/dist/sdk/i18n/messages/pt-BR.json +3 -0
  199. package/dist/sdk/identity/use-identity-actions.js +3 -2
  200. package/dist/sdk/identity/use-identity-actions.js.map +1 -1
  201. package/dist/sdk/payment/gateway-origin.js +35 -0
  202. package/dist/sdk/payment/gateway-origin.js.map +1 -0
  203. package/dist/sdk/payment/gateway.d.ts +85 -0
  204. package/dist/sdk/payment/gateway.js +183 -0
  205. package/dist/sdk/payment/gateway.js.map +1 -0
  206. package/dist/sdk/payment/operations.d.ts +52 -0
  207. package/dist/sdk/payment/operations.js +223 -0
  208. package/dist/sdk/payment/operations.js.map +1 -0
  209. package/dist/sdk/payment/security-code-mask.js +71 -0
  210. package/dist/sdk/payment/security-code-mask.js.map +1 -0
  211. package/dist/sdk/payment/submission-registry.d.ts +33 -0
  212. package/dist/sdk/payment/submission-registry.js +58 -0
  213. package/dist/sdk/payment/submission-registry.js.map +1 -0
  214. package/dist/sdk/payment/use-payment-actions.d.ts +78 -0
  215. package/dist/sdk/payment/use-payment-actions.js +276 -0
  216. package/dist/sdk/payment/use-payment-actions.js.map +1 -0
  217. package/dist/sdk/payment/use-place-order.d.ts +65 -0
  218. package/dist/sdk/payment/use-place-order.js +640 -0
  219. package/dist/sdk/payment/use-place-order.js.map +1 -0
  220. package/dist/sdk/shipping/use-shipping-actions.js +1 -1
  221. package/dist/utils/loggable-error-name.js +49 -0
  222. package/dist/utils/loggable-error-name.js.map +1 -0
  223. package/dist/utils/resolve-authored-copy.js +18 -0
  224. package/dist/utils/resolve-authored-copy.js.map +1 -0
  225. package/dist/utils/substitute-template.js +33 -0
  226. package/dist/utils/substitute-template.js.map +1 -0
  227. package/package.json +2 -1
  228. package/src/styles/tailwind.css +34 -0
  229. package/dist/docs/client-profile-form.md +0 -192
  230. package/dist/matter/client-profile-form/client-profile-form.d.ts +0 -126
  231. package/dist/matter/client-profile-form/client-profile-form.js +0 -662
  232. package/dist/matter/client-profile-form/client-profile-form.js.map +0 -1
@@ -0,0 +1,305 @@
1
+ "use client";
2
+ import { defineComponent } from "../../core/dist/define-component.js";
3
+ import "../../core/dist/index.js";
4
+ import { useDataOverride } from "../../sdk/data/data-override-context.js";
5
+ import { ReactiveRootContext } from "../../sdk/data/page-reactive-data-root.js";
6
+ import { substituteTemplate } from "../../utils/substitute-template.js";
7
+ import { useContext } from "react";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+ import { CombinedGraphQLErrors } from "@apollo/client/errors";
10
+ //#region src/matter/order-placed-payment-summary/order-placed-payment-summary.tsx
11
+ /**
12
+ * OrderPlacedPaymentSummary matter (epic `credit-card`, T17 — recap the payment on the order
13
+ * confirmation). Single-file matter per ADR-0001: owns its own `Query.order(orderGroup: $orderGroup)`
14
+ * fragment and sets no `requiresContext`, so it composes independently on a confirmation surface — no
15
+ * ancestor context needed.
16
+ *
17
+ * This component is sourced from the settled order, never the live order form
18
+ * (specs/order-placed-payment-summary.md, Decision 1): `Query.payment` projects the live order form
19
+ * and stops resolving once the order is placed, so a post-purchase recap needs the settled-order
20
+ * projection (`Query.order`, T16) instead — never compose it in checkout, and never
21
+ * point this component at `Query.payment`.
22
+ *
23
+ * Read-only recap — no write affordance, no internal state; every transition is driven purely by
24
+ * `$data`, `loading`, and the reactive root's query error (the multi-seller signal — Decision 4).
25
+ */
26
+ const fragment = `
27
+ fragment OrderPlacedPaymentSummaryData on Query {
28
+ order(orderGroup: $orderGroup) {
29
+ __typename
30
+ id
31
+ network
32
+ lastFourDigits
33
+ label
34
+ installment {
35
+ count
36
+ total {
37
+ amount
38
+ currencyCode
39
+ }
40
+ }
41
+ }
42
+ }
43
+ `;
44
+ const MULTI_SELLER_ERROR_CODE = "UNSUPPORTED_MULTI_SELLER_ORDER";
45
+ const TOTAL_TOKEN = "{total}";
46
+ const COUNT_TOKEN = "{count}";
47
+ /**
48
+ * Extracts the multi-seller signal from the reactive root's query error (Decision 4): `Query.order`
49
+ * throws a `GraphQLError` with `extensions.code === 'UNSUPPORTED_MULTI_SELLER_ORDER'` and
50
+ * `extensions.sellerCount` set to the seller count, rather than returning a data field — the count
51
+ * exists only inside the combined GraphQL error's own `errors[].extensions`, never on `$data`.
52
+ * Returns `null` when `error` is absent, is not a `CombinedGraphQLErrors`, or carries no matching
53
+ * entry — a differently-coded failure (e.g. a generic upstream error) falls through to the
54
+ * `unresolved` state instead, matching FR-5's precedence.
55
+ */
56
+ function resolveMultiSellerSignal(error) {
57
+ if (!error || !CombinedGraphQLErrors.is(error)) return null;
58
+ const multiSellerError = error.errors.find((graphQLError) => graphQLError.extensions?.code === MULTI_SELLER_ERROR_CODE);
59
+ if (!multiSellerError) return null;
60
+ const sellerCount = multiSellerError.extensions?.sellerCount;
61
+ return { sellerCount: typeof sellerCount === "number" ? sellerCount : null };
62
+ }
63
+ /**
64
+ * Resolves `$data` plus the reactive root's `loading`/`error` into exactly one of five states, in
65
+ * FR-5's total, ordered precedence: multi-seller wins over everything else — including a resolved
66
+ * `$data` — because in that state the one seller's card that *is* present would otherwise be shown as
67
+ * if it were the whole payment (Decision 4). `loading && data == null` (Decision 6) separates a
68
+ * transient in-flight window from a genuinely unresolved order group, so the unresolved alert never
69
+ * flashes on first paint of a successful confirmation. `no-record` fires only when every recap field
70
+ * is null — a record carrying just one of the three still renders as `recap` (Open Question 5).
71
+ *
72
+ * `no-record`'s gate is unchanged by T33 (FR-6, INV-6): it still checks only network/lastFourDigits/
73
+ * label, never installment — an order carrying an installment but none of the original three fields
74
+ * still renders `no-record`, not a recap with only an installment line.
75
+ */
76
+ function resolveOrderPlacedPayment(data, loading, error) {
77
+ const multiSeller = resolveMultiSellerSignal(error);
78
+ if (multiSeller) return {
79
+ kind: "multi-seller",
80
+ sellerCount: multiSeller.sellerCount
81
+ };
82
+ if (loading && data == null) return { kind: "loading" };
83
+ if (data == null) return { kind: "unresolved" };
84
+ if (data.network == null && data.lastFourDigits == null && data.label == null) return { kind: "no-record" };
85
+ return {
86
+ kind: "recap",
87
+ network: data.network ?? "",
88
+ lastFourDigits: data.lastFourDigits ?? null,
89
+ label: data.label ?? null,
90
+ installment: data.installment ?? null
91
+ };
92
+ }
93
+ /**
94
+ * Substitutes `{count}` when the seller count is known; otherwise drops the token and collapses the
95
+ * resulting run of spaces, so an unknown count never prints the literal string "undefined" or "NaN".
96
+ */
97
+ function formatMultiSellerNotice(template, sellerCount) {
98
+ if (sellerCount == null) return substituteTemplate(template, { count: "" }).replace(/ {2,}/g, " ").trim();
99
+ return substituteTemplate(template, { count: String(sellerCount) });
100
+ }
101
+ /** Locale-formats a settled Money value (e.g. "R$ 600,00"). Fixed to pt-BR — not because it matches
102
+ * every other default in this component (T31 moved every copy default here to en-US, which falsifies
103
+ * that reasoning) but because this component has no `locale` prop of its own to read instead: adding
104
+ * one is a formatting change, not a copy change, and is out of this task's scope (see OQ-3 in
105
+ * specs/make-the-payment-components-authored-copy.md).
106
+ *
107
+ * `currencyCode` reaches this function already validated: `Money.currencyCode` is `CurrencyCode!`,
108
+ * a GraphQL enum (`base/schema.graphql:186,206`), so a value outside the declared members fails
109
+ * GraphQL's own enum serialization server-side and nulls `total` before it ever reaches a query
110
+ * result — verified against this app's exact `buildSubgraphSchema` wiring (T33 fix pass: an empty
111
+ * string / "XX" / "BRLL" each produced a `total: null` field error, never a value reaching here).
112
+ * The try/catch below is defense in depth only, for a caller that bypasses that enum entirely (a
113
+ * Storybook fixture, a `$data` override, a future schema change) — never expected to fire against a
114
+ * real query response. */
115
+ function formatMoney(money) {
116
+ try {
117
+ return new Intl.NumberFormat("pt-BR", {
118
+ style: "currency",
119
+ currency: money.currencyCode
120
+ }).format(Number(money.amount));
121
+ } catch {
122
+ return money.amount;
123
+ }
124
+ }
125
+ /**
126
+ * Drops the `{total}` placeholder from the RAW template — before `{count}` is substituted — together
127
+ * with the WHOLE run of text between `{count}`'s own glued suffix (if any) and `{total}`, not merely
128
+ * one connector word (Decision 8, T31 — specs/make-the-payment-components-authored-copy.md; this
129
+ * hardens FR-12/T97's original single-run rule, which dropped only one word and left a multi-word
130
+ * connector dangling, e.g. `'{count}x no valor total de {total}'` degrading to "6x no valor total").
131
+ *
132
+ * When `{count}` appears before `{total}`, this scans FORWARD from the end of `{count}` to the first
133
+ * whitespace character: that run (possibly zero-length) is the count's own glued suffix — a unit
134
+ * symbol like "x"/"×"/"回", never a connector — and is kept verbatim; everything from there through
135
+ * `{total}` itself is dropped. When no whitespace separates `{count}` from `{total}` at all, the whole
136
+ * run between them is glued and kept, and only the `{total}` token is dropped. A null total therefore
137
+ * degrades `'{count}x of {total}'` to "6x" (the whitespace-separated run "of" is dropped in full),
138
+ * `'{count}x {total}'` to "6x" (the "x" is glued to `{count}`, so it survives even though `{total}`
139
+ * does not), `'{count}x{total}'` to "6x" too (no whitespace at all between them — the glued run,
140
+ * `{total}` aside, is kept), and `'{count} parcelas de {total}'` — a self-contained unit noun with no
141
+ * glued suffix — to a bare "6": no algorithm can tell "parcelas" apart from a dangling preposition, so
142
+ * the same deterministic rule that kills every dangling phrase also takes a legitimate unit noun with
143
+ * it (OQ-2). This applies wherever `{count}` sits in the template, not only at its start (`'Paid
144
+ * {count}x {total}'` → "Paid 6x").
145
+ *
146
+ * The forward scan is bounded so it can never cross into `{total}`: `{total}` is a fixed literal
147
+ * marker with a known position, unlike the count's own digits once substituted, which could otherwise
148
+ * be mistaken for a connector by shape alone. Running this on the raw template — and only substituting
149
+ * `{count}` afterward — is what keeps the count itself from ever being the thing removed (FR-4: the
150
+ * element must always render when `count` resolves, for any template that contains `{count}`).
151
+ *
152
+ * Two edge cases are deliberate limits of this rule, not bugs to chase. `'{count} x {total}'` still
153
+ * degrades to "6": a whitespace-separated single-character "x" is by construction indistinguishable
154
+ * from a connector word, so it is dropped along with it — that IS the rule's principle, not an
155
+ * exception to it. `'{count}-{total}'` degrades to "6-": the glued "-" is kept verbatim as the
156
+ * author's own template text, whatever character it is. Both outcomes are stable and predictable,
157
+ * which is the point.
158
+ *
159
+ * When `{count}` does not appear before `{total}` at all (a template that never shows the count, e.g.
160
+ * `'Total: {total}'`), there is no boundary to scan forward from, and this path is unchanged from
161
+ * before Decision 8: walk backward from `{total}`, drop one connector run and its surrounding
162
+ * whitespace, and fall back to cutting the bare `{total}` token only when that would leave nothing but
163
+ * whitespace (`installmentTemplate = '{total}'` collapses to `''`). FR-4's "always renders when count
164
+ * resolves" guarantee is therefore scoped to templates that contain `{count}`; a total-only template
165
+ * never had a count to render, and asked for the total alone.
166
+ */
167
+ function stripTotalToken(template) {
168
+ const totalIndex = template.indexOf(TOTAL_TOKEN);
169
+ if (totalIndex === -1) return template;
170
+ const countIndex = template.indexOf(COUNT_TOKEN);
171
+ const countEnd = countIndex + 7;
172
+ const hasCountBoundary = countIndex !== -1 && countEnd <= totalIndex;
173
+ const isWhitespaceAt = (index) => /\s/.test(template[index] ?? "");
174
+ if (hasCountBoundary) {
175
+ let gluedEnd = countEnd;
176
+ while (gluedEnd < totalIndex && !isWhitespaceAt(gluedEnd)) gluedEnd++;
177
+ return template.slice(0, gluedEnd) + template.slice(totalIndex + 7);
178
+ }
179
+ let cursor = totalIndex;
180
+ while (cursor > 0 && isWhitespaceAt(cursor - 1)) cursor--;
181
+ while (cursor > 0 && !isWhitespaceAt(cursor - 1)) cursor--;
182
+ while (cursor > 0 && isWhitespaceAt(cursor - 1)) cursor--;
183
+ const withConnectorDropped = template.slice(0, cursor) + template.slice(totalIndex + 7);
184
+ if (withConnectorDropped.trim() !== "") return withConnectorDropped;
185
+ return template.slice(0, totalIndex) + template.slice(totalIndex + 7);
186
+ }
187
+ /**
188
+ * Substitutes `{count}` and `{total}` with replacer FUNCTIONS, not replacement strings (FR-5, the
189
+ * same S1 fix `lastFourText` below applies to `lastFourTemplate`): `total.amount`/`total.currencyCode`
190
+ * are server-sourced and unconstrained, so a replacement STRING could let a hostile value rewrite the
191
+ * surrounding template text via `$&`/`` $` ``/`$'`/`$$`.
192
+ *
193
+ * When total is null, `{total}` and its connector word are dropped by `stripTotalToken` — on the raw
194
+ * template, before `{count}` is substituted; see that function's doc comment for the exact rule. A
195
+ * prior version ran an unanchored regex over the ALREADY-substituted string and, for any template
196
+ * where the connector sits right next to `{count}` with only whitespace further out (e.g.
197
+ * `'{count}x {total}'`), matched from the very start of the string and deleted the count along with
198
+ * the connector, silently un-rendering the whole line (T33 fix-round-2 regression).
199
+ */
200
+ function formatInstallmentLine(template, installment) {
201
+ const { total } = installment;
202
+ if (!total) return substituteTemplate(stripTotalToken(template), { count: String(installment.count) }).trim();
203
+ return substituteTemplate(template, {
204
+ count: String(installment.count),
205
+ total: formatMoney(total)
206
+ }).trim();
207
+ }
208
+ const OrderPlacedPaymentSummaryDefinition = defineComponent({
209
+ name: "OrderPlacedPaymentSummary",
210
+ importPath: "@vtex/faststore/components",
211
+ component: OrderPlacedPaymentSummary,
212
+ fragment,
213
+ fetchStrategy: "reactive",
214
+ editor: {
215
+ label: "Order Placed Payment Summary",
216
+ category: "commerce"
217
+ },
218
+ schema: {
219
+ lastFourTemplate: {
220
+ type: "string",
221
+ default: "•••• {lastFour}",
222
+ label: "Last-four template"
223
+ },
224
+ noPaymentRecordLabel: {
225
+ type: "string",
226
+ default: "Payment method not available",
227
+ label: "No payment record label"
228
+ },
229
+ unresolvedOrderLabel: {
230
+ type: "string",
231
+ default: "Could not load the payment details",
232
+ label: "Unresolved order label"
233
+ },
234
+ multiSellerNotice: {
235
+ type: "string",
236
+ default: "This order was split across {count} sellers; the payment summary does not cover split orders",
237
+ label: "Multi-seller notice"
238
+ },
239
+ installmentTemplate: {
240
+ type: "string",
241
+ default: "{count}x of {total}",
242
+ label: "Installment template"
243
+ }
244
+ },
245
+ guideline: "Read-only recap, on a post-purchase confirmation surface, of the card the shopper paid with — sourced from the settled order (Query.order), never the live order form. Owns its own fragment; requires no ancestor context, so it composes standalone. Renders exactly one of five states, in strict precedence: a role=\"alert\" notice for a split (multi-seller) order — the projection never covers one; nothing while the query is in flight; a role=\"alert\" notice when the order group does not resolve; an informational line when the order resolves with no payment record; or the recap itself (network, plus last four whenever the projection carries one, plus the saved-card label when set, plus an installment line — count and settled total only, never a per-installment amount or interest rate — whenever the settled record reports one). Never compose it in checkout — the order group it reads does not exist until the order is placed, and no component renders a payment recap there. Never renders a full card number or CVV. See order-placed-payment-summary.md."
246
+ });
247
+ /**
248
+ * Renders the settled order's payment recap on a confirmation surface. Resolves through
249
+ * `resolveOrderPlacedPayment`'s fixed precedence and renders exactly one state: the multi-seller and
250
+ * unresolved notices as inline `role="alert"` elements (FR-6); the no-record line as plain
251
+ * informational text; the in-flight state as nothing at all (Decision 6); the recap as network plus
252
+ * last four (whenever present) plus the saved-card label (whenever set) plus the installment line
253
+ * (whenever the settled record reports one, T33). Requires no context — composable standalone.
254
+ */
255
+ function OrderPlacedPaymentSummary({ className, lastFourTemplate = "•••• {lastFour}", noPaymentRecordLabel = "Payment method not available", unresolvedOrderLabel = "Could not load the payment details", multiSellerNotice = "This order was split across {count} sellers; the payment summary does not cover split orders", installmentTemplate = "{count}x of {total}", $data: $dataProp }) {
256
+ const $data = useDataOverride($dataProp);
257
+ const { loading, error } = useContext(ReactiveRootContext);
258
+ const resolved = resolveOrderPlacedPayment($data ?? null, loading, error);
259
+ if (resolved.kind === "loading") return null;
260
+ if (resolved.kind === "multi-seller") return /* @__PURE__ */ jsx("div", {
261
+ className,
262
+ role: "alert",
263
+ "data-fs-order-placed-payment-summary": "",
264
+ "data-fs-order-placed-payment-summary-multi-seller": "",
265
+ children: formatMultiSellerNotice(multiSellerNotice, resolved.sellerCount)
266
+ });
267
+ if (resolved.kind === "unresolved") return /* @__PURE__ */ jsx("div", {
268
+ className,
269
+ role: "alert",
270
+ "data-fs-order-placed-payment-summary": "",
271
+ "data-fs-order-placed-payment-summary-unresolved": "",
272
+ children: unresolvedOrderLabel
273
+ });
274
+ if (resolved.kind === "no-record") return /* @__PURE__ */ jsx("div", {
275
+ className,
276
+ "data-fs-order-placed-payment-summary": "",
277
+ "data-fs-order-placed-payment-summary-no-record": "",
278
+ children: noPaymentRecordLabel
279
+ });
280
+ const { network, lastFourDigits, label, installment } = resolved;
281
+ const methodText = [network, lastFourDigits ? substituteTemplate(lastFourTemplate, { lastFour: lastFourDigits }) : null].filter(Boolean).join(" ");
282
+ const installmentText = installment ? formatInstallmentLine(installmentTemplate, installment) : null;
283
+ return /* @__PURE__ */ jsxs("div", {
284
+ className,
285
+ "data-fs-order-placed-payment-summary": "",
286
+ children: [
287
+ methodText ? /* @__PURE__ */ jsx("p", {
288
+ "data-fs-order-placed-payment-summary-method": "",
289
+ children: methodText
290
+ }) : null,
291
+ label ? /* @__PURE__ */ jsx("p", {
292
+ "data-fs-order-placed-payment-summary-label": "",
293
+ children: label
294
+ }) : null,
295
+ installmentText ? /* @__PURE__ */ jsx("p", {
296
+ "data-fs-order-placed-payment-summary-installment": "",
297
+ children: installmentText
298
+ }) : null
299
+ ]
300
+ });
301
+ }
302
+ //#endregion
303
+ export { OrderPlacedPaymentSummary, OrderPlacedPaymentSummaryDefinition };
304
+
305
+ //# sourceMappingURL=order-placed-payment-summary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order-placed-payment-summary.js","names":[],"sources":["../../../src/matter/order-placed-payment-summary/order-placed-payment-summary.tsx"],"sourcesContent":["'use client';\n\nimport type { ErrorLike } from '@apollo/client';\nimport { CombinedGraphQLErrors } from '@apollo/client/errors';\nimport { defineComponent } from '@vtex/fs-core';\nimport { useContext } from 'react';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport { ReactiveRootContext } from '../../sdk/data/page-reactive-data-root.js';\nimport { substituteTemplate } from '../../utils/substitute-template.js';\n\n/**\n * OrderPlacedPaymentSummary matter (epic `credit-card`, T17 — recap the payment on the order\n * confirmation). Single-file matter per ADR-0001: owns its own `Query.order(orderGroup: $orderGroup)`\n * fragment and sets no `requiresContext`, so it composes independently on a confirmation surface — no\n * ancestor context needed.\n *\n * This component is sourced from the settled order, never the live order form\n * (specs/order-placed-payment-summary.md, Decision 1): `Query.payment` projects the live order form\n * and stops resolving once the order is placed, so a post-purchase recap needs the settled-order\n * projection (`Query.order`, T16) instead — never compose it in checkout, and never\n * point this component at `Query.payment`.\n *\n * Read-only recap — no write affordance, no internal state; every transition is driven purely by\n * `$data`, `loading`, and the reactive root's query error (the multi-seller signal — Decision 4).\n */\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment OrderPlacedPaymentSummaryData on Query {\n order(orderGroup: $orderGroup) {\n __typename\n id\n network\n lastFourDigits\n label\n installment {\n count\n total {\n amount\n currencyCode\n }\n }\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * The installment plan the order was charged in (T33, specs/settle-and-project-the-placed-\n * order.md). Null when the settled record reports none — never fabricated, never defaulted to\n * `{ count: 1 }`. `total` is null when the source carries the count but not enough to build a\n * Money from (Decision 2: the settled-order source never carries a per-installment amount or an\n * interest rate at all, see US-2).\n */\nexport type OrderPlacedInstallment = {\n count: number;\n total?: { amount: string; currencyCode: string } | null;\n} | null;\n\n/**\n * The `$data` slice forwarded by the reactive runtime. `OrderPlacedPaymentSummaryData` selects\n * exactly one top-level field (`order`), so per ADR-0015 the compiler derives a single entity\n * fragment and `$data` is the unwrapped value of that field — never a `{ order: … }` wrapper. `null`\n * when the order group does not resolve to a settled order.\n */\nexport type OrderPlacedPaymentSummaryData = {\n network?: string | null;\n lastFourDigits?: string | null;\n label?: string | null;\n installment?: OrderPlacedInstallment;\n} | null;\n\n/** The five states FR-5 resolves to, in strict, total precedence order. */\ntype ResolvedOrderPlacedPayment =\n | { kind: 'multi-seller'; sellerCount: number | null }\n | { kind: 'loading' }\n | { kind: 'unresolved' }\n | { kind: 'no-record' }\n | {\n kind: 'recap';\n network: string;\n lastFourDigits: string | null;\n label: string | null;\n installment: OrderPlacedInstallment;\n };\n\nexport type OrderPlacedPaymentSummaryProps = {\n className?: string;\n /** Rendered only when `lastFourDigits` resolves; `{lastFour}` placeholder. */\n lastFourTemplate?: string;\n /** Shown when the order resolves but carries no projectable payment record. */\n noPaymentRecordLabel?: string;\n /** Shown when the order group does not resolve to a settled order. */\n unresolvedOrderLabel?: string;\n /** Shown for a split order; `{count}` placeholder, omitted when the count is unknown. */\n multiSellerNotice?: string;\n /**\n * Rendered only when `installment` resolves; `{count}`/`{total}` placeholders. When the source\n * carries the count but not enough to build a total from (US-2 — never a fabricated per-installment\n * amount), `{total}` is dropped together with everything between `{count}`'s own glued suffix (if\n * any) and `{total}` — but a run glued directly to `{count}`, with no whitespace, is a suffix of the\n * count, not a connector, and is kept. A null total therefore degrades `'{count}x of {total}'` to\n * \"6x\" — never \"6x of\" or a bare \"6\" (see `stripTotalToken`).\n */\n installmentTemplate?: string;\n /** Unwrapped `Order` slice forwarded by the reactive runtime — never a `{ order: … }` wrapper. */\n $data?: OrderPlacedPaymentSummaryData;\n};\n\n// ---------------------------------------------------------------------------\n// Resolution\n// ---------------------------------------------------------------------------\n\nconst MULTI_SELLER_ERROR_CODE = 'UNSUPPORTED_MULTI_SELLER_ORDER';\nconst TOTAL_TOKEN = '{total}';\nconst COUNT_TOKEN = '{count}';\n\n/**\n * Extracts the multi-seller signal from the reactive root's query error (Decision 4): `Query.order`\n * throws a `GraphQLError` with `extensions.code === 'UNSUPPORTED_MULTI_SELLER_ORDER'` and\n * `extensions.sellerCount` set to the seller count, rather than returning a data field — the count\n * exists only inside the combined GraphQL error's own `errors[].extensions`, never on `$data`.\n * Returns `null` when `error` is absent, is not a `CombinedGraphQLErrors`, or carries no matching\n * entry — a differently-coded failure (e.g. a generic upstream error) falls through to the\n * `unresolved` state instead, matching FR-5's precedence.\n */\nfunction resolveMultiSellerSignal(error: ErrorLike | undefined): { sellerCount: number | null } | null {\n if (!error || !CombinedGraphQLErrors.is(error)) return null;\n const multiSellerError = error.errors.find(\n (graphQLError) => graphQLError.extensions?.code === MULTI_SELLER_ERROR_CODE,\n );\n if (!multiSellerError) return null;\n const sellerCount = multiSellerError.extensions?.sellerCount;\n return { sellerCount: typeof sellerCount === 'number' ? sellerCount : null };\n}\n\n/**\n * Resolves `$data` plus the reactive root's `loading`/`error` into exactly one of five states, in\n * FR-5's total, ordered precedence: multi-seller wins over everything else — including a resolved\n * `$data` — because in that state the one seller's card that *is* present would otherwise be shown as\n * if it were the whole payment (Decision 4). `loading && data == null` (Decision 6) separates a\n * transient in-flight window from a genuinely unresolved order group, so the unresolved alert never\n * flashes on first paint of a successful confirmation. `no-record` fires only when every recap field\n * is null — a record carrying just one of the three still renders as `recap` (Open Question 5).\n *\n * `no-record`'s gate is unchanged by T33 (FR-6, INV-6): it still checks only network/lastFourDigits/\n * label, never installment — an order carrying an installment but none of the original three fields\n * still renders `no-record`, not a recap with only an installment line.\n */\nfunction resolveOrderPlacedPayment(\n data: OrderPlacedPaymentSummaryData,\n loading: boolean,\n error: ErrorLike | undefined,\n): ResolvedOrderPlacedPayment {\n const multiSeller = resolveMultiSellerSignal(error);\n if (multiSeller) return { kind: 'multi-seller', sellerCount: multiSeller.sellerCount };\n if (loading && data == null) return { kind: 'loading' };\n if (data == null) return { kind: 'unresolved' };\n if (data.network == null && data.lastFourDigits == null && data.label == null) return { kind: 'no-record' };\n return {\n kind: 'recap',\n network: data.network ?? '',\n lastFourDigits: data.lastFourDigits ?? null,\n label: data.label ?? null,\n installment: data.installment ?? null,\n };\n}\n\n/**\n * Substitutes `{count}` when the seller count is known; otherwise drops the token and collapses the\n * resulting run of spaces, so an unknown count never prints the literal string \"undefined\" or \"NaN\".\n */\nfunction formatMultiSellerNotice(template: string, sellerCount: number | null): string {\n if (sellerCount == null) return substituteTemplate(template, { count: '' }).replace(/ {2,}/g, ' ').trim();\n return substituteTemplate(template, { count: String(sellerCount) });\n}\n\n/** Locale-formats a settled Money value (e.g. \"R$ 600,00\"). Fixed to pt-BR — not because it matches\n * every other default in this component (T31 moved every copy default here to en-US, which falsifies\n * that reasoning) but because this component has no `locale` prop of its own to read instead: adding\n * one is a formatting change, not a copy change, and is out of this task's scope (see OQ-3 in\n * specs/make-the-payment-components-authored-copy.md).\n *\n * `currencyCode` reaches this function already validated: `Money.currencyCode` is `CurrencyCode!`,\n * a GraphQL enum (`base/schema.graphql:186,206`), so a value outside the declared members fails\n * GraphQL's own enum serialization server-side and nulls `total` before it ever reaches a query\n * result — verified against this app's exact `buildSubgraphSchema` wiring (T33 fix pass: an empty\n * string / \"XX\" / \"BRLL\" each produced a `total: null` field error, never a value reaching here).\n * The try/catch below is defense in depth only, for a caller that bypasses that enum entirely (a\n * Storybook fixture, a `$data` override, a future schema change) — never expected to fire against a\n * real query response. */\nfunction formatMoney(money: { amount: string; currencyCode: string }): string {\n try {\n return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: money.currencyCode }).format(\n Number(money.amount),\n );\n } catch {\n return money.amount;\n }\n}\n\n/**\n * Drops the `{total}` placeholder from the RAW template — before `{count}` is substituted — together\n * with the WHOLE run of text between `{count}`'s own glued suffix (if any) and `{total}`, not merely\n * one connector word (Decision 8, T31 — specs/make-the-payment-components-authored-copy.md; this\n * hardens FR-12/T97's original single-run rule, which dropped only one word and left a multi-word\n * connector dangling, e.g. `'{count}x no valor total de {total}'` degrading to \"6x no valor total\").\n *\n * When `{count}` appears before `{total}`, this scans FORWARD from the end of `{count}` to the first\n * whitespace character: that run (possibly zero-length) is the count's own glued suffix — a unit\n * symbol like \"x\"/\"×\"/\"回\", never a connector — and is kept verbatim; everything from there through\n * `{total}` itself is dropped. When no whitespace separates `{count}` from `{total}` at all, the whole\n * run between them is glued and kept, and only the `{total}` token is dropped. A null total therefore\n * degrades `'{count}x of {total}'` to \"6x\" (the whitespace-separated run \"of\" is dropped in full),\n * `'{count}x {total}'` to \"6x\" (the \"x\" is glued to `{count}`, so it survives even though `{total}`\n * does not), `'{count}x{total}'` to \"6x\" too (no whitespace at all between them — the glued run,\n * `{total}` aside, is kept), and `'{count} parcelas de {total}'` — a self-contained unit noun with no\n * glued suffix — to a bare \"6\": no algorithm can tell \"parcelas\" apart from a dangling preposition, so\n * the same deterministic rule that kills every dangling phrase also takes a legitimate unit noun with\n * it (OQ-2). This applies wherever `{count}` sits in the template, not only at its start (`'Paid\n * {count}x {total}'` → \"Paid 6x\").\n *\n * The forward scan is bounded so it can never cross into `{total}`: `{total}` is a fixed literal\n * marker with a known position, unlike the count's own digits once substituted, which could otherwise\n * be mistaken for a connector by shape alone. Running this on the raw template — and only substituting\n * `{count}` afterward — is what keeps the count itself from ever being the thing removed (FR-4: the\n * element must always render when `count` resolves, for any template that contains `{count}`).\n *\n * Two edge cases are deliberate limits of this rule, not bugs to chase. `'{count} x {total}'` still\n * degrades to \"6\": a whitespace-separated single-character \"x\" is by construction indistinguishable\n * from a connector word, so it is dropped along with it — that IS the rule's principle, not an\n * exception to it. `'{count}-{total}'` degrades to \"6-\": the glued \"-\" is kept verbatim as the\n * author's own template text, whatever character it is. Both outcomes are stable and predictable,\n * which is the point.\n *\n * When `{count}` does not appear before `{total}` at all (a template that never shows the count, e.g.\n * `'Total: {total}'`), there is no boundary to scan forward from, and this path is unchanged from\n * before Decision 8: walk backward from `{total}`, drop one connector run and its surrounding\n * whitespace, and fall back to cutting the bare `{total}` token only when that would leave nothing but\n * whitespace (`installmentTemplate = '{total}'` collapses to `''`). FR-4's \"always renders when count\n * resolves\" guarantee is therefore scoped to templates that contain `{count}`; a total-only template\n * never had a count to render, and asked for the total alone.\n */\nfunction stripTotalToken(template: string): string {\n const totalIndex = template.indexOf(TOTAL_TOKEN);\n if (totalIndex === -1) return template;\n\n const countIndex = template.indexOf(COUNT_TOKEN);\n const countEnd = countIndex + COUNT_TOKEN.length;\n const hasCountBoundary = countIndex !== -1 && countEnd <= totalIndex;\n\n const isWhitespaceAt = (index: number) => /\\s/.test(template[index] ?? '');\n\n if (hasCountBoundary) {\n // Forward scan (Decision 8): the count's own glued suffix runs from countEnd up to the first\n // whitespace, or up to {total} itself when there is no whitespace between them at all.\n let gluedEnd = countEnd;\n while (gluedEnd < totalIndex && !isWhitespaceAt(gluedEnd)) gluedEnd++;\n return template.slice(0, gluedEnd) + template.slice(totalIndex + TOTAL_TOKEN.length);\n }\n\n // No {count} precedes {total} (e.g. \"Total: {total}\") — there is no boundary to stop a\n // connector-removal scan at, so this walks backward from {total} exactly as it did before Decision\n // 8: drop the whitespace immediately before {total}, then the one connector run before that, falling\n // back to dropping only the bare {total} token when removing the connector would leave nothing but\n // whitespace. Unchanged by Decision 8.\n let cursor = totalIndex;\n while (cursor > 0 && isWhitespaceAt(cursor - 1)) cursor--; // whitespace right before {total}\n while (cursor > 0 && !isWhitespaceAt(cursor - 1)) cursor--; // the candidate connector run\n while (cursor > 0 && isWhitespaceAt(cursor - 1)) cursor--; // whitespace right before the connector\n\n const withConnectorDropped = template.slice(0, cursor) + template.slice(totalIndex + TOTAL_TOKEN.length);\n if (withConnectorDropped.trim() !== '') return withConnectorDropped;\n\n return template.slice(0, totalIndex) + template.slice(totalIndex + TOTAL_TOKEN.length);\n}\n\n/**\n * Substitutes `{count}` and `{total}` with replacer FUNCTIONS, not replacement strings (FR-5, the\n * same S1 fix `lastFourText` below applies to `lastFourTemplate`): `total.amount`/`total.currencyCode`\n * are server-sourced and unconstrained, so a replacement STRING could let a hostile value rewrite the\n * surrounding template text via `$&`/`` $` ``/`$'`/`$$`.\n *\n * When total is null, `{total}` and its connector word are dropped by `stripTotalToken` — on the raw\n * template, before `{count}` is substituted; see that function's doc comment for the exact rule. A\n * prior version ran an unanchored regex over the ALREADY-substituted string and, for any template\n * where the connector sits right next to `{count}` with only whitespace further out (e.g.\n * `'{count}x {total}'`), matched from the very start of the string and deleted the count along with\n * the connector, silently un-rendering the whole line (T33 fix-round-2 regression).\n */\nfunction formatInstallmentLine(\n template: string,\n installment: { count: number; total?: { amount: string; currencyCode: string } | null },\n): string {\n const { total } = installment;\n if (!total) {\n return substituteTemplate(stripTotalToken(template), { count: String(installment.count) }).trim();\n }\n return substituteTemplate(template, { count: String(installment.count), total: formatMoney(total) }).trim();\n}\n\n// ---------------------------------------------------------------------------\n// Definition\n// ---------------------------------------------------------------------------\n\nexport const OrderPlacedPaymentSummaryDefinition = defineComponent<OrderPlacedPaymentSummaryProps>({\n name: 'OrderPlacedPaymentSummary',\n importPath: '@vtex/faststore/components',\n component: OrderPlacedPaymentSummary,\n fragment,\n fetchStrategy: 'reactive',\n editor: { label: 'Order Placed Payment Summary', category: 'commerce' },\n schema: {\n lastFourTemplate: { type: 'string', default: '•••• {lastFour}', label: 'Last-four template' },\n noPaymentRecordLabel: {\n type: 'string',\n default: 'Payment method not available',\n label: 'No payment record label',\n },\n unresolvedOrderLabel: {\n type: 'string',\n default: 'Could not load the payment details',\n label: 'Unresolved order label',\n },\n multiSellerNotice: {\n type: 'string',\n default: 'This order was split across {count} sellers; the payment summary does not cover split orders',\n label: 'Multi-seller notice',\n },\n installmentTemplate: {\n type: 'string',\n default: '{count}x of {total}',\n label: 'Installment template',\n },\n },\n guideline:\n 'Read-only recap, on a post-purchase confirmation surface, of the card the shopper paid with — sourced from the settled order (Query.order), never the live order form. Owns its own fragment; requires no ancestor context, so it composes standalone. Renders exactly one of five states, in strict precedence: a role=\"alert\" notice for a split (multi-seller) order — the projection never covers one; nothing while the query is in flight; a role=\"alert\" notice when the order group does not resolve; an informational line when the order resolves with no payment record; or the recap itself (network, plus last four whenever the projection carries one, plus the saved-card label when set, plus an installment line — count and settled total only, never a per-installment amount or interest rate — whenever the settled record reports one). Never compose it in checkout — the order group it reads does not exist until the order is placed, and no component renders a payment recap there. Never renders a full card number or CVV. See order-placed-payment-summary.md.',\n});\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n/**\n * Renders the settled order's payment recap on a confirmation surface. Resolves through\n * `resolveOrderPlacedPayment`'s fixed precedence and renders exactly one state: the multi-seller and\n * unresolved notices as inline `role=\"alert\"` elements (FR-6); the no-record line as plain\n * informational text; the in-flight state as nothing at all (Decision 6); the recap as network plus\n * last four (whenever present) plus the saved-card label (whenever set) plus the installment line\n * (whenever the settled record reports one, T33). Requires no context — composable standalone.\n */\nexport function OrderPlacedPaymentSummary({\n className,\n lastFourTemplate = '•••• {lastFour}',\n noPaymentRecordLabel = 'Payment method not available',\n unresolvedOrderLabel = 'Could not load the payment details',\n multiSellerNotice = 'This order was split across {count} sellers; the payment summary does not cover split orders',\n installmentTemplate = '{count}x of {total}',\n $data: $dataProp,\n}: OrderPlacedPaymentSummaryProps) {\n const $data = useDataOverride($dataProp);\n const { loading, error } = useContext(ReactiveRootContext);\n const resolved = resolveOrderPlacedPayment($data ?? null, loading, error);\n\n if (resolved.kind === 'loading') return null;\n\n if (resolved.kind === 'multi-seller') {\n return (\n <div\n className={className}\n role=\"alert\"\n data-fs-order-placed-payment-summary=\"\"\n data-fs-order-placed-payment-summary-multi-seller=\"\"\n >\n {formatMultiSellerNotice(multiSellerNotice, resolved.sellerCount)}\n </div>\n );\n }\n\n if (resolved.kind === 'unresolved') {\n return (\n <div\n className={className}\n role=\"alert\"\n data-fs-order-placed-payment-summary=\"\"\n data-fs-order-placed-payment-summary-unresolved=\"\"\n >\n {unresolvedOrderLabel}\n </div>\n );\n }\n\n if (resolved.kind === 'no-record') {\n return (\n <div\n className={className}\n data-fs-order-placed-payment-summary=\"\"\n data-fs-order-placed-payment-summary-no-record=\"\"\n >\n {noPaymentRecordLabel}\n </div>\n );\n }\n\n const { network, lastFourDigits, label, installment } = resolved;\n // `substituteTemplate` inserts every value literally (Decision 7): lastFourDigits is server-sourced\n // and unconstrained (a plain `String` in the schema, mapped straight from VTEX's payment.lastDigits\n // with no length/charset check — schema.graphql:2508, mappers.ts:650), so a hostile or buggy\n // upstream value can never rewrite the surrounding template text (S1).\n const lastFourText = lastFourDigits ? substituteTemplate(lastFourTemplate, { lastFour: lastFourDigits }) : null;\n // network/lastFourDigits/label are each independently nullable (schema.graphql:2496-2516,\n // mappers.ts:649-651) — the no-record gate above fires only when ALL THREE are null (FR-5), so a\n // record carrying just one or two of them still reaches this branch. Joining only the present parts\n // avoids a leading space (network null, lastFourDigits set) and avoids rendering an empty <p> above\n // the label line (network and lastFourDigits both null, label set) — the exact blank US-2 forbids\n // (R2).\n const methodText = [network, lastFourText].filter(Boolean).join(' ');\n // FR-4/FR-6: rendered whenever installment resolves, regardless of the count's value (Open\n // Question 3 — whether to suppress at count <= 1 — is left to a future product decision; this\n // component has no such special case today), and only inside this recap branch.\n const installmentText = installment ? formatInstallmentLine(installmentTemplate, installment) : null;\n\n return (\n <div className={className} data-fs-order-placed-payment-summary=\"\">\n {methodText ? <p data-fs-order-placed-payment-summary-method=\"\">{methodText}</p> : null}\n {label ? <p data-fs-order-placed-payment-summary-label=\"\">{label}</p> : null}\n {installmentText ? <p data-fs-order-placed-payment-summary-installment=\"\">{installmentText}</p> : null}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import "../../sdk/actions/types.js";
2
+ import "./use-payment-method-selection.js";
3
+ import { ReactNode } from "react";
4
+ //#region src/matter/payment/payment-card-capture.d.ts
5
+ /**
6
+ * The checkout's `card-capture` provider (specs/sfs-3457-card-entry-card-capture-context.md),
7
+ * rendered by both payment roots inside their `PaymentSubmissionProvider`. It hands `CardEntry` the
8
+ * scoped methods and the selection facts, registers its capture as the `new-card` submission, folds
9
+ * the draft's non-sensitive values into the card, and sends it through `sendPayments`. It also owns
10
+ * `add_payment_info`, which needs the cart total `CardEntry` no longer reads.
11
+ */
12
+ export type PaymentCartData = {
13
+ total?: {
14
+ amount: number;
15
+ currencyCode: string;
16
+ } | null;
17
+ } | null;
18
+ //#endregion
19
+ //# sourceMappingURL=payment-card-capture.d.ts.map
@@ -0,0 +1,129 @@
1
+ "use client";
2
+ import { usePaymentSubmission } from "../../sdk/payment/submission-registry.js";
3
+ import { PAYMENT_NETWORK_ERROR_CODE } from "../../sdk/payment/operations.js";
4
+ import { CardCaptureContext } from "../card-entry/card-capture.js";
5
+ import { isCardMethod } from "./use-payment-method-selection.js";
6
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from "react";
7
+ import { jsx } from "react/jsx-runtime";
8
+ //#region src/matter/payment/payment-card-capture.tsx
9
+ /** The `add_payment_info` payment type for a card group; `undefined` for any other group (T140). */
10
+ function paymentTypeOf(groupName) {
11
+ if (groupName === "creditCardPaymentGroup") return "Credit Card";
12
+ if (groupName === "debitCardPaymentGroup") return "Debit Card";
13
+ }
14
+ /** Mirrors `usePaymentActions`' own infrastructure-failure code for the same underlying meaning. */
15
+ const MISSING_GATEWAY_ACTION_ERROR = {
16
+ code: PAYMENT_NETWORK_ERROR_CODE,
17
+ message: "Unable to complete the request. Please try again."
18
+ };
19
+ function PaymentCardCaptureProvider({ selection, cart, $actions, children }) {
20
+ const { register } = usePaymentSubmission();
21
+ const latest = useRef({
22
+ selection,
23
+ $actions
24
+ });
25
+ useLayoutEffect(() => {
26
+ latest.current = {
27
+ selection,
28
+ $actions
29
+ };
30
+ });
31
+ const total = cart?.total ?? null;
32
+ const totalRef = useRef(total);
33
+ const activeCaptures = useRef(0);
34
+ const analyticsDispatched = useRef(false);
35
+ const dispatchAddPaymentInfo = useCallback(() => {
36
+ const currentTotal = totalRef.current;
37
+ if (analyticsDispatched.current || activeCaptures.current === 0 || !currentTotal) return;
38
+ const track = latest.current.$actions?.analytics?.track;
39
+ if (!track) return;
40
+ const { methods, selectedMethodId } = latest.current.selection;
41
+ const paymentType = paymentTypeOf(methods.find((method) => method.id === selectedMethodId)?.groupName);
42
+ track({
43
+ name: "add_payment_info",
44
+ value: {
45
+ amount: currentTotal.amount,
46
+ currencyCode: currentTotal.currencyCode
47
+ },
48
+ ...paymentType ? { paymentType } : {}
49
+ });
50
+ analyticsDispatched.current = true;
51
+ }, []);
52
+ useEffect(() => {
53
+ totalRef.current = total;
54
+ dispatchAddPaymentInfo();
55
+ }, [
56
+ total,
57
+ $actions,
58
+ dispatchAddPaymentInfo
59
+ ]);
60
+ const registerCapture = useCallback((capture, preflight) => {
61
+ const submit = (transaction) => capture(async (card) => {
62
+ const sendPayments = latest.current.$actions?.payment?.sendPayments;
63
+ if (!sendPayments) {
64
+ console.error("[CardEntry] the payment action namespace is not injected");
65
+ return [MISSING_GATEWAY_ACTION_ERROR];
66
+ }
67
+ const { draft } = latest.current.selection;
68
+ return sendPayments({
69
+ transaction,
70
+ card: {
71
+ kind: "new",
72
+ ...card,
73
+ document: draft.document ?? void 0,
74
+ cardLabel: draft.cardLabel ?? void 0,
75
+ saveCard: draft.saveCard ?? void 0
76
+ }
77
+ });
78
+ });
79
+ const unregister = register("new-card", submit, preflight);
80
+ activeCaptures.current += 1;
81
+ dispatchAddPaymentInfo();
82
+ return () => {
83
+ activeCaptures.current -= 1;
84
+ unregister();
85
+ };
86
+ }, [register, dispatchAddPaymentInfo]);
87
+ const { methods, placing, selectMethod, selectNewCard, setDraftBin, draft, cardPaymentCollapsed, cardModeActive, selectedMethodId, selectionOutOfScope, selectedMethod } = selection;
88
+ const selectionNonCard = selectedMethod !== null && !isCardMethod(selectedMethod);
89
+ const value = useMemo(() => ({
90
+ networks: methods,
91
+ busy: placing,
92
+ clearOn: "success",
93
+ registerCapture,
94
+ selection: {
95
+ selectMethod,
96
+ selectNewCard,
97
+ setDraftBin,
98
+ path: draft.path,
99
+ bin: draft.bin,
100
+ collapsed: cardPaymentCollapsed,
101
+ cardModeActive,
102
+ selectedMethodId,
103
+ selectionOutOfScope,
104
+ selectionNonCard
105
+ }
106
+ }), [
107
+ methods,
108
+ placing,
109
+ registerCapture,
110
+ selectMethod,
111
+ selectNewCard,
112
+ setDraftBin,
113
+ draft.path,
114
+ draft.bin,
115
+ cardPaymentCollapsed,
116
+ cardModeActive,
117
+ selectedMethodId,
118
+ selectionOutOfScope,
119
+ selectionNonCard
120
+ ]);
121
+ return /* @__PURE__ */ jsx(CardCaptureContext, {
122
+ value,
123
+ children
124
+ });
125
+ }
126
+ //#endregion
127
+ export { PaymentCardCaptureProvider };
128
+
129
+ //# sourceMappingURL=payment-card-capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-card-capture.js","names":[],"sources":["../../../src/matter/payment/payment-card-capture.tsx"],"sourcesContent":["'use client';\n\nimport { type ReactNode, useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport { PAYMENT_NETWORK_ERROR_CODE, type PaymentUserError } from '../../sdk/payment/operations.js';\nimport { type PaymentSubmission, usePaymentSubmission } from '../../sdk/payment/submission-registry.js';\nimport {\n type CardCapture,\n CardCaptureContext,\n type CardCaptureContextValue,\n type CardCapturePreflight,\n} from '../card-entry/card-capture.js';\nimport { isCardMethod, type PaymentMethodSelection } from './use-payment-method-selection.js';\n\n/**\n * The checkout's `card-capture` provider (specs/sfs-3457-card-entry-card-capture-context.md),\n * rendered by both payment roots inside their `PaymentSubmissionProvider`. It hands `CardEntry` the\n * scoped methods and the selection facts, registers its capture as the `new-card` submission, folds\n * the draft's non-sensitive values into the card, and sends it through `sendPayments`. It also owns\n * `add_payment_info`, which needs the cart total `CardEntry` no longer reads.\n */\n\nexport type PaymentCartData = {\n total?: { amount: number; currencyCode: string } | null;\n} | null;\n\n/** The `add_payment_info` payment type for a card group; `undefined` for any other group (T140). */\nfunction paymentTypeOf(groupName: string | undefined): 'Credit Card' | 'Debit Card' | undefined {\n if (groupName === 'creditCardPaymentGroup') return 'Credit Card';\n if (groupName === 'debitCardPaymentGroup') return 'Debit Card';\n return undefined;\n}\n\n/** Mirrors `usePaymentActions`' own infrastructure-failure code for the same underlying meaning. */\nconst MISSING_GATEWAY_ACTION_ERROR: PaymentUserError = {\n code: PAYMENT_NETWORK_ERROR_CODE,\n message: 'Unable to complete the request. Please try again.',\n};\n\ntype PaymentCardCaptureProviderProps = Readonly<{\n selection: PaymentMethodSelection;\n cart: PaymentCartData | undefined;\n $actions: ComponentActions | undefined;\n children: ReactNode;\n}>;\n\nexport function PaymentCardCaptureProvider({ selection, cart, $actions, children }: PaymentCardCaptureProviderProps) {\n const { register } = usePaymentSubmission();\n // Read at submission and analytics time, never captured by a closure: the place-order sequence must\n // fold the draft as it stands then, and a keystroke must not re-render the section.\n const latest = useRef({ selection, $actions });\n useLayoutEffect(() => {\n latest.current = { selection, $actions };\n });\n\n const total = cart?.total ?? null;\n const totalRef = useRef(total);\n const activeCaptures = useRef(0);\n const analyticsDispatched = useRef(false);\n\n // `add_payment_info` (T2), once per root, when a card capture is mounted and the cart total has\n // resolved. T140, Decision 5: `paymentType` comes from whatever is selected when it fires, and the key\n // is absent otherwise; a later selection never re-fires it.\n const dispatchAddPaymentInfo = useCallback(() => {\n const currentTotal = totalRef.current;\n if (analyticsDispatched.current || activeCaptures.current === 0 || !currentTotal) return;\n const track = latest.current.$actions?.analytics?.track;\n if (!track) return;\n const { methods, selectedMethodId } = latest.current.selection;\n const paymentType = paymentTypeOf(methods.find((method) => method.id === selectedMethodId)?.groupName);\n track({\n name: 'add_payment_info',\n value: { amount: currentTotal.amount, currencyCode: currentTotal.currencyCode },\n // The key itself must be absent when nothing resolves to a known card kind — never a present\n // key holding `undefined` (toEcommercePayload's own doc comment carries the rest of this rule).\n ...(paymentType ? { paymentType } : {}),\n });\n analyticsDispatched.current = true;\n }, []);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: `$actions` re-runs the dispatch when the analytics namespace arrives after the total; the dispatch reads it through `latest`.\n useEffect(() => {\n totalRef.current = total;\n dispatchAddPaymentInfo();\n }, [total, $actions, dispatchAddPaymentInfo]);\n\n const registerCapture = useCallback(\n (capture: CardCapture, preflight: CardCapturePreflight) => {\n const submit: PaymentSubmission = (transaction) =>\n capture(async (card) => {\n const sendPayments = latest.current.$actions?.payment?.sendPayments;\n if (!sendPayments) {\n console.error('[CardEntry] the payment action namespace is not injected');\n return [MISSING_GATEWAY_ACTION_ERROR];\n }\n const { draft } = latest.current.selection;\n return sendPayments({\n transaction,\n card: {\n kind: 'new',\n ...card,\n document: draft.document ?? undefined,\n cardLabel: draft.cardLabel ?? undefined,\n saveCard: draft.saveCard ?? undefined,\n },\n });\n });\n const unregister = register('new-card', submit, preflight);\n activeCaptures.current += 1;\n dispatchAddPaymentInfo();\n return () => {\n activeCaptures.current -= 1;\n unregister();\n };\n },\n [register, dispatchAddPaymentInfo],\n );\n\n const {\n methods,\n placing,\n selectMethod,\n selectNewCard,\n setDraftBin,\n draft,\n cardPaymentCollapsed,\n cardModeActive,\n selectedMethodId,\n selectionOutOfScope,\n selectedMethod,\n } = selection;\n // T191 (FR-13, Decision 8): classified with the engine's own `isCardMethod`, so the card form's\n // refusal and the collapse can never disagree about a method.\n const selectionNonCard = selectedMethod !== null && !isCardMethod(selectedMethod);\n\n const value = useMemo<CardCaptureContextValue>(\n () => ({\n networks: methods,\n busy: placing,\n clearOn: 'success',\n registerCapture,\n selection: {\n selectMethod,\n selectNewCard,\n setDraftBin,\n path: draft.path,\n bin: draft.bin,\n collapsed: cardPaymentCollapsed,\n cardModeActive,\n selectedMethodId,\n selectionOutOfScope,\n selectionNonCard,\n },\n }),\n [\n methods,\n placing,\n registerCapture,\n selectMethod,\n selectNewCard,\n setDraftBin,\n draft.path,\n draft.bin,\n cardPaymentCollapsed,\n cardModeActive,\n selectedMethodId,\n selectionOutOfScope,\n selectionNonCard,\n ],\n );\n\n return <CardCaptureContext value={value}>{children}</CardCaptureContext>;\n}\n"],"mappings":";;;;;;;;;AA2BA,SAAS,cAAc,WAAyE;CAC9F,IAAI,cAAc,0BAA0B,OAAO;CACnD,IAAI,cAAc,yBAAyB,OAAO;AAEpD;;AAGA,MAAM,+BAAiD;CACrD,MAAM;CACN,SAAS;AACX;AASA,SAAgB,2BAA2B,EAAE,WAAW,MAAM,UAAU,YAA6C;CACnH,MAAM,EAAE,aAAa,qBAAqB;CAG1C,MAAM,SAAS,OAAO;EAAE;EAAW;CAAS,CAAC;CAC7C,sBAAsB;EACpB,OAAO,UAAU;GAAE;GAAW;EAAS;CACzC,CAAC;CAED,MAAM,QAAQ,MAAM,SAAS;CAC7B,MAAM,WAAW,OAAO,KAAK;CAC7B,MAAM,iBAAiB,OAAO,CAAC;CAC/B,MAAM,sBAAsB,OAAO,KAAK;CAKxC,MAAM,yBAAyB,kBAAkB;EAC/C,MAAM,eAAe,SAAS;EAC9B,IAAI,oBAAoB,WAAW,eAAe,YAAY,KAAK,CAAC,cAAc;EAClF,MAAM,QAAQ,OAAO,QAAQ,UAAU,WAAW;EAClD,IAAI,CAAC,OAAO;EACZ,MAAM,EAAE,SAAS,qBAAqB,OAAO,QAAQ;EACrD,MAAM,cAAc,cAAc,QAAQ,MAAM,WAAW,OAAO,OAAO,gBAAgB,CAAC,EAAE,SAAS;EACrG,MAAM;GACJ,MAAM;GACN,OAAO;IAAE,QAAQ,aAAa;IAAQ,cAAc,aAAa;GAAa;GAG9E,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;EACvC,CAAC;EACD,oBAAoB,UAAU;CAChC,GAAG,CAAC,CAAC;CAGL,gBAAgB;EACd,SAAS,UAAU;EACnB,uBAAuB;CACzB,GAAG;EAAC;EAAO;EAAU;CAAsB,CAAC;CAE5C,MAAM,kBAAkB,aACrB,SAAsB,cAAoC;EACzD,MAAM,UAA6B,gBACjC,QAAQ,OAAO,SAAS;GACtB,MAAM,eAAe,OAAO,QAAQ,UAAU,SAAS;GACvD,IAAI,CAAC,cAAc;IACjB,QAAQ,MAAM,0DAA0D;IACxE,OAAO,CAAC,4BAA4B;GACtC;GACA,MAAM,EAAE,UAAU,OAAO,QAAQ;GACjC,OAAO,aAAa;IAClB;IACA,MAAM;KACJ,MAAM;KACN,GAAG;KACH,UAAU,MAAM,YAAY,KAAA;KAC5B,WAAW,MAAM,aAAa,KAAA;KAC9B,UAAU,MAAM,YAAY,KAAA;IAC9B;GACF,CAAC;EACH,CAAC;EACH,MAAM,aAAa,SAAS,YAAY,QAAQ,SAAS;EACzD,eAAe,WAAW;EAC1B,uBAAuB;EACvB,aAAa;GACX,eAAe,WAAW;GAC1B,WAAW;EACb;CACF,GACA,CAAC,UAAU,sBAAsB,CACnC;CAEA,MAAM,EACJ,SACA,SACA,cACA,eACA,aACA,OACA,sBACA,gBACA,kBACA,qBACA,mBACE;CAGJ,MAAM,mBAAmB,mBAAmB,QAAQ,CAAC,aAAa,cAAc;CAEhF,MAAM,QAAQ,eACL;EACL,UAAU;EACV,MAAM;EACN,SAAS;EACT;EACA,WAAW;GACT;GACA;GACA;GACA,MAAM,MAAM;GACZ,KAAK,MAAM;GACX,WAAW;GACX;GACA;GACA;GACA;EACF;CACF,IACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA,MAAM;EACN,MAAM;EACN;EACA;EACA;EACA;EACA;CACF,CACF;CAEA,OAAO,oBAAC,oBAAD;EAA2B;EAAQ;CAA6B,CAAA;AACzE"}
@@ -0,0 +1,24 @@
1
+ import { ComponentDefinition } from "../../core/dist/define-component.js";
2
+ import "../../core/dist/index.js";
3
+ import { ComponentActions } from "../../sdk/actions/types.js";
4
+ import { PaymentSectionRootData } from "./payment.js";
5
+ import { ReactNode } from "react";
6
+ //#region src/matter/payment/payment-controller.d.ts
7
+ export type PaymentSectionControllerProps = {
8
+ /**
9
+ * Scopes the method list to a **set** of `PaymentMethod.groupName` values — a method is in scope
10
+ * when its `groupName` is a member (T140, FR-1, Decision 1). A single string is still accepted and
11
+ * scopes to exactly that one group. An empty string scopes nothing.
12
+ */
13
+ group?: string | readonly string[];
14
+ /** Keyed `{ payment, cart }` forwarded by the reactive runtime (arity-2 fragment, ADR-0015). */
15
+ $data?: PaymentSectionRootData;
16
+ /** Compiler-injected action surface; carries `payment.setMethod`, `payment.sendPayments`, and `analytics.track`. */
17
+ $actions?: ComponentActions;
18
+ /** The single overlay `Root` to control — a `DialogRoot` (modal) or `DrawerRoot` (slide-in). */
19
+ children?: ReactNode;
20
+ };
21
+ export declare const PaymentSectionControllerDefinition: ComponentDefinition<PaymentSectionControllerProps>;
22
+ export declare function PaymentSectionController({ group, $data: $dataProp, $actions: $actionsProp, children }: Readonly<PaymentSectionControllerProps>): string | number | bigint | boolean | import("react/jsx-runtime").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null | undefined;
23
+ //#endregion
24
+ //# sourceMappingURL=payment-controller.d.ts.map