@vtex/faststore 0.0.8 → 0.0.11

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 (568) hide show
  1. package/CHANGELOG.md +101 -0
  2. package/README.md +0 -1
  3. package/dist/actions.json +48 -1
  4. package/dist/catalog.json +1397 -1594
  5. package/dist/cli/index.d.ts +66 -28
  6. package/dist/cli/index.js +7371 -512
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/cli/server-runtime.js +41 -60
  9. package/dist/cli/server-runtime.js.map +1 -1
  10. package/dist/client.d.ts +3 -3
  11. package/dist/client.js +2 -2
  12. package/dist/components.d.ts +67 -56
  13. package/dist/components.js +64 -53
  14. package/dist/content-types.json +5886 -555
  15. package/dist/core/dist/define-component.d.ts +7 -2
  16. package/dist/core/dist/define-content-type.d.ts +50 -2
  17. package/dist/core/dist/define-content-type.js +107 -3
  18. package/dist/core/dist/define-content-type.js.map +1 -1
  19. package/dist/core/dist/index.d.ts +2 -2
  20. package/dist/docs/access-code-field.md +1 -1
  21. package/dist/docs/access-code-sign-in.md +126 -77
  22. package/dist/docs/account-address-book.md +210 -95
  23. package/dist/docs/account-entry-point.md +4 -4
  24. package/dist/docs/account-menu.md +16 -17
  25. package/dist/docs/account-saved-cards.md +306 -0
  26. package/dist/docs/alert-dialog.md +3 -1
  27. package/dist/docs/avatar.md +4 -2
  28. package/dist/docs/billing-address.md +99 -30
  29. package/dist/docs/binding-switcher.md +157 -0
  30. package/dist/docs/buy-button.md +7 -8
  31. package/dist/docs/card-entry.md +42 -29
  32. package/dist/docs/card-summary.md +185 -0
  33. package/dist/docs/cardholder-document.md +5 -3
  34. package/dist/docs/carousel.md +5 -4
  35. package/dist/docs/cart-destination.md +251 -0
  36. package/dist/docs/cart-item.md +48 -15
  37. package/dist/docs/cart-totalizer.md +8 -6
  38. package/dist/docs/cart.md +240 -39
  39. package/dist/docs/challenge-return.md +135 -0
  40. package/dist/docs/client-preferences-fieldset.md +10 -5
  41. package/dist/docs/client-preferences.md +31 -8
  42. package/dist/docs/client-profile-fieldset.md +20 -6
  43. package/dist/docs/client-profile.md +12 -3
  44. package/dist/docs/copy-text.md +9 -7
  45. package/dist/docs/document-field.md +1 -1
  46. package/dist/docs/email-field.md +2 -1
  47. package/dist/docs/email-identification.md +12 -2
  48. package/dist/docs/footer-copyright.md +4 -2
  49. package/dist/docs/footer-social.md +1 -1
  50. package/dist/docs/form.md +54 -18
  51. package/dist/docs/free-shipping-progress.md +18 -13
  52. package/dist/docs/identity-edit.md +8 -2
  53. package/dist/docs/identity-summary.md +18 -5
  54. package/dist/docs/incentives.md +2 -2
  55. package/dist/docs/installments.md +46 -32
  56. package/dist/docs/logo.md +1 -1
  57. package/dist/docs/my-account-profile.md +16 -11
  58. package/dist/docs/navigation-link.md +5 -3
  59. package/dist/docs/order-cancel.md +3 -1
  60. package/dist/docs/order-delivery-address.md +7 -5
  61. package/dist/docs/order-delivery-group.md +1 -3
  62. package/dist/docs/order-details-back.md +1 -1
  63. package/dist/docs/order-details-header.md +1 -4
  64. package/dist/docs/order-invoice-download.md +60 -0
  65. package/dist/docs/order-list-row.md +1 -1
  66. package/dist/docs/order-list-search.md +13 -20
  67. package/dist/docs/order-list-views.md +11 -17
  68. package/dist/docs/order-list.md +12 -5
  69. package/dist/docs/order-payment.md +9 -7
  70. package/dist/docs/order-placed-date.md +1 -1
  71. package/dist/docs/order-placed-payment-summary.md +28 -15
  72. package/dist/docs/order-placed.md +49 -14
  73. package/dist/docs/order-progress.md +1 -1
  74. package/dist/docs/order-status-badge.md +20 -9
  75. package/dist/docs/order-summary.md +2 -2
  76. package/dist/docs/order-tracking-link.md +58 -0
  77. package/dist/docs/payment-method-badges.md +32 -6
  78. package/dist/docs/payment-methods.md +2 -2
  79. package/dist/docs/payment.md +36 -17
  80. package/dist/docs/phone-field.md +4 -4
  81. package/dist/docs/place-order.md +84 -36
  82. package/dist/docs/postal-code-field.md +3 -1
  83. package/dist/docs/pricing.md +21 -23
  84. package/dist/docs/product-gallery.md +19 -20
  85. package/dist/docs/product-information.md +14 -10
  86. package/dist/docs/product-list.md +7 -6
  87. package/dist/docs/product-shelf.md +7 -1
  88. package/dist/docs/promotion.md +4 -2
  89. package/dist/docs/region.md +23 -15
  90. package/dist/docs/save-card.md +7 -6
  91. package/dist/docs/saved-cards.md +65 -36
  92. package/dist/docs/select.md +3 -4
  93. package/dist/docs/shipping-address.md +57 -21
  94. package/dist/docs/shipping-simulator.md +53 -15
  95. package/dist/docs/sign-in-disclosure.md +176 -0
  96. package/dist/docs/sign-in-form.md +78 -47
  97. package/dist/docs/sign-in-redirect.md +5 -3
  98. package/dist/docs/sign-in.md +173 -0
  99. package/dist/docs/toast.md +45 -16
  100. package/dist/docs/variant-selector.md +15 -11
  101. package/dist/graphql/types.d.ts +4 -14
  102. package/dist/index.d.ts +3 -2
  103. package/dist/internal/client.d.ts +4 -3
  104. package/dist/internal/client.js +5 -2
  105. package/dist/internal/server.d.ts +10 -5
  106. package/dist/internal/server.js +9 -4
  107. package/dist/matter/access-code-sign-in/access-code-sign-in.d.ts +250 -66
  108. package/dist/matter/access-code-sign-in/access-code-sign-in.js +459 -188
  109. package/dist/matter/access-code-sign-in/access-code-sign-in.js.map +1 -1
  110. package/dist/matter/account-address-book/account-address-book-delete.d.ts +78 -40
  111. package/dist/matter/account-address-book/account-address-book-delete.js +86 -50
  112. package/dist/matter/account-address-book/account-address-book-delete.js.map +1 -1
  113. package/dist/matter/account-address-book/account-address-book-deliverability.d.ts +21 -4
  114. package/dist/matter/account-address-book/account-address-book-deliverability.js +45 -17
  115. package/dist/matter/account-address-book/account-address-book-deliverability.js.map +1 -1
  116. package/dist/matter/account-address-book/account-address-book-form-parts.d.ts +74 -19
  117. package/dist/matter/account-address-book/account-address-book-form-parts.js +77 -26
  118. package/dist/matter/account-address-book/account-address-book-form-parts.js.map +1 -1
  119. package/dist/matter/account-address-book/account-address-book-profile-notice.d.ts +27 -6
  120. package/dist/matter/account-address-book/account-address-book-profile-notice.js +55 -28
  121. package/dist/matter/account-address-book/account-address-book-profile-notice.js.map +1 -1
  122. package/dist/matter/account-address-book/account-address-book.d.ts +67 -14
  123. package/dist/matter/account-address-book/account-address-book.js +74 -33
  124. package/dist/matter/account-address-book/account-address-book.js.map +1 -1
  125. package/dist/matter/account-address-book/account-address-country-switch.js +3 -3
  126. package/dist/matter/account-address-book/account-address-country-switch.js.map +1 -1
  127. package/dist/matter/account-address-book/account-address-country.d.ts +66 -20
  128. package/dist/matter/account-address-book/account-address-country.js +83 -49
  129. package/dist/matter/account-address-book/account-address-country.js.map +1 -1
  130. package/dist/matter/account-address-book/account-address-form-context.d.ts +8 -4
  131. package/dist/matter/account-address-book/account-address-form-context.js.map +1 -1
  132. package/dist/matter/account-address-book/account-address-form.d.ts +468 -45
  133. package/dist/matter/account-address-book/account-address-form.js +518 -155
  134. package/dist/matter/account-address-book/account-address-form.js.map +1 -1
  135. package/dist/matter/account-address-book/account-address-record.d.ts +27 -6
  136. package/dist/matter/account-address-book/account-address-record.js +32 -17
  137. package/dist/matter/account-address-book/account-address-record.js.map +1 -1
  138. package/dist/matter/account-menu/account-menu.d.ts +34 -17
  139. package/dist/matter/account-menu/account-menu.js +46 -54
  140. package/dist/matter/account-menu/account-menu.js.map +1 -1
  141. package/dist/matter/account-menu-identity/account-menu-identity.js +1 -1
  142. package/dist/matter/account-saved-cards/account-saved-cards.d.ts +463 -0
  143. package/dist/matter/account-saved-cards/account-saved-cards.js +1011 -0
  144. package/dist/matter/account-saved-cards/account-saved-cards.js.map +1 -0
  145. package/dist/matter/billing-address/billing-address.d.ts +156 -74
  146. package/dist/matter/billing-address/billing-address.js +227 -156
  147. package/dist/matter/billing-address/billing-address.js.map +1 -1
  148. package/dist/matter/breadcrumb/breadcrumb.d.ts +21 -3
  149. package/dist/matter/breadcrumb/breadcrumb.js +24 -9
  150. package/dist/matter/breadcrumb/breadcrumb.js.map +1 -1
  151. package/dist/matter/buy-button/buy-button.d.ts +8 -11
  152. package/dist/matter/buy-button/buy-button.js +10 -19
  153. package/dist/matter/buy-button/buy-button.js.map +1 -1
  154. package/dist/matter/card-entry/card-capture.js +12 -4
  155. package/dist/matter/card-entry/card-capture.js.map +1 -1
  156. package/dist/matter/card-entry/card-entry.d.ts +123 -32
  157. package/dist/matter/card-entry/card-entry.js +348 -221
  158. package/dist/matter/card-entry/card-entry.js.map +1 -1
  159. package/dist/matter/cart/cart-namespace.d.ts +11 -1
  160. package/dist/matter/cart/cart-namespace.js +11 -1
  161. package/dist/matter/cart/cart-namespace.js.map +1 -1
  162. package/dist/matter/cart/cart.d.ts +416 -45
  163. package/dist/matter/cart/cart.js +782 -101
  164. package/dist/matter/cart/cart.js.map +1 -1
  165. package/dist/matter/cart-destination/cart-destination.d.ts +233 -0
  166. package/dist/matter/cart-destination/cart-destination.js +703 -0
  167. package/dist/matter/cart-destination/cart-destination.js.map +1 -0
  168. package/dist/matter/cart-item/cart-item.d.ts +145 -16
  169. package/dist/matter/cart-item/cart-item.js +230 -54
  170. package/dist/matter/cart-item/cart-item.js.map +1 -1
  171. package/dist/matter/cart-item-compositions/cart-item-compositions.js +1 -1
  172. package/dist/matter/cart-item-compositions/cart-item-compositions.js.map +1 -1
  173. package/dist/matter/cart-item-customizations/cart-item-customizations.js +1 -1
  174. package/dist/matter/cart-item-customizations/cart-item-customizations.js.map +1 -1
  175. package/dist/matter/cart-item-discounts/cart-item-discounts.js +1 -1
  176. package/dist/matter/cart-item-discounts/cart-item-discounts.js.map +1 -1
  177. package/dist/matter/cart-item-seller-info/cart-item-seller-info.js +1 -1
  178. package/dist/matter/cart-item-seller-info/cart-item-seller-info.js.map +1 -1
  179. package/dist/matter/cart-item-variations/cart-item-variations.js +1 -1
  180. package/dist/matter/cart-item-variations/cart-item-variations.js.map +1 -1
  181. package/dist/matter/cart-totalizer/cart-totalizer.d.ts +43 -9
  182. package/dist/matter/cart-totalizer/cart-totalizer.js +49 -19
  183. package/dist/matter/cart-totalizer/cart-totalizer.js.map +1 -1
  184. package/dist/matter/challenge-return/challenge-return.d.ts +62 -0
  185. package/dist/matter/challenge-return/challenge-return.js +241 -0
  186. package/dist/matter/challenge-return/challenge-return.js.map +1 -0
  187. package/dist/matter/client-preferences/client-preferences-fieldset.d.ts +86 -17
  188. package/dist/matter/client-preferences/client-preferences-fieldset.js +126 -54
  189. package/dist/matter/client-preferences/client-preferences-fieldset.js.map +1 -1
  190. package/dist/matter/client-preferences/client-preferences.d.ts +50 -7
  191. package/dist/matter/client-preferences/client-preferences.js +84 -15
  192. package/dist/matter/client-preferences/client-preferences.js.map +1 -1
  193. package/dist/matter/client-profile/client-profile.d.ts +44 -9
  194. package/dist/matter/client-profile/client-profile.js +64 -24
  195. package/dist/matter/client-profile/client-profile.js.map +1 -1
  196. package/dist/matter/client-profile-form/client-profile-fieldset.d.ts +119 -22
  197. package/dist/matter/client-profile-form/client-profile-fieldset.js +161 -73
  198. package/dist/matter/client-profile-form/client-profile-fieldset.js.map +1 -1
  199. package/dist/matter/delivery-options/delivery-options-accordion.d.ts +49 -4
  200. package/dist/matter/delivery-options/delivery-options-accordion.js +67 -14
  201. package/dist/matter/delivery-options/delivery-options-accordion.js.map +1 -1
  202. package/dist/matter/delivery-options/delivery-options-controller.d.ts +69 -9
  203. package/dist/matter/delivery-options/delivery-options-controller.js +80 -14
  204. package/dist/matter/delivery-options/delivery-options-controller.js.map +1 -1
  205. package/dist/matter/delivery-options/delivery-options.d.ts +37 -2
  206. package/dist/matter/delivery-options/delivery-options.js +75 -12
  207. package/dist/matter/delivery-options/delivery-options.js.map +1 -1
  208. package/dist/matter/delivery-options/use-delivery-option-selection.d.ts +1 -0
  209. package/dist/matter/delivery-options/use-delivery-option-selection.js +1 -1
  210. package/dist/matter/delivery-options/use-delivery-option-selection.js.map +1 -1
  211. package/dist/matter/email-identification/email-identification.d.ts +25 -3
  212. package/dist/matter/email-identification/email-identification.js +49 -12
  213. package/dist/matter/email-identification/email-identification.js.map +1 -1
  214. package/dist/matter/free-shipping-progress/free-shipping-progress.d.ts +85 -10
  215. package/dist/matter/free-shipping-progress/free-shipping-progress.js +153 -53
  216. package/dist/matter/free-shipping-progress/free-shipping-progress.js.map +1 -1
  217. package/dist/matter/identity-edit/identity-edit.d.ts +52 -10
  218. package/dist/matter/identity-edit/identity-edit.js +47 -19
  219. package/dist/matter/identity-edit/identity-edit.js.map +1 -1
  220. package/dist/matter/identity-summary/identity-summary.d.ts +76 -22
  221. package/dist/matter/identity-summary/identity-summary.js +95 -45
  222. package/dist/matter/identity-summary/identity-summary.js.map +1 -1
  223. package/dist/matter/installments/installments-select.d.ts +98 -16
  224. package/dist/matter/installments/installments-select.js +156 -48
  225. package/dist/matter/installments/installments-select.js.map +1 -1
  226. package/dist/matter/installments/installments.d.ts +96 -16
  227. package/dist/matter/installments/installments.js +115 -40
  228. package/dist/matter/installments/installments.js.map +1 -1
  229. package/dist/matter/installments/use-installments-selection.d.ts +1 -0
  230. package/dist/matter/installments/use-installments-selection.js +8 -3
  231. package/dist/matter/installments/use-installments-selection.js.map +1 -1
  232. package/dist/matter/my-account-profile/my-account-profile.d.ts +358 -103
  233. package/dist/matter/my-account-profile/my-account-profile.js +370 -174
  234. package/dist/matter/my-account-profile/my-account-profile.js.map +1 -1
  235. package/dist/matter/order-cancel/order-cancel.d.ts +65 -19
  236. package/dist/matter/order-cancel/order-cancel.js +118 -51
  237. package/dist/matter/order-cancel/order-cancel.js.map +1 -1
  238. package/dist/matter/order-delivery-address/order-delivery-address.d.ts +8 -18
  239. package/dist/matter/order-delivery-address/order-delivery-address.js +14 -23
  240. package/dist/matter/order-delivery-address/order-delivery-address.js.map +1 -1
  241. package/dist/matter/order-delivery-group/order-delivery-group.d.ts +40 -24
  242. package/dist/matter/order-delivery-group/order-delivery-group.js +64 -74
  243. package/dist/matter/order-delivery-group/order-delivery-group.js.map +1 -1
  244. package/dist/matter/order-details-header/order-details-header.d.ts +32 -15
  245. package/dist/matter/order-details-header/order-details-header.js +66 -58
  246. package/dist/matter/order-details-header/order-details-header.js.map +1 -1
  247. package/dist/matter/order-invoice-download/order-invoice-download.d.ts +78 -0
  248. package/dist/matter/order-invoice-download/order-invoice-download.js +173 -0
  249. package/dist/matter/order-invoice-download/order-invoice-download.js.map +1 -0
  250. package/dist/matter/order-list/order-list.d.ts +164 -19
  251. package/dist/matter/order-list/order-list.js +229 -87
  252. package/dist/matter/order-list/order-list.js.map +1 -1
  253. package/dist/matter/order-list-row/order-list-row.d.ts +6 -1
  254. package/dist/matter/order-list-row/order-list-row.js +3 -2
  255. package/dist/matter/order-list-row/order-list-row.js.map +1 -1
  256. package/dist/matter/order-list-search/order-list-search.d.ts +18 -26
  257. package/dist/matter/order-list-search/order-list-search.js +30 -69
  258. package/dist/matter/order-list-search/order-list-search.js.map +1 -1
  259. package/dist/matter/order-list-views/order-list-views.d.ts +15 -22
  260. package/dist/matter/order-list-views/order-list-views.js +22 -68
  261. package/dist/matter/order-list-views/order-list-views.js.map +1 -1
  262. package/dist/matter/order-payment/order-payment.d.ts +33 -10
  263. package/dist/matter/order-payment/order-payment.js +44 -39
  264. package/dist/matter/order-payment/order-payment.js.map +1 -1
  265. package/dist/matter/order-placed/order-placed-deliveries.d.ts +144 -12
  266. package/dist/matter/order-placed/order-placed-deliveries.js +115 -40
  267. package/dist/matter/order-placed/order-placed-deliveries.js.map +1 -1
  268. package/dist/matter/order-placed/order-placed.d.ts +198 -31
  269. package/dist/matter/order-placed/order-placed.js +230 -139
  270. package/dist/matter/order-placed/order-placed.js.map +1 -1
  271. package/dist/matter/order-placed-date/order-placed-date.d.ts +5 -8
  272. package/dist/matter/order-placed-date/order-placed-date.js +7 -8
  273. package/dist/matter/order-placed-date/order-placed-date.js.map +1 -1
  274. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.d.ts +90 -17
  275. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js +93 -117
  276. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js.map +1 -1
  277. package/dist/matter/order-progress/order-progress.d.ts +19 -8
  278. package/dist/matter/order-progress/order-progress.js +37 -45
  279. package/dist/matter/order-progress/order-progress.js.map +1 -1
  280. package/dist/matter/order-summary/order-summary.d.ts +19 -9
  281. package/dist/matter/order-summary/order-summary.js +38 -44
  282. package/dist/matter/order-summary/order-summary.js.map +1 -1
  283. package/dist/matter/order-tracking-link/order-tracking-link.d.ts +43 -0
  284. package/dist/matter/order-tracking-link/order-tracking-link.js +107 -0
  285. package/dist/matter/order-tracking-link/order-tracking-link.js.map +1 -0
  286. package/dist/matter/payment/payment-card-capture.js +17 -10
  287. package/dist/matter/payment/payment-card-capture.js.map +1 -1
  288. package/dist/matter/payment/payment-controller.d.ts +4 -2
  289. package/dist/matter/payment/payment-controller.js +30 -9
  290. package/dist/matter/payment/payment-controller.js.map +1 -1
  291. package/dist/matter/payment/payment-method-badges.js +1 -1
  292. package/dist/matter/payment/payment.d.ts +69 -19
  293. package/dist/matter/payment/payment.js +101 -35
  294. package/dist/matter/payment/payment.js.map +1 -1
  295. package/dist/matter/payment/use-payment-method-selection.d.ts +25 -4
  296. package/dist/matter/payment/use-payment-method-selection.js +94 -32
  297. package/dist/matter/payment/use-payment-method-selection.js.map +1 -1
  298. package/dist/matter/payment/use-payment-submit-gate.d.ts +14 -3
  299. package/dist/matter/payment/use-payment-submit-gate.js +47 -8
  300. package/dist/matter/payment/use-payment-submit-gate.js.map +1 -1
  301. package/dist/matter/place-order/place-order.d.ts +171 -57
  302. package/dist/matter/place-order/place-order.js +376 -185
  303. package/dist/matter/place-order/place-order.js.map +1 -1
  304. package/dist/matter/pricing/pricing.d.ts +152 -13
  305. package/dist/matter/pricing/pricing.js +153 -52
  306. package/dist/matter/pricing/pricing.js.map +1 -1
  307. package/dist/matter/product-information/product-information.d.ts +27 -4
  308. package/dist/matter/product-information/product-information.js +32 -15
  309. package/dist/matter/product-information/product-information.js.map +1 -1
  310. package/dist/matter/product-list/product-list.d.ts +24 -6
  311. package/dist/matter/product-list/product-list.js +32 -26
  312. package/dist/matter/product-list/product-list.js.map +1 -1
  313. package/dist/matter/product-shelf/product-shelf.d.ts +8 -3
  314. package/dist/matter/product-shelf/product-shelf.js +111 -10
  315. package/dist/matter/product-shelf/product-shelf.js.map +1 -1
  316. package/dist/matter/region/region.d.ts +33 -16
  317. package/dist/matter/region/region.js +59 -52
  318. package/dist/matter/region/region.js.map +1 -1
  319. package/dist/matter/save-card/save-card.d.ts +43 -8
  320. package/dist/matter/save-card/save-card.js +61 -23
  321. package/dist/matter/save-card/save-card.js.map +1 -1
  322. package/dist/matter/saved-cards/saved-cards.d.ts +113 -21
  323. package/dist/matter/saved-cards/saved-cards.js +349 -77
  324. package/dist/matter/saved-cards/saved-cards.js.map +1 -1
  325. package/dist/matter/shipping-address/address-payload.js +25 -0
  326. package/dist/matter/shipping-address/address-payload.js.map +1 -0
  327. package/dist/matter/shipping-address/country-descriptors.js +71 -71
  328. package/dist/matter/shipping-address/country-descriptors.js.map +1 -1
  329. package/dist/matter/shipping-address/library-country-descriptors.js +136 -0
  330. package/dist/matter/shipping-address/library-country-descriptors.js.map +1 -0
  331. package/dist/matter/shipping-address/shipping-address.d.ts +312 -45
  332. package/dist/matter/shipping-address/shipping-address.js +443 -188
  333. package/dist/matter/shipping-address/shipping-address.js.map +1 -1
  334. package/dist/matter/shipping-simulator/shipping-simulator.d.ts +251 -25
  335. package/dist/matter/shipping-simulator/shipping-simulator.js +261 -51
  336. package/dist/matter/shipping-simulator/shipping-simulator.js.map +1 -1
  337. package/dist/matter/sign-in/sign-in.d.ts +108 -0
  338. package/dist/matter/sign-in/sign-in.js +285 -0
  339. package/dist/matter/sign-in/sign-in.js.map +1 -0
  340. package/dist/matter/sign-in-disclosure/sign-in-disclosure.d.ts +81 -0
  341. package/dist/matter/sign-in-disclosure/sign-in-disclosure.js +232 -0
  342. package/dist/matter/sign-in-disclosure/sign-in-disclosure.js.map +1 -0
  343. package/dist/matter/sign-in-form/sign-in-form.d.ts +93 -62
  344. package/dist/matter/sign-in-form/sign-in-form.js +134 -144
  345. package/dist/matter/sign-in-form/sign-in-form.js.map +1 -1
  346. package/dist/particles/binding-switcher/binding-switcher.d.ts +59 -0
  347. package/dist/particles/binding-switcher/binding-switcher.js +194 -0
  348. package/dist/particles/binding-switcher/binding-switcher.js.map +1 -0
  349. package/dist/particles/card-summary/card-network.d.ts +17 -0
  350. package/dist/particles/card-summary/card-network.js +25 -0
  351. package/dist/particles/card-summary/card-network.js.map +1 -0
  352. package/dist/particles/card-summary/card-summary.d.ts +113 -0
  353. package/dist/particles/card-summary/card-summary.js +221 -0
  354. package/dist/particles/card-summary/card-summary.js.map +1 -0
  355. package/dist/particles/carousel/carousel.d.ts +99 -11
  356. package/dist/particles/carousel/carousel.js +103 -35
  357. package/dist/particles/carousel/carousel.js.map +1 -1
  358. package/dist/particles/copy-text/copy-text.js +5 -15
  359. package/dist/particles/copy-text/copy-text.js.map +1 -1
  360. package/dist/particles/dialog/dialog.d.ts +14 -6
  361. package/dist/particles/dialog/dialog.js +6 -3
  362. package/dist/particles/dialog/dialog.js.map +1 -1
  363. package/dist/particles/drawer/drawer.js +1 -1
  364. package/dist/particles/drawer/drawer.js.map +1 -1
  365. package/dist/particles/email-field/email-field.d.ts +2 -1
  366. package/dist/particles/email-field/email-field.js +2 -1
  367. package/dist/particles/email-field/email-field.js.map +1 -1
  368. package/dist/particles/footer-link-group/footer-link-group.js +1 -0
  369. package/dist/particles/footer-link-group/footer-link-group.js.map +1 -1
  370. package/dist/particles/footer-social/footer-social.d.ts +1 -1
  371. package/dist/particles/footer-social/footer-social.js +1 -1
  372. package/dist/particles/footer-social/footer-social.js.map +1 -1
  373. package/dist/particles/form/form.d.ts +85 -13
  374. package/dist/particles/form/form.js +91 -42
  375. package/dist/particles/form/form.js.map +1 -1
  376. package/dist/particles/gallery/gallery.d.ts +129 -12
  377. package/dist/particles/gallery/gallery.js +121 -34
  378. package/dist/particles/gallery/gallery.js.map +1 -1
  379. package/dist/particles/incentives/incentives.d.ts +1 -1
  380. package/dist/particles/incentives/incentives.js +1 -1
  381. package/dist/particles/incentives/incentives.js.map +1 -1
  382. package/dist/particles/order-details-back/order-details-back.d.ts +5 -3
  383. package/dist/particles/order-details-back/order-details-back.js +8 -10
  384. package/dist/particles/order-details-back/order-details-back.js.map +1 -1
  385. package/dist/particles/order-status-badge/order-status-badge.d.ts +3 -3
  386. package/dist/particles/order-status-badge/order-status-badge.js +4 -20
  387. package/dist/particles/order-status-badge/order-status-badge.js.map +1 -1
  388. package/dist/particles/password-field/password-field.d.ts +1 -1
  389. package/dist/particles/password-field/password-field.js +2 -4
  390. package/dist/particles/password-field/password-field.js.map +1 -1
  391. package/dist/particles/payment-methods/payment-methods.d.ts +1 -1
  392. package/dist/particles/payment-methods/payment-methods.js +1 -1
  393. package/dist/particles/payment-methods/payment-methods.js.map +1 -1
  394. package/dist/particles/phone-field/phone-field.d.ts +4 -4
  395. package/dist/particles/phone-field/phone-field.js +8 -11
  396. package/dist/particles/phone-field/phone-field.js.map +1 -1
  397. package/dist/particles/spinner/spinner.d.ts +24 -4
  398. package/dist/particles/spinner/spinner.js +25 -9
  399. package/dist/particles/spinner/spinner.js.map +1 -1
  400. package/dist/particles/switch/switch.d.ts +4 -1
  401. package/dist/particles/switch/switch.js +3 -1
  402. package/dist/particles/switch/switch.js.map +1 -1
  403. package/dist/particles/toast/toast.d.ts +49 -9
  404. package/dist/particles/toast/toast.js +81 -15
  405. package/dist/particles/toast/toast.js.map +1 -1
  406. package/dist/schema.graphql +461 -89
  407. package/dist/sdk/actions/index.d.ts +3 -1
  408. package/dist/sdk/actions/index.js +3 -1
  409. package/dist/sdk/actions/locale-action-provider.d.ts +16 -0
  410. package/dist/sdk/actions/locale-action-provider.js +20 -0
  411. package/dist/sdk/actions/locale-action-provider.js.map +1 -0
  412. package/dist/sdk/actions/saved-card-action-provider.d.ts +12 -0
  413. package/dist/sdk/actions/saved-card-action-provider.js +16 -0
  414. package/dist/sdk/actions/saved-card-action-provider.js.map +1 -0
  415. package/dist/sdk/actions/types.d.ts +6 -2
  416. package/dist/sdk/address/address-fields.js +4 -4
  417. package/dist/sdk/address/address-fields.js.map +1 -1
  418. package/dist/sdk/address/constants.js +1 -6
  419. package/dist/sdk/address/constants.js.map +1 -1
  420. package/dist/sdk/address/messages.js +44 -44
  421. package/dist/sdk/address/messages.js.map +1 -1
  422. package/dist/sdk/address/postal-code.js +15 -2
  423. package/dist/sdk/address/postal-code.js.map +1 -1
  424. package/dist/sdk/address/types.d.ts +5 -3
  425. package/dist/sdk/address/validate-address.js +3 -3
  426. package/dist/sdk/address/validate-address.js.map +1 -1
  427. package/dist/sdk/analytics/use-analytics-actions.d.ts +1 -1
  428. package/dist/sdk/analytics/use-analytics-actions.js.map +1 -1
  429. package/dist/sdk/cache/storefront-cache.js +2 -1
  430. package/dist/sdk/cache/storefront-cache.js.map +1 -1
  431. package/dist/sdk/cart/operations.d.ts +2 -1
  432. package/dist/sdk/cart/operations.js +55 -1
  433. package/dist/sdk/cart/operations.js.map +1 -1
  434. package/dist/sdk/cart/optimistic.d.ts +30 -7
  435. package/dist/sdk/cart/optimistic.js +78 -41
  436. package/dist/sdk/cart/optimistic.js.map +1 -1
  437. package/dist/sdk/cart/use-cart-actions.d.ts +7 -0
  438. package/dist/sdk/cart/use-cart-actions.js +61 -15
  439. package/dist/sdk/cart/use-cart-actions.js.map +1 -1
  440. package/dist/sdk/cart/use-cart-sync-sales-channel.js +70 -0
  441. package/dist/sdk/cart/use-cart-sync-sales-channel.js.map +1 -0
  442. package/dist/sdk/data/index.js +2 -2
  443. package/dist/sdk/data/reactive-list-item-data-provider.js +2 -2
  444. package/dist/sdk/generated/gql.js +16 -12
  445. package/dist/sdk/generated/gql.js.map +1 -1
  446. package/dist/sdk/generated/graphql.d.ts +197 -51
  447. package/dist/sdk/generated/graphql.js +2318 -91
  448. package/dist/sdk/generated/graphql.js.map +1 -1
  449. package/dist/sdk/i18n/format-error-code.js +23 -0
  450. package/dist/sdk/i18n/format-error-code.js.map +1 -0
  451. package/dist/sdk/i18n/message-formatter.d.ts +22 -0
  452. package/dist/sdk/i18n/message-formatter.js +140 -0
  453. package/dist/sdk/i18n/message-formatter.js.map +1 -0
  454. package/dist/sdk/i18n/use-message-formatter.d.ts +8 -0
  455. package/dist/sdk/i18n/use-message-formatter.js +18 -0
  456. package/dist/sdk/i18n/use-message-formatter.js.map +1 -0
  457. package/dist/sdk/identity/use-identity-actions.js.map +1 -1
  458. package/dist/sdk/locale/binding-switch-provider.d.ts +13 -0
  459. package/dist/sdk/locale/binding-switch-provider.js +20 -0
  460. package/dist/sdk/locale/binding-switch-provider.js.map +1 -0
  461. package/dist/sdk/locale/use-locale-actions.d.ts +16 -0
  462. package/dist/sdk/locale/use-locale-actions.js +70 -0
  463. package/dist/sdk/locale/use-locale-actions.js.map +1 -0
  464. package/dist/sdk/navigation/binding-switch.d.ts +39 -0
  465. package/dist/sdk/navigation/binding-switch.js +129 -0
  466. package/dist/sdk/navigation/binding-switch.js.map +1 -0
  467. package/dist/sdk/navigation/decode-route-params.d.ts +11 -0
  468. package/dist/sdk/navigation/decode-route-params.js +32 -0
  469. package/dist/sdk/navigation/decode-route-params.js.map +1 -0
  470. package/dist/sdk/navigation/redirect.d.ts +2 -0
  471. package/dist/sdk/navigation/redirect.js +19 -1
  472. package/dist/sdk/navigation/redirect.js.map +1 -1
  473. package/dist/sdk/navigation/types.d.ts +13 -0
  474. package/dist/sdk/payment/bin-format.js +20 -0
  475. package/dist/sdk/payment/bin-format.js.map +1 -0
  476. package/dist/sdk/payment/challenge-return.js +37 -0
  477. package/dist/sdk/payment/challenge-return.js.map +1 -0
  478. package/dist/sdk/payment/confirmation-last-four.js +39 -0
  479. package/dist/sdk/payment/confirmation-last-four.js.map +1 -0
  480. package/dist/sdk/payment/gateway.d.ts +1 -1
  481. package/dist/sdk/payment/gateway.js +8 -2
  482. package/dist/sdk/payment/gateway.js.map +1 -1
  483. package/dist/sdk/payment/operations.d.ts +1 -0
  484. package/dist/sdk/payment/operations.js +28 -6
  485. package/dist/sdk/payment/operations.js.map +1 -1
  486. package/dist/sdk/payment/resumed-purchase.d.ts +1 -0
  487. package/dist/sdk/payment/resumed-purchase.js +119 -0
  488. package/dist/sdk/payment/resumed-purchase.js.map +1 -0
  489. package/dist/sdk/payment/security-code-mask.js +16 -23
  490. package/dist/sdk/payment/security-code-mask.js.map +1 -1
  491. package/dist/sdk/payment/submission-registry.js +6 -2
  492. package/dist/sdk/payment/submission-registry.js.map +1 -1
  493. package/dist/sdk/payment/use-payment-actions.d.ts +18 -3
  494. package/dist/sdk/payment/use-payment-actions.js +23 -3
  495. package/dist/sdk/payment/use-payment-actions.js.map +1 -1
  496. package/dist/sdk/payment/use-place-order.d.ts +10 -2
  497. package/dist/sdk/payment/use-place-order.js +138 -76
  498. package/dist/sdk/payment/use-place-order.js.map +1 -1
  499. package/dist/sdk/provider/storefront-provider.d.ts +1 -7
  500. package/dist/sdk/provider/storefront-provider.js +4 -11
  501. package/dist/sdk/provider/storefront-provider.js.map +1 -1
  502. package/dist/sdk/saved-cards/operations.d.ts +16 -0
  503. package/dist/sdk/saved-cards/operations.js +41 -0
  504. package/dist/sdk/saved-cards/operations.js.map +1 -0
  505. package/dist/sdk/saved-cards/use-saved-card-actions.d.ts +26 -0
  506. package/dist/sdk/saved-cards/use-saved-card-actions.js +86 -0
  507. package/dist/sdk/saved-cards/use-saved-card-actions.js.map +1 -0
  508. package/dist/sdk/seo/entity-landing.d.ts +24 -0
  509. package/dist/sdk/seo/entity-landing.js +30 -0
  510. package/dist/sdk/seo/entity-landing.js.map +1 -0
  511. package/dist/sdk/seo/page-alternates.d.ts +8 -5
  512. package/dist/sdk/seo/page-alternates.js +11 -14
  513. package/dist/sdk/seo/page-alternates.js.map +1 -1
  514. package/dist/sdk/seo/search-landing.d.ts +42 -0
  515. package/dist/sdk/seo/search-landing.js +85 -0
  516. package/dist/sdk/seo/search-landing.js.map +1 -0
  517. package/dist/sdk/session/mock-session.d.ts +9 -0
  518. package/dist/sdk/session/mock-session.js +31 -0
  519. package/dist/sdk/session/mock-session.js.map +1 -0
  520. package/dist/sdk/session/read-server-session.d.ts +3 -0
  521. package/dist/sdk/session/read-server-session.js +5 -0
  522. package/dist/sdk/session/read-server-session.js.map +1 -1
  523. package/dist/sdk/session/return-path-segment.d.ts +10 -0
  524. package/dist/sdk/session/return-path-segment.js +15 -0
  525. package/dist/sdk/session/return-path-segment.js.map +1 -0
  526. package/dist/sdk/session/use-session-actions.js.map +1 -1
  527. package/dist/sdk/shipping/shipping-operations.d.ts +1 -1
  528. package/dist/sdk/shipping/shipping-operations.js +13 -0
  529. package/dist/sdk/shipping/shipping-operations.js.map +1 -1
  530. package/dist/sdk/shipping/use-cart-shipping-simulation.js.map +1 -1
  531. package/dist/sdk/shipping/use-shipping-actions.js.map +1 -1
  532. package/dist/sdk/shipping/use-shipping-simulation.js.map +1 -1
  533. package/dist/sdk/state/cart.d.ts +9 -2
  534. package/dist/sdk/state/cart.js +9 -2
  535. package/dist/sdk/state/cart.js.map +1 -1
  536. package/dist/utils/safe-href.d.ts +2 -0
  537. package/package.json +4 -3
  538. package/skills/storefront-asset/SKILL.md +2 -2
  539. package/skills/storefront-asset/references/api-side.md +27 -14
  540. package/skills/storefront-asset/references/findings.md +5 -2
  541. package/src/styles/tailwind.css +9 -2
  542. package/dist/matter/shipping-address/country-descriptors.d.ts +0 -1
  543. package/dist/message-locales.json +0 -7
  544. package/dist/sdk/i18n/client.d.ts +0 -4
  545. package/dist/sdk/i18n/client.js +0 -4
  546. package/dist/sdk/i18n/locales.d.ts +0 -8
  547. package/dist/sdk/i18n/locales.js +0 -57
  548. package/dist/sdk/i18n/locales.js.map +0 -1
  549. package/dist/sdk/i18n/messages/en-US.js +0 -164
  550. package/dist/sdk/i18n/messages/en-US.js.map +0 -1
  551. package/dist/sdk/i18n/messages/en-US.json +0 -159
  552. package/dist/sdk/i18n/messages/en-US.json.d.ts +0 -163
  553. package/dist/sdk/i18n/messages/es-ES.js +0 -164
  554. package/dist/sdk/i18n/messages/es-ES.js.map +0 -1
  555. package/dist/sdk/i18n/messages/es-ES.json +0 -159
  556. package/dist/sdk/i18n/messages/pt-BR.js +0 -164
  557. package/dist/sdk/i18n/messages/pt-BR.js.map +0 -1
  558. package/dist/sdk/i18n/messages/pt-BR.json +0 -159
  559. package/dist/sdk/i18n/provider.d.ts +0 -1
  560. package/dist/sdk/i18n/provider.js +0 -30
  561. package/dist/sdk/i18n/provider.js.map +0 -1
  562. package/dist/sdk/i18n/server.d.ts +0 -28
  563. package/dist/sdk/i18n/server.js +0 -59
  564. package/dist/sdk/i18n/server.js.map +0 -1
  565. package/dist/utils/resolve-authored-copy.js +0 -18
  566. package/dist/utils/resolve-authored-copy.js.map +0 -1
  567. package/dist/utils/substitute-template.js +0 -33
  568. package/dist/utils/substitute-template.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"use-place-order.js","names":[],"sources":["../../../src/sdk/payment/use-place-order.ts"],"sourcesContent":["'use client';\n\nimport { useCallback, useMemo, useRef, useState } from 'react';\nimport { loggableErrorName } from '../../utils/loggable-error-name.js';\nimport type { ComponentActions } from '../actions/types.js';\nimport type { PaymentGatewayTransaction } from './gateway.js';\nimport { isTrustedGatewayOrigin } from './gateway-origin.js';\nimport { PAYMENT_NETWORK_ERROR_CODE, type PaymentMoney, type PaymentUserError } from './operations.js';\nimport {\n type PaymentMissingMethodExplainer,\n type PaymentPreflightCheck,\n type PaymentSubmission,\n type PaymentSubmissionChannel,\n type PaymentSubmissionResult,\n usePaymentSubmission,\n} from './submission-registry.js';\nimport type {\n PaymentActions,\n PaymentGatewayCallbackResult,\n PaymentStartTransactionResult,\n} from './use-payment-actions.js';\n\n/**\n * The place-order orchestration (specs/use-place-order.md, T13; docs/features/credit-card/rfc.md\n * § D9) — the PCI-critical core of the credit-card feature, and the only module that knows placing an\n * order is three calls rather than one mutation.\n *\n * **This hook never holds card data.** It resolves an opaque continuation from the submission channel\n * by the shopper's entry path and invokes it between the two backend legs; the card stays in the\n * holder's closure (T7 for a new card, T12 for a saved one), which calls\n * `$actions.payment.sendPayments` itself. So the actions this hook needs are `startTransaction`,\n * `gatewayCallback`, and `analytics.track` — never the gateway façade. Two tests assert the boundary\n * against this file's own source text: no card field is named in code, and the single import from\n * `./gateway.js` is the transaction descriptor's type.\n *\n * The order of the pre-flight is load-bearing, not stylistic. The path, the continuation, and the\n * selected method are all resolved *before* `startTransaction` runs, because a transaction that is\n * opened and then cannot be completed is dangling state on the shopper's order form.\n *\n * v1 is the direct-API mode: the gateway leg resolves inline and the shopper stays on the page. The\n * redirect mode — an external navigation with a resume-on-callback route, and with it 3D Secure — is\n * phase 2, which is why no `callbackUrl` reaches the descriptor.\n *\n * Every failure resolves as `userErrors`; nothing here throws for an expected outcome, so the CTA\n * (T14) renders one error path. Its one per-code branch is PLACE_ORDER_UNCONFIRMED, a pending outcome\n * it shows as a status rather than an error (T176).\n */\n\n// ---------------------------------------------------------------------------\n// Client-synthesized error codes\n// ---------------------------------------------------------------------------\n\n/** No entry path chosen, or no holder registered for the one that was. */\nexport const PAYMENT_MISSING_SUBMISSION_CODE = 'MISSING_PAYMENT_SUBMISSION';\n\n/**\n * No payment method selected. Reuses `PaymentStartTransactionErrorCode`'s own value for the same\n * state, so a consumer matches one code whichever side detected it.\n */\nexport const PAYMENT_MISSING_METHOD_CODE = 'MISSING_PAYMENT_METHOD_SELECTION';\n\n/**\n * The transaction opened but cannot be paid — an incomplete payload, or an origin this client will\n * not post a card to. Reuses the server's code for the same meaning (T18 Decision 5).\n */\nexport const PAYMENT_INVALID_TRANSACTION_CODE = 'INVALID_TRANSACTION_DATA';\n\n/**\n * The attempt's own sequence is still running — opened or finalizing, not yet settled. Not a\n * refusal: nothing failed, the record just belongs to a sequence this caller did not start.\n */\nexport const PLACE_ORDER_IN_FLIGHT_CODE = 'PLACE_ORDER_IN_FLIGHT';\n\n/**\n * The gatewayCallback leg ran after the gateway accepted the card POST, and this client could not confirm\n * its outcome — the payment may or may not have been authorized. Not a refusal (T176).\n */\nexport const PLACE_ORDER_UNCONFIRMED_CODE = 'PLACE_ORDER_UNCONFIRMED';\n\nconst MISSING_SUBMISSION_ERROR: PaymentUserError = {\n code: PAYMENT_MISSING_SUBMISSION_CODE,\n message: 'Enter your payment details before placing the order.',\n};\n\nconst MISSING_METHOD_ERROR: PaymentUserError = {\n code: PAYMENT_MISSING_METHOD_CODE,\n message: 'Choose a payment method before placing the order.',\n};\n\nconst INVALID_TRANSACTION_ERROR: PaymentUserError = {\n code: PAYMENT_INVALID_TRANSACTION_CODE,\n message: 'Unable to start the payment. Please try again.',\n};\n\nconst PLACE_ORDER_IN_FLIGHT_ERROR: PaymentUserError = {\n code: PLACE_ORDER_IN_FLIGHT_CODE,\n message: 'This order is already being placed. Please wait.',\n};\n\n/**\n * Worded as a pending state, never a failure: it must not tell the shopper to try again (FR-1).\n * `PlaceOrder`'s `unconfirmedMessage` schema default repeats this text verbatim.\n */\nconst PLACE_ORDER_UNCONFIRMED_ERROR: PaymentUserError = {\n code: PLACE_ORDER_UNCONFIRMED_CODE,\n message: 'We could not confirm your order yet. Your payment may still be processing, so please do not pay again.',\n};\n\n/** Mirrors `usePaymentActions`' own infrastructure-failure userError, which that module keeps private. */\nconst NETWORK_ERROR: PaymentUserError = {\n code: PAYMENT_NETWORK_ERROR_CODE,\n message: 'Unable to complete the request. Please try again.',\n};\n\n// ---------------------------------------------------------------------------\n// Data the descriptor needs from the read model\n// ---------------------------------------------------------------------------\n\nexport type PlaceOrderInstallment = Readonly<{\n count: number;\n /** Per-installment amount, as a display decimal. The gateway's unit is minor units. */\n value: PaymentMoney;\n interestRate: number;\n}>;\n\nexport type PlaceOrderMethod = Readonly<{ id: string; groupName: string }>;\n\n/**\n * The `Payment` slice T14's fragment forwards (arity-1, ADR-0015) — the same convention as\n * `PaymentSectionData`. These five values are the part of the descriptor `paymentStartTransaction`\n * does not carry: T18 deliberately did not re-project the read model onto a write payload.\n */\nexport type PlaceOrderData = {\n selectedMethodId?: string | null;\n selectedInstallment?: PlaceOrderInstallment | null;\n methods?: readonly PlaceOrderMethod[];\n} | null;\n\nexport type PlaceOrderResult = Readonly<{\n /** The placed order's id, once the callback resolved one. */\n orderId: string | null;\n /** Empty on success. Carries the server's own codes verbatim when a backend leg reported one. */\n userErrors: readonly PaymentUserError[];\n /**\n * `false` only when the card provably never left the browser: a refusal raised before the sequence\n * began, a `startTransaction` failure, or a continuation that reported it refused before issuing any\n * request. In each a fresh full attempt is legal. `true` for everything else, including every outcome\n * this hook cannot classify — a resume, a placed order, an in-flight short-circuit, a throw after the\n * card was handed over. Read this instead of inferring it from an error code (T112).\n */\n reachedGateway: boolean;\n}>;\n\nexport type UsePlaceOrder = Readonly<{\n /** Runs the three-call sequence. Resolves; never rejects for an expected outcome. */\n placeOrder: () => Promise<PlaceOrderResult>;\n /** True while the sequence runs — what T14's blocking loading state renders. */\n placing: boolean;\n /** The latest attempt's userErrors. Empty before the first attempt and while one is in flight. */\n userErrors: readonly PaymentUserError[];\n /** The latest attempt's order id. */\n orderId: string | null;\n /** The latest attempt's `PlaceOrderResult.reachedGateway`. `null` before the first attempt and while one is in flight. */\n reachedGateway: boolean | null;\n}>;\n\n// ---------------------------------------------------------------------------\n// Payload settling\n// ---------------------------------------------------------------------------\n\n/**\n * The start payload once every field the descriptor needs is known non-null. `userProfileId` is the\n * exception: a guest order legitimately has none.\n */\ntype SettledTransaction = Readonly<{\n transactionId: string;\n merchantName: string;\n gatewayUrl: string;\n orderGroup: string;\n userProfileId: string | null;\n value: PaymentMoney;\n valueInCents: number;\n referenceValueInCents: number;\n}>;\n\n/** Identifiers, a host, and amounts — nothing here is sensitive, so naming them in a log is safe. */\nfunction missingTransactionFields(payload: PaymentStartTransactionResult): string[] {\n return Object.entries({\n transactionId: payload.transactionId,\n merchantName: payload.merchantName,\n orderGroup: payload.orderGroup,\n value: payload.value,\n valueInCents: payload.valueInCents,\n referenceValueInCents: payload.referenceValueInCents,\n })\n .filter(([, field]) => field === null)\n .map(([name]) => name);\n}\n\n/**\n * Narrows a payload that reported no userErrors, or refuses it. The origin is checked first and\n * separately: it is the field that decides where a card is sent, and a refusal here happens before\n * any holder is asked to assemble one.\n */\nfunction settleTransaction(payload: PaymentStartTransactionResult): SettledTransaction | null {\n if (!isTrustedGatewayOrigin(payload.gatewayUrl)) {\n console.error('[payment.placeOrder] the opened transaction carries no trusted https gateway origin');\n return null;\n }\n\n const { transactionId, merchantName, orderGroup, userProfileId, value, valueInCents, referenceValueInCents } =\n payload;\n\n if (\n transactionId === null ||\n merchantName === null ||\n orderGroup === null ||\n value === null ||\n valueInCents === null ||\n referenceValueInCents === null\n ) {\n console.error(\n '[payment.placeOrder] the opened transaction payload is incomplete',\n missingTransactionFields(payload).join(', '),\n );\n return null;\n }\n\n return {\n gatewayUrl: payload.gatewayUrl,\n transactionId,\n merchantName,\n orderGroup,\n userProfileId,\n value,\n valueInCents,\n referenceValueInCents,\n };\n}\n\n/**\n * The one derived amount on the descriptor. VTEX's `payments[0]` carries no per-installment minor-unit\n * value, so T18 could not expose one and the conversion stays here — rounded, because\n * `19.99 * 100 === 1998.9999999999998`. `Number()` is not redundant: the components codegen maps the\n * `Decimal` scalar to `number` while the resolver serializes it as a string (see the spec's Open items).\n */\nfunction toMinorUnits(amount: number): number {\n return Math.round(Number(amount) * 100);\n}\n\n/**\n * The one call into holder-owned code. A holder's continuation is specified to resolve a\n * `PaymentSubmissionResult` — `sendPayments` already collapses every gateway outcome to one — so a throw,\n * or a result that is not that shape, means its contract broke. Only a sanitised error *name* is logged\n * (T160): the closure that threw holds the card, and a message or stack could echo it — and so,\n * unsanitised, could `.name` itself, since it is a writable own property. See `loggableErrorName()`.\n *\n * T112 (Decision 6): the result is totalled here and fails closed. Anything that is not an object with an\n * array `userErrors` — a nullish resolution, or the bare array the contract returned before this change —\n * collapses to `{ reachedGateway: true, userErrors: [NETWORK_ERROR] }`, and a `reachedGateway` that is not\n * a boolean is read as `true`: only a literal `false` may free the attempt, so a holder that omits the\n * signal locks it rather than inviting a second payment. An empty `userErrors` is a success whatever the\n * flag says. Each field is read once, into a local, and the result handed back is a fresh object — a\n * getter that answers differently across reads has no second read to answer differently on.\n */\nasync function invokeSubmission(\n submit: PaymentSubmission,\n transaction: PaymentGatewayTransaction,\n): Promise<PaymentSubmissionResult> {\n try {\n // T160: totals the awaited resolution inside this `try`, mirroring `invokeStartTransaction`'s and\n // `invokeGatewayCallback`'s own T156 fix shape — a bare `return await submit(transaction)` let a\n // nullish resolution escape untouched, so the caller's own `submissionErrors.length` read threw\n // one call site away, latching the attempt at `opening` past the point `resume()` can reach it.\n const result = await submit(transaction);\n if (result == null) {\n console.error('[payment.placeOrder] the registered submission resolved with no result');\n return { reachedGateway: true, userErrors: [NETWORK_ERROR] };\n }\n\n const { reachedGateway, userErrors } = result;\n if (!Array.isArray(userErrors)) {\n console.error('[payment.placeOrder] the registered submission resolved a result with no userErrors list');\n return { reachedGateway: true, userErrors: [NETWORK_ERROR] };\n }\n\n if (userErrors.length === 0) return { reachedGateway: true, userErrors: [] };\n\n if (typeof reachedGateway !== 'boolean') {\n console.error('[payment.placeOrder] the registered submission resolved a non-boolean reachedGateway');\n return { reachedGateway: true, userErrors };\n }\n\n return { reachedGateway, userErrors };\n } catch (error) {\n console.error('[payment.placeOrder] the registered submission threw', loggableErrorName(error));\n return { reachedGateway: true, userErrors: [NETWORK_ERROR] };\n }\n}\n\n/**\n * The pre-flight counterpart of `invokeSubmission`, and it inherits that function's one rule: the\n * closure that throws holds the card, so only a sanitised error *name* is logged (T160) — never a\n * message, a stack, or the raw writable `.name` property. See `loggableErrorName()`.\n */\nfunction runPreflight(check: PaymentPreflightCheck): readonly PaymentUserError[] {\n try {\n return check();\n } catch (error) {\n console.error('[payment.placeOrder] the registered pre-flight threw', loggableErrorName(error));\n return [NETWORK_ERROR];\n }\n}\n\n/**\n * Asks the registered missing-method explainer (T47) why nothing is selected. A hint, never a check\n * (Decision 5): a throw, a non-object, or an object whose `code` or `message` is not a string all answer\n * `null`, so the caller keeps the generic refusal. A throw logs only the sanitised error name — the\n * closure that throws can see the typed number, so never its message.\n */\nfunction runMissingMethodExplainer(explain: PaymentMissingMethodExplainer): PaymentUserError | null {\n try {\n const explained: unknown = explain();\n if (typeof explained !== 'object' || explained === null) return null;\n const { code, message } = explained as { code?: unknown; message?: unknown };\n return typeof code === 'string' && typeof message === 'string' ? { code, message } : null;\n } catch (error) {\n console.error('[payment.placeOrder] the registered explainer threw', loggableErrorName(error));\n return null;\n }\n}\n\n/**\n * The `startTransaction` counterpart of `invokeSubmission`/`runPreflight` (fix pass, Medium finding):\n * before this, `run()` awaited `startTransaction()` directly, so a **rejection** — as opposed to a\n * resolved payload carrying `userErrors` — escaped past every one of `run()`'s own outcome branches\n * and `settleSequence`'s rejection handler never touches the attempt record, leaving it stuck at\n * `opening` for the payment root's lifetime. `opening` is the one phase `placeOrder()` treats as\n * \"already running\" with no failure exit, so every later click resolves `{ orderId: null, userErrors:\n * [] }` without running anything — a false \"Order placed successfully.\" with the CTA disabled forever.\n * `usePlaceOrder` is exported public API and `$actions` is compiler-injected and pluggable, so a\n * provider that rejects here is reachable, not hypothetical. Collapsing the rejection to a resolved\n * payload with one `userErrors` entry means `run()`'s own existing `payload.userErrors.length > 0`\n * branch is what catches it — the same `refused` outcome a server-reported refusal already gets,\n * correct here too since nothing can have been paid before the transaction even opened.\n *\n * T156 fold: the result is destructured inside this `try`, not returned as `await startTransaction()`\n * untouched — a provider resolving `undefined` used to throw at the caller's own\n * `payload.userErrors.length` read, past this function's own `catch`, latching the attempt at\n * `opening` exactly like the rejection case above. Destructuring a nullish resolution throws here\n * instead, so it collapses the same way. Mirrors `invokeGatewayCallback`'s own reason for\n * destructuring, below.\n *\n * Fix pass (review F1/S1): `userErrors` is defaulted with `?? [NETWORK_ERROR]`, not `?? []` — a\n * *partial* payload (present, but missing `userErrors`, e.g. a provider whose GraphQL selection omits\n * the field) used to leave it `undefined`, reaching the caller's `payload.userErrors.length` read\n * (`:627`) and throwing past this function's own `catch`, latching the attempt at `opening` exactly\n * like the cases already fixed. `userErrors` is this result's only signal of whether anything actually\n * went wrong, so a payload that omits it defaults to a *failure*, mirroring a rejection — defaulting to\n * `[]` instead would settle the attempt on a payload this function never actually saw. (The in-repo\n * default provider's own `payload?.userErrors ?? []`, `use-payment-actions.ts:202`, defaults to `[]`\n * only because there `payload` itself — not just the field — is what may be missing.)\n */\nasync function invokeStartTransaction(\n startTransaction: PaymentActions['startTransaction'],\n): Promise<PaymentStartTransactionResult> {\n try {\n const {\n transactionId,\n merchantName,\n gatewayUrl,\n orderGroup,\n userProfileId,\n value,\n valueInCents,\n referenceValueInCents,\n userErrors,\n } = await startTransaction();\n if (userErrors == null) {\n console.error('[payment.placeOrder] startTransaction resolved with no userErrors', orderGroup);\n }\n return {\n transactionId,\n merchantName,\n gatewayUrl,\n orderGroup,\n userProfileId,\n value,\n valueInCents,\n referenceValueInCents,\n userErrors: userErrors ?? [NETWORK_ERROR],\n };\n } catch (error) {\n console.error('[payment.placeOrder] startTransaction threw', loggableErrorName(error));\n return {\n transactionId: null,\n merchantName: null,\n gatewayUrl: null,\n orderGroup: null,\n userProfileId: null,\n value: null,\n valueInCents: null,\n referenceValueInCents: null,\n userErrors: [NETWORK_ERROR],\n };\n }\n}\n\n/**\n * The `gatewayCallback` counterpart of `invokeStartTransaction` above (T156, PR #756 review R-004):\n * before this, both call sites below awaited `gatewayCallback` directly, so a **rejection**\n * escaped past every outcome branch and `settleSequence`'s rejection handler never touches the attempt\n * record, leaving it stuck at `finalizing` for the payment root's lifetime — `placeOrder()` treats\n * `finalizing` exactly like `opening`, an already-running phase with no failure exit, so every later\n * click resolved `{ orderId: attempt.orderId, userErrors: [] }` without issuing anything.\n *\n * The destination is `unresolved`, never `refused` like `invokeStartTransaction`'s own collapse: this\n * leg only runs once the card already reached the gateway, so nothing here can pretend no money moved.\n * `unresolved` is exactly the phase `PlaceOrderAttemptPhase` already documents for this case, below,\n * and is already resumable.\n *\n * Three failure shapes collapse to the same `NETWORK_ERROR` result, logged distinctly (fix pass,\n * review F1/S1/F3): a **rejection**, caught below; a **nullish resolution** (`undefined`/`null`),\n * caught by the explicit check that follows rather than by letting a destructure throw, so it logs a\n * line of its own instead of printing identically to a rejection; and a **partial resolution** — an\n * object present but missing `userErrors`, e.g. a provider whose GraphQL selection omits the field —\n * defaulted with `?? [NETWORK_ERROR]`, not `?? []`. That last case used to return `{ orderId,\n * userErrors }` untouched, leaving `userErrors: undefined`, so the *caller's* `callback.userErrors.length`\n * read threw one call site away from where this wrapper's whole job is to stop exactly that. `[]` would\n * default a payload this function never actually saw to a clean success; `userErrors` is the only\n * signal of whether the callback settled anything, so omitting it means treating it as a failure.\n * Both failure branches log `orderGroup` and `attemptId` — neither is card data\n * (`missingTransactionFields`'s comment above), and this is the one point in the flow where money may\n * already have moved — but never the error's message, stack, or any call argument (FR-6).\n *\n * No `gatewayCallback` call may exist in this module outside this function (INV-2, asserted against\n * this file's own source text in use-place-order.test.tsx) — the two call sites below (`resume()` and\n * `run()`) call this and nothing else.\n */\nasync function invokeGatewayCallback(\n gatewayCallback: PaymentActions['gatewayCallback'],\n orderGroup: string,\n attemptId: string,\n): Promise<PaymentGatewayCallbackResult> {\n let result: PaymentGatewayCallbackResult | null | undefined;\n try {\n result = await gatewayCallback(orderGroup);\n } catch (error) {\n console.error('[payment.placeOrder] gatewayCallback threw', orderGroup, attemptId, loggableErrorName(error));\n return { orderId: null, userErrors: [NETWORK_ERROR] };\n }\n\n if (result == null) {\n console.error('[payment.placeOrder] gatewayCallback resolved with no result', orderGroup, attemptId);\n return { orderId: null, userErrors: [NETWORK_ERROR] };\n }\n\n if (result.userErrors == null) {\n console.error('[payment.placeOrder] gatewayCallback resolved with no userErrors', orderGroup, attemptId);\n }\n return { orderId: result.orderId ?? null, userErrors: result.userErrors ?? [NETWORK_ERROR] };\n}\n\n/**\n * The two codes the server sends today on `gatewayCallback`. Neither is a verdict on the order, so\n * `callbackFreesAttempt` never frees an attempt that carries either (T110, Decision 3):\n * `REDIRECT_REQUIRED` is a bank challenge, and `MISSING_TRANSACTION_COOKIES` means the card POST was\n * already accepted while the callback never reached VTEX, so a second POST could authorize the card twice.\n */\nconst REDIRECT_REQUIRED_CODE = 'REDIRECT_REQUIRED';\nconst MISSING_TRANSACTION_COOKIES_CODE = 'MISSING_TRANSACTION_COOKIES';\n\n/** The entry's `code`, or `undefined` when it cannot be read (a `null` entry, a getter that throws). */\nfunction readCode(userError: PaymentUserError): unknown {\n try {\n return userError.code;\n } catch {\n return undefined;\n }\n}\n\n/** True when the entry carries a string `code` other than this package's own `NETWORK_ERROR`. Never throws. */\nfunction carriesServerCode(userError: PaymentUserError): boolean {\n const code = readCode(userError);\n return typeof code === 'string' && code !== PAYMENT_NETWORK_ERROR_CODE;\n}\n\n/**\n * T110: whether a `gatewayCallback` that reported `userErrors` frees the attempt for a fresh run. It does\n * when the server sent a string `code`, none of the entries is a locked code (`REDIRECT_REQUIRED`,\n * `MISSING_TRANSACTION_COOKIES`), and the result carries no `orderId`: an order the callback confirmed is\n * never freed. Everything else keeps the attempt resume-only, because a payment that may have gone through\n * must never be paid twice. Total, and never throws: a non-array, an empty list or an entry nobody can read\n * frees nothing.\n *\n * Dormant today: the server sends only the two locked codes, so no real callback frees an attempt yet.\n * The rule fires for a server-coded error the server does not send yet (task T225 will). It fails open on\n * a code it does not know (spec AC-5: no per-code table); a future allowlist of the codes that really are\n * declines belongs to T225, not to this hook.\n */\nfunction callbackFreesAttempt(callback: {\n readonly orderId: string | null;\n readonly userErrors: readonly PaymentUserError[];\n}): boolean {\n const { orderId, userErrors } = callback;\n if (orderId != null) return false;\n if (!Array.isArray(userErrors)) return false;\n return (\n userErrors.some((userError) => carriesServerCode(userError)) &&\n !userErrors.some((userError) => {\n const code = readCode(userError);\n return code === REDIRECT_REQUIRED_CODE || code === MISSING_TRANSACTION_COOKIES_CODE;\n })\n );\n}\n\n/**\n * T176 (FR-2, FR-3): what the caller reads from a `gatewayCallback` that reported `userErrors`, on both\n * paths that issue it. The gateway already accepted the card POST by then, so a result made only of this\n * package's own collapse — `PAYMENT_NETWORK_ERROR_CODE`, which `usePaymentActions` and\n * `invokeGatewayCallback` resolve for every thrown, lost or shapeless response — says nothing about\n * whether VTEX processed the order. It becomes PLACE_ORDER_UNCONFIRMED rather than a \"try again\" that\n * invites a second payment. A code the server sent is an answer, and comes back unchanged (INV-10).\n *\n * Total, and never throws: a non-array, or an entry with no readable string `code`, carries no server\n * code, so it reads as unconfirmed too. A server code elsewhere in the same list still wins, whatever\n * order the entries come in.\n */\nfunction reconcileCallbackUserErrors(userErrors: readonly PaymentUserError[]): readonly PaymentUserError[] {\n try {\n if (!Array.isArray(userErrors)) return [PLACE_ORDER_UNCONFIRMED_ERROR];\n if (userErrors.length === 0) return userErrors;\n return userErrors.some((userError) => carriesServerCode(userError)) ? userErrors : [PLACE_ORDER_UNCONFIRMED_ERROR];\n } catch {\n return [PLACE_ORDER_UNCONFIRMED_ERROR];\n }\n}\n\n/**\n * `crypto.randomUUID()` is secure-context-only (fix pass, Low finding) — on a plain-HTTP preview or\n * staging origin the method does not exist on `crypto` at all, so calling it throws, and every click\n * collapses to a generic `NETWORK_ERROR` with no diagnostic. `localhost` is always a secure context,\n * so dev never reproduces it. Falls back to a timestamp+random string, which only needs to be unique\n * for one payment root's lifetime (the staleness guard below is its one reader) — not a real UUID.\n */\nfunction generateAttemptId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;\n}\n\n/**\n * Fix pass (High finding, race #2): guards every write past an attempt's genesis against a stale,\n * out-of-order completion clobbering a newer outcome. `run()`/`resume()` mint one write *before* their\n * own first await (safe by construction — nothing can have superseded an attempt this call itself just\n * created) and one or more writes *after* an await, once `startTransaction`/the continuation/\n * `gatewayCallback` resolve. Because the writing closure keeps running after its own CTA unmounts\n * (NFR-3), that later write cannot assume the shared attempt is still the one it started with — a\n * remounted or sibling instance may have already resumed it to a newer phase in the meantime. Using\n * the setter's own functional-update form (`SetPlaceOrderAttempt`) checks against whatever the shared\n * attempt actually is *when the write is applied*, not the value this closure captured before its\n * await — mirrors `selectionToken`'s own precedent (`matter/payment/use-payment-method-selection.ts`)\n * for the identical shape of problem, keyed on the attempt's own id instead of a second counter.\n * `settled` is additionally excluded because INV-6 forbids any transition away from it.\n */\nfunction writeAttemptIfCurrent(\n setAttempt: SetPlaceOrderAttempt | null | undefined,\n attemptId: string,\n next: PlaceOrderAttempt | null,\n): void {\n setAttempt?.((prev) => (prev !== null && prev.attemptId === attemptId && prev.phase !== 'settled' ? next : prev));\n}\n\n/** The record an attempt carries once nothing was paid: a fresh full sequence is legal next time. */\nfunction refusedRecord(attemptId: string): PlaceOrderAttempt {\n return { attemptId, phase: 'refused', orderGroup: null, orderId: null };\n}\n\n/**\n * The record `resume()` writes once its one `gatewayCallback` answered: `refused` when the retry rule\n * freed the attempt, `unresolved` for any other error, `settled` with the order id on success.\n */\nfunction resumedRecord(\n current: PlaceOrderAttempt,\n callback: PaymentGatewayCallbackResult,\n failed: boolean,\n freed: boolean,\n): PlaceOrderAttempt {\n if (freed) return refusedRecord(current.attemptId);\n if (failed) return { ...current, phase: 'unresolved' };\n return { ...current, phase: 'settled', orderId: callback.orderId };\n}\n\n/**\n * The terminal record `run()`'s `catch` re-asserts: the `settled` one a throw may have interrupted, else\n * `unresolved` once the card was handed over, else `refused`. A failed leg writes the same pair, with\n * no placed attempt: `refused` when it freed the attempt, `unresolved` (keeping the order group) otherwise.\n */\nfunction recoveryRecord(\n attemptId: string,\n placedAttempt: PlaceOrderAttempt | null,\n handedOver: boolean,\n settledOrderGroup: string | null,\n): PlaceOrderAttempt {\n if (placedAttempt !== null) return placedAttempt;\n return handedOver\n ? { attemptId, phase: 'unresolved', orderGroup: settledOrderGroup, orderId: null }\n : refusedRecord(attemptId);\n}\n\n/**\n * `run()`'s recovery write, guarded because it goes through the same caller-supplied `setAttempt` that\n * may be what threw in the first place: nothing further can be tried once the writer itself is broken, so\n * it is logged (name only, FR-7) and the caller returns its result either way.\n */\nfunction writeRecoveryRecord(\n setAttempt: SetPlaceOrderAttempt | null | undefined,\n attemptId: string,\n record: PlaceOrderAttempt,\n): void {\n try {\n writeAttemptIfCurrent(setAttempt, attemptId, record);\n } catch (writeError) {\n console.error(\n '[payment.placeOrder] the place-order recovery write threw',\n attemptId,\n loggableErrorName(writeError),\n );\n }\n}\n\n/** `run()`'s refusal before any request: nothing was paid and the card never left the browser. */\nfunction refusalBeforeOpening(userErrors: readonly PaymentUserError[]): PlaceOrderResult {\n return { orderId: null, userErrors, reachedGateway: false };\n}\n\ntype SubmissionVerdict =\n | { kind: 'refused'; result: PlaceOrderResult }\n | { kind: 'ready'; submit: PaymentSubmission; method: PlaceOrderMethod };\n\nfunction refused(userErrors: readonly PaymentUserError[]): SubmissionVerdict {\n return { kind: 'refused', result: refusalBeforeOpening(userErrors) };\n}\n\ntype SubmissionChannelFacts = Pick<\n PaymentSubmissionChannel,\n 'path' | 'resolve' | 'resolvePreflight' | 'resolveMissingMethodExplainer'\n>;\n\n/**\n * T47 (Decision 2/4/5): the holder may know a more specific reason than \"choose a method\". Asked only\n * with nothing selected, and never before the checks that follow, so the order of checks cannot change.\n * Only the opaque refusal comes back, and only its code is logged.\n */\nfunction refuseMissingMethod({ path, resolveMissingMethodExplainer }: SubmissionChannelFacts): SubmissionVerdict {\n const explain = path === null ? null : resolveMissingMethodExplainer(path);\n const explained = explain === null ? null : runMissingMethodExplainer(explain);\n if (explained !== null) {\n console.error(\n '[payment.placeOrder] no payment method is selected, and the registered explainer named why',\n explained.code,\n );\n return refused([explained]);\n }\n console.error('[payment.placeOrder] no payment method is selected');\n return refused([MISSING_METHOD_ERROR]);\n}\n\n/**\n * Every synchronous refusal `run()` resolves before anything is opened, in their original order: no\n * registered submission, no selected method, the submit gate, then the holder's own pre-flight. A\n * transaction nothing can complete is dangling state on the order form. The first two causes are one\n * state to a shopper and differ only in the log.\n */\nfunction checkSubmissionPreconditions({\n channel,\n selectedMethod,\n submitGate,\n}: {\n channel: SubmissionChannelFacts;\n selectedMethod: PlaceOrderMethod | null;\n submitGate: PaymentUserError | null | undefined;\n}): SubmissionVerdict {\n const { path, resolve, resolvePreflight } = channel;\n const submit = path === null ? null : resolve(path);\n if (submit === null) {\n console.error('[payment.placeOrder] no submission is registered for the entry path', path ?? 'none');\n return refused([MISSING_SUBMISSION_ERROR]);\n }\n\n // `paymentSystem` and `groupName` come from the read model, not from the start payload, so a\n // missing selection would otherwise surface only after the transaction was already open.\n if (selectedMethod === null) return refuseMissingMethod(channel);\n\n // T24's submit gate: an already-resolved client-side refusal (`matter/payment/\n // usePaymentSubmitGate()`), joining the pre-flight so no transaction opens for a submission this\n // client already knows would be refused. Only the opaque verdict is logged — never a fact behind\n // it — keeping this module's own PCI boundary intact (Decision 2).\n if (submitGate) {\n console.error('[payment.placeOrder] a client-side gate refused the submission', submitGate.code);\n return refused([submitGate]);\n }\n\n // The holder's own pre-flight on the draft it holds (T27). Only the opaque refusal crosses back —\n // this module learns nothing about what was wrong, exactly as with the gate above.\n //\n // Absence is a silent no-op, not a refusal (FR-6): a holder that registered no check (the\n // two-argument overload) contributes nothing here, exactly as if this branch did not exist. Logging on\n // this branch would fire on the overwhelming majority of calls, diluting the signal this file's other\n // console.error calls carry for genuine anomalies; failing closed would refuse every one of those\n // callers, breaking `register`'s own optional third argument (NFR-6, Decision 2). A caller that wants\n // this protection must register a check.\n const preflight = path === null ? null : resolvePreflight(path);\n if (preflight) {\n const refusals = runPreflight(preflight);\n if (refusals.length > 0) {\n console.error('[payment.placeOrder] the registered pre-flight refused the submission', refusals[0]?.code);\n return refused(refusals);\n }\n }\n\n return { kind: 'ready', submit, method: selectedMethod };\n}\n\ntype OpenedTransaction =\n | { kind: 'refused'; result: PlaceOrderResult }\n | { kind: 'opened'; settled: SettledTransaction };\n\n/**\n * `run()`'s first backend leg: opens the transaction and settles its payload. Either refusal records\n * `refused` — nothing was paid, so a fresh full sequence stays legal next time — through\n * `writeAttemptIfCurrent`, not a plain write: the caller's closure may be running after its own CTA\n * unmounted (NFR-3), so a newer completion for the same attempt must win instead.\n */\nasync function openTransaction(\n startTransaction: PaymentActions['startTransaction'],\n setAttempt: SetPlaceOrderAttempt | null | undefined,\n attemptId: string,\n): Promise<OpenedTransaction> {\n const payload = await invokeStartTransaction(startTransaction);\n\n // Before the completeness check, so a server diagnosis is never replaced by a synthesized one —\n // T18 returns INVALID_TRANSACTION_DATA with every other field null for a transaction VTEX opened\n // with no merchant to pay, which a null-field check would otherwise swallow.\n if (payload.userErrors.length > 0) {\n writeAttemptIfCurrent(setAttempt, attemptId, refusedRecord(attemptId));\n return { kind: 'refused', result: refusalBeforeOpening(payload.userErrors) };\n }\n\n const settled = settleTransaction(payload);\n if (settled === null) {\n writeAttemptIfCurrent(setAttempt, attemptId, refusedRecord(attemptId));\n return { kind: 'refused', result: refusalBeforeOpening([INVALID_TRANSACTION_ERROR]) };\n }\n\n return { kind: 'opened', settled };\n}\n\n/** The descriptor `run()` hands to the registered submission. Nothing in it is sensitive. */\nfunction buildTransaction({\n settled,\n method,\n selectedInstallment,\n billingAddressDifferent,\n}: {\n settled: SettledTransaction;\n method: PlaceOrderMethod;\n selectedInstallment: PlaceOrderInstallment | null;\n billingAddressDifferent: boolean | null | undefined;\n}): PaymentGatewayTransaction {\n return {\n gatewayUrl: settled.gatewayUrl,\n transactionId: settled.transactionId,\n merchantName: settled.merchantName,\n orderGroup: settled.orderGroup,\n userProfileId: settled.userProfileId,\n paymentSystem: method.id,\n groupName: method.groupName,\n currencyCode: settled.value.currencyCode,\n valueInCents: settled.valueInCents,\n referenceValueInCents: settled.referenceValueInCents,\n installments: selectedInstallment?.count ?? 1,\n // A count: 1 installment is the full transaction value by definition. The read model's\n // selectedInstallment.value for a synthesized 1x tier is option.value (mappers.ts:597),\n // which can differ from the transaction's own value (Decision 8, spec:232) — e.g. an option\n // priced at 10000 on an order form whose value is 8997. Using settled.valueInCents for the\n // 1x case avoids charging option.value instead of the transaction value. For count > 1 the\n // tier value is upstream data, never synthesized, so the per-installment amount is correct.\n installmentsValueInCents:\n selectedInstallment && selectedInstallment.count !== 1\n ? toMinorUnits(selectedInstallment.value.amount)\n : settled.valueInCents,\n interestRate: selectedInstallment?.interestRate ?? 0,\n // Omitted entirely (never an explicit `undefined` key) when the caller passed none, matching\n // `callbackUrl`'s own absent-when-not-provided convention above (fix pass, review F1).\n ...(billingAddressDifferent == null ? {} : { billingAddressDifferent }),\n };\n}\n\n/**\n * T29 (Decision 3, FR-3): the only path back into a `sent`/`unresolved` attempt — `gatewayCallback` is\n * the sole call it ever issues. Module scope, not a `useCallback` inside the hook (fix pass, Low\n * finding): the previous version of this doc comment claimed `startTransaction`, the submission\n * channel, and `PaymentGatewayTransaction` were \"not in scope,\" so a careless future edit could not\n * make a resume re-pay — true at runtime today (verified by identifier sweep) but false as a\n * structural guarantee, since all three were merely unreferenced identifiers a nested closure could\n * still have read. Hoisting here makes the claim actually true: none of those three is even\n * *importable* from a bare module-level function that takes only `gatewayCallback`/`setAttempt`/\n * `setPlacing`/`current` as arguments — a resume is safe by construction, not by convention.\n *\n * T110: a callback the retry rule frees records `refused`, as `run()` does, so the next click is a fresh\n * full run. This function still issues nothing but `gatewayCallback`.\n */\nasync function resume(\n gatewayCallback: PaymentActions['gatewayCallback'] | undefined,\n setAttempt: SetPlaceOrderAttempt | null | undefined,\n setPlacing: (placing: boolean) => void,\n current: PlaceOrderAttempt,\n): Promise<PlaceOrderResult> {\n if (!gatewayCallback || current.orderGroup === null) {\n // Not reachable through this hook's own writes — §3's own invariant keeps `orderGroup` non-null\n // past `opening` — only through a caller-constructed attempt or a missing action namespace. Fails\n // the same way `run()`'s own first guard does, above.\n console.error('[payment.placeOrder] cannot resume an attempt with no order group');\n return { orderId: null, userErrors: [NETWORK_ERROR], reachedGateway: true };\n }\n\n // Mirrors `run()`'s own `setPlacing(true)` placement, immediately before its one `await`, so the\n // shared placing state stays accurate across a remount too. A plain write, guarded by\n // `writeAttemptIfCurrent` rather than an unconditional one, since `current` is a caller-supplied\n // snapshot that may already be stale by the time this async function actually runs.\n writeAttemptIfCurrent(setAttempt, current.attemptId, { ...current, phase: 'finalizing' });\n setPlacing(true);\n\n const callback = await invokeGatewayCallback(gatewayCallback, current.orderGroup, current.attemptId);\n const failed = callback.userErrors.length > 0;\n // T176 (FR-4, FR-7): the result is fixed by the callback before the write, so a caller-supplied\n // writer that throws can no longer turn a confirmed order into a rejection — `settleSequence` would\n // resolve that as NETWORK_ERROR. The record may stay at `finalizing` then; nothing here can repair a\n // broken writer. Only a sanitised error name is logged, as on every other catch in this module.\n const result: PlaceOrderResult = failed\n ? { orderId: null, userErrors: reconcileCallbackUserErrors(callback.userErrors), reachedGateway: true }\n : { orderId: callback.orderId, userErrors: [], reachedGateway: true };\n\n // T110: a callback the rule frees (see `callbackFreesAttempt`) records `refused`, exactly as in `run()`;\n // any other stays resume-only. `orderGroup` is cleared on `refused`, so a stale group is never resumed.\n const freed = failed && callbackFreesAttempt(callback);\n try {\n writeAttemptIfCurrent(setAttempt, current.attemptId, resumedRecord(current, callback, failed, freed));\n } catch (error) {\n console.error('[payment.placeOrder] the resume record write threw', current.attemptId, loggableErrorName(error));\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// The order-attempt record (T29, specs/make-an-order-attempt-survive-a.md) — an identity and a phase\n// that outlive the CTA's own mount, so a remount or a second click resumes a sequence instead of\n// re-running its dangerous half. Defined here, not in matter/payment/use-payment-method-selection.ts:\n// this is the only module that knows the sequence is three calls, and the layering stays\n// matter → sdk (submission-registry.tsx's own rule) — usePaymentMethodSelection imports this type,\n// mirroring `PaymentPath`'s own alias of `PaymentSubmissionPath`.\n// ---------------------------------------------------------------------------\n\n/**\n * How far an order attempt got. The phase, not an error code, is what says whether money may already\n * have moved — an aborted card POST and a refused transaction both surface as the same userError, and\n * only this distinguishes them.\n */\nexport type PlaceOrderAttemptPhase =\n /** `startTransaction` issued or resolved. Nothing has been paid. */\n | 'opening'\n /** A leg refused before the card left the browser, or `gatewayCallback` answered with a server-coded\n * error the retry rule frees (T110; dormant today, the server sends no such code yet). A fresh full\n * attempt is legal. */\n | 'refused'\n /** The continuation resolved with no userErrors — the card reached the gateway. */\n | 'sent'\n /** The card's fate is unknown: the continuation threw, the POST was aborted, or a finalization got no\n * answer or only a locked code (`REDIRECT_REQUIRED`, `MISSING_TRANSACTION_COOKIES`). Resume-only.\n * Treated exactly like `sent`. */\n | 'unresolved'\n /** `gatewayCallback` is in flight for this attempt's order group. */\n | 'finalizing'\n /** `gatewayCallback` resolved with no userErrors. Terminal. */\n | 'settled';\n\nexport type PlaceOrderAttempt = Readonly<{\n /** Identity that outlives any one mount. Not `useId()` — that is per-instance and resets. */\n attemptId: string;\n phase: PlaceOrderAttemptPhase;\n /** Resolved by `startTransaction`; the only key a resume needs. Null before `opening` settles. */\n orderGroup: string | null;\n /**\n * Resolved by `gatewayCallback`. Not an independent identifier: the resolver returns\n * `orderId: input.orderGroup` (`checkout/index.ts:1965`), echoing the argument back, because the\n * upstream success response body is discarded. So `orderId === orderGroup` on every success today.\n */\n orderId: string | null;\n}>;\n\n/**\n * Fix pass (High finding, race #2): widened past a plain value so a write can be made conditional on\n * whatever the shared attempt actually is *at write time* — the primitive `useState` already exposes\n * this (its setter has always accepted an updater function; only this package's own declared type\n * stopped a caller from using that form). A plain value remains valid; every existing caller that\n * never needed the guard is unaffected.\n */\nexport type SetPlaceOrderAttempt = (\n update: PlaceOrderAttempt | null | ((prev: PlaceOrderAttempt | null) => PlaceOrderAttempt | null),\n) => void;\n\n// ---------------------------------------------------------------------------\n// The hook\n// ---------------------------------------------------------------------------\n\nexport type UsePlaceOrderArgs = {\n /** `Payment` slice from the caller's fragment. */\n $data?: PlaceOrderData;\n /** Compiler-injected action surface; carries `payment.startTransaction`, `payment.gatewayCallback`, and `analytics.track`. */\n $actions?: ComponentActions;\n /**\n * T29's order-attempt record and its writer — `usePayment().attempt` / `.setAttempt`, forwarded by\n * the caller exactly like `billingAddressDifferent` below. **Optional by necessity, not by safety**\n * (mirrors `submitGate`'s own doc note): a caller that omits either gets today's behaviour, silently\n * — `placeOrder()` always takes the full `run()` path, with no resume and no phase written anywhere.\n * `PlaceOrder` always sources both from `usePayment()`.\n */\n attempt?: PlaceOrderAttempt | null;\n /** See `attempt` above. Identity must be stable across renders (NFR-3) — `usePayment().setAttempt` already is. */\n setAttempt?: SetPlaceOrderAttempt | null;\n /**\n * `usePayment().draft.billingAddressDifferent` (T11's widening of `PaymentDraft`) — an explicit\n * argument, not read from `usePayment()`'s context in this hook, so the hook keeps its existing\n * shape of taking every fact it needs as a parameter rather than adding a second hard context\n * dependency alongside the submission channel below. `null`/omitted folds to the same \"not set\"\n * descriptor shape `gateway.ts`'s own `transaction.billingAddressDifferent ?? false` already expects\n * (fix pass, PR #544 review F1). The caller is responsible for sourcing this from `usePayment()` and\n * forwarding it here.\n */\n billingAddressDifferent?: boolean | null;\n /**\n * A client-side refusal the caller already resolved, or `null` to proceed. Deliberately an opaque\n * `PaymentUserError` rather than the facts behind it: this module may name no identifier matching\n * `CARD_FIELD_PATTERN`, which includes `document`. The caller owns resolving it —\n * `matter/payment/usePaymentSubmitGate()` is the one in this package.\n *\n * **Optional by necessity, not by safety.** This hook is exported public API; `PlaceOrder` is the\n * only in-repo caller, and it always sources this from `usePaymentSubmitGate()`, but nothing stops a\n * storefront from composing its own place-order button directly on this hook. Omitting this argument\n * is silent and behaves exactly as if this whole pre-flight branch did not exist — no cardholder or\n * billing-address refusal ever reaches a CTA built that way, even though the same client-side gate\n * would have caught it. There is no compile-time signal for that omission (making the type mandatory\n * would be a breaking change to a hook other code may already call, and a caller can still opt out\n * explicitly with `null` regardless of the type), so a store composing its own CTA on this hook must\n * source this argument from `usePaymentSubmitGate()` deliberately, not by copying an example that\n * happened to include it.\n */\n submitGate?: PaymentUserError | null;\n};\n\nexport function usePlaceOrder({\n $data,\n $actions,\n attempt,\n setAttempt,\n billingAddressDifferent,\n submitGate,\n}: UsePlaceOrderArgs): UsePlaceOrder {\n const { path, resolve, resolvePreflight, resolveMissingMethodExplainer } = usePaymentSubmission();\n\n const [placing, setPlacing] = useState(false);\n const [userErrors, setUserErrors] = useState<readonly PaymentUserError[]>([]);\n const [orderId, setOrderId] = useState<string | null>(null);\n const [reachedGateway, setReachedGateway] = useState<boolean | null>(null);\n const inFlight = useRef<Promise<PlaceOrderResult> | null>(null);\n\n const startTransaction = $actions?.payment?.startTransaction;\n const gatewayCallback = $actions?.payment?.gatewayCallback;\n const track = $actions?.analytics?.track;\n\n const selectedMethodId = $data?.selectedMethodId ?? null;\n const methods = $data?.methods;\n const selectedInstallment = $data?.selectedInstallment ?? null;\n\n const selectedMethod = useMemo(\n () => methods?.find((method) => method.id === selectedMethodId) ?? null,\n [methods, selectedMethodId],\n );\n\n const run = useCallback(async (): Promise<PlaceOrderResult> => {\n if (!startTransaction || !gatewayCallback) {\n console.error('[payment.placeOrder] the payment action namespace is not injected');\n return { orderId: null, userErrors: [NETWORK_ERROR], reachedGateway: false };\n }\n\n // Resolved before anything is opened (epic, T13): a transaction nothing can complete is dangling\n // state on the order form. See `checkSubmissionPreconditions`.\n const verdict = checkSubmissionPreconditions({\n channel: { path, resolve, resolvePreflight, resolveMissingMethodExplainer },\n selectedMethod,\n submitGate,\n });\n if (verdict.kind === 'refused') return verdict.result;\n const { submit, method } = verdict;\n\n // T170 (FR-2/FR-3): the three facts this function's own `catch` needs and cannot read from inside\n // its `try`. `handedOver` records whether the card had already been handed to the registered\n // submission when a throw happened — the record's own phase cannot answer that, since it reads\n // `opening` on both sides of the order-group write below. `settledOrderGroup` carries the one\n // field an `unresolved` record must keep for `resume()` to be able to act on it at all, since the\n // settled payload itself is scoped inside the `try`.\n //\n // `placedAttempt` is the terminal record the success tail below is about to write, captured\n // *before* that write (fix round, Critical finding). `handedOver` cannot stand in for it: it is\n // equally true for the whole span from the card POST onwards, including the window *after*\n // `gatewayCallback` has already resolved a real order — and in that window neither `refused` nor\n // `unresolved` is the truth, because the order is placed. The sequence is the only thing that\n // knows it got there, so it carries the fact rather than letting the `catch` infer a phase the\n // record cannot distinguish.\n //\n // T176 (FR-5, Decision 4): `outcome` is the result each branch past the card hand-over fixes\n // *before* its own record write, so the `catch` can return it. From that point a throw — by a\n // caller-supplied writer or by the tracker — may still need the record recovered, but it no longer\n // changes what the caller reads: a confirmed order stays confirmed, and a refusal stays that refusal.\n // It is still null for a throw raised before any branch fixed a result, which keeps T170's\n // NETWORK_ERROR.\n //\n // Locals, not fields on `PlaceOrderAttempt` (NFR-1): only this closure ever reads them, and\n // putting any of them on the exported record would re-open the question T167 closed — readers\n // second-guessing the phase (INV-4).\n let handedOver = false;\n let settledOrderGroup: string | null = null;\n let placedAttempt: PlaceOrderAttempt | null = null;\n let outcome: PlaceOrderResult | null = null;\n\n // T29 (FR-1/FR-2): a fresh attempt record, minted only once every synchronous refusal guard above\n // has passed — none of those guards ever creates one, so a pre-card refusal (Scenario 6) leaves\n // `attempt` untouched and a fresh full run stays legal next time. Not `useId()` (T28's own\n // `instanceKey`): that resets on every remount, and this identity must not. A plain write, not\n // `writeAttemptIfCurrent`: this is the genesis of the attempt, so there is nothing yet for a\n // concurrent completion to have superseded.\n const attemptId = generateAttemptId();\n setAttempt?.({ attemptId, phase: 'opening', orderGroup: null, orderId: null });\n try {\n // FR-1 (T56): raised here — after every synchronous refusal guard, immediately before the first\n // `await` — so a refusal that returns before this line never commits a `placing: true` frame.\n // `setUserErrors([])` and `setOrderId(null)` remain in `placeOrder` (the click handler), and\n // `settle`'s `setPlacing(false)` is untouched.\n setPlacing(true);\n\n const opened = await openTransaction(startTransaction, setAttempt, attemptId);\n if (opened.kind === 'refused') return opened.result;\n const { settled } = opened;\n\n // T167: orderGroup is known and validated as soon as settleTransaction() returns non-null — well\n // before invokeSubmission() (the card POST, below) resolves. Writing it now closes that gap for the\n // whole 'opening' window instead of leaving it null until a terminal write. Data-completeness only:\n // placeOrder()'s phase branch and resumable (place-order.tsx) still derive only from `phase`, which\n // stays 'opening' here, so neither becomes actionable while this record sits in this phase.\n //\n // T170: kept for the recovery path too, which cannot reach `settled` from the `catch` below.\n settledOrderGroup = settled.orderGroup;\n writeAttemptIfCurrent(setAttempt, attemptId, {\n attemptId,\n phase: 'opening',\n orderGroup: settled.orderGroup,\n orderId: null,\n });\n\n const transaction = buildTransaction({ settled, method, selectedInstallment, billingAddressDifferent });\n\n // T170 (FR-3): raised on this side of the await, never the other. From the next line on, the\n // card has left the browser and nothing may recover this attempt as `refused` — which is the\n // only fact the `catch` below cannot re-derive, since the record reads `opening` either way.\n handedOver = true;\n // Still named for the errors it carries — the card-data boundary test pins this statement's text —\n // but it is the whole `PaymentSubmissionResult`, `reachedGateway` included.\n const submissionErrors = await invokeSubmission(submit, transaction);\n // An unpaid transaction is never finalized.\n if (submissionErrors.userErrors.length > 0) {\n // T112 (INV-1): only a result whose `reachedGateway` is literally `false` frees the attempt. The\n // holder refused before issuing any request, so nothing was paid and a fresh full sequence is\n // legal — the same `refused` record the two refusals above write. `handedOver` is cleared first,\n // so a writer that throws on this very write lands `refused` in the `catch` below, not\n // `unresolved`. `invokeSubmission` has already turned every non-boolean into `true`.\n //\n // T29 (FR-2, Decision 5): otherwise a request was issued, or this hook cannot show it was not — so\n // the card's fate is unknown past this point. `gateway.ts` resolves one opaque userError whether the\n // gateway declined the card, the network failed, or the POST was aborted (FR-6) — this hook cannot\n // and must not try to tell those apart, so every one of them is `unresolved`, never `refused`.\n // Sticky for the payment root's lifetime (FR-4), until a `gatewayCallback` answers with an error the\n // retry rule frees (T110): the only call `resume()` ever issues again is `gatewayCallback(orderGroup)`.\n //\n // `handedOver` is `true` here (set on the statement before the await), so `!freed` clears it exactly\n // when the attempt is freed and leaves it set otherwise.\n const freed = submissionErrors.reachedGateway === false;\n handedOver = !freed;\n outcome = { orderId: null, userErrors: submissionErrors.userErrors, reachedGateway: !freed };\n writeAttemptIfCurrent(setAttempt, attemptId, recoveryRecord(attemptId, null, handedOver, settled.orderGroup));\n return outcome;\n }\n\n // T29: the card reached the gateway clean. Past this line the card may be charged — `resume()`\n // (below) is the only legal path back in (Decision 3). Fix pass (High finding): written as\n // `finalizing`, not `sent` — `sent` and `finalizing` are two distinct edges in the spec's own state\n // diagram (`sent --> finalizing: gatewayCallback issued`), but this call issues `gatewayCallback`\n // with no `await` in between, so nothing can ever legally observe the record at rest in `sent`\n // here: React batches same-tick writes, so whichever value is written *last*, before the next\n // `await`, is the only one any render (or a remount reading mid-flight) ever sees. The previous\n // version of this comment argued that fact meant the choice between the two values did not matter;\n // it does — `resumable` (place-order.tsx) treats `sent` as safe to resume, so writing it here left\n // `resumable` true for the entire span of this in-flight `gatewayCallback`, and a remounted or\n // sibling CTA could issue a second, concurrent one on the same order group (this file's own \"is\n // between the card POST and gatewayCallback\" test used to pin exactly that window). `finalizing` is\n // the value that keeps `resumable` false until this call actually settles — exactly what `resume()`\n // (below) already writes for its own, separately-triggered invocation of this same call.\n writeAttemptIfCurrent(setAttempt, attemptId, {\n attemptId,\n phase: 'finalizing',\n orderGroup: settled.orderGroup,\n orderId: null,\n });\n\n const callback = await invokeGatewayCallback(gatewayCallback, settled.orderGroup, attemptId);\n if (callback.userErrors.length > 0) {\n outcome = {\n orderId: null,\n userErrors: reconcileCallbackUserErrors(callback.userErrors),\n reachedGateway: true,\n };\n // T110: a callback the rule frees (see `callbackFreesAttempt`) records `refused`, so the next click is\n // a fresh full run on a new order group. `handedOver` is cleared first, so a writer that throws on\n // this very write lands `refused` in the `catch` below, not `unresolved`. Any other callback error\n // (no answer, a bank challenge, a lapsed transaction cookie, a result with an `orderId`) stays\n // resume-only: that payment may have gone through.\n const freed = callbackFreesAttempt(callback);\n handedOver = !freed;\n writeAttemptIfCurrent(setAttempt, attemptId, recoveryRecord(attemptId, null, handedOver, settled.orderGroup));\n return outcome;\n }\n\n // Built into a local before it is written, not inline (fix round, Critical finding): the write\n // below is itself a throw site — `setAttempt` is caller-supplied — and from the line above onwards\n // the order is placed. Handing the same value to the `catch` lets it re-assert the terminal truth\n // instead of downgrading a paid order to a resumable one.\n placedAttempt = {\n attemptId,\n phase: 'settled',\n orderGroup: settled.orderGroup,\n orderId: callback.orderId,\n };\n outcome = { orderId: callback.orderId, userErrors: [], reachedGateway: true };\n\n // GA4's transaction_id is the order a merchant reconciles against. A callback can succeed without\n // resolving one, and that is still a placed order, so the payment transaction id is the fallback.\n // `settled.value.amount` is typed `number` but arrives over the wire as a decimal string (the same\n // Decimal-scalar mismatch `toMinorUnits` coerces above), so GA4's numeric `value` needs the same\n // `Number()` coercion the rest of this package applies at the point of numeric use (e.g.\n // `sdk/cart/optimistic.ts`'s `moneyAdd`).\n //\n // T176 (Decision 5): dispatched before the terminal write, not after it. The event reports VTEX's\n // confirmation, which has already happened; the record is local bookkeeping about that fact, so a\n // writer that throws on it no longer loses the event. A tracker that throws lands in the `catch`\n // before that write, and the recovery write lands `placedAttempt` instead — never a second call.\n track?.({\n name: 'purchase',\n transactionId: callback.orderId ?? settled.transactionId,\n value: { amount: Number(settled.value.amount), currencyCode: settled.value.currencyCode },\n });\n\n writeAttemptIfCurrent(setAttempt, attemptId, placedAttempt);\n\n return outcome;\n } catch (error) {\n // T170 (FR-1): before this, `run()` had no `try`/`catch` of its own, so a throw anywhere past\n // the genesis write above — the synchronous descriptor build, or the caller-supplied `setAttempt`\n // that `writeAttemptIfCurrent` invokes — escaped every one of the outcome branches inside it.\n // `settleSequence`'s rejection handler (`placeOrder`, below) restores this hook's *local* state\n // only and never touches the shared record, so the record stayed latched at `opening` with no\n // sequence alive to move it: every later click hit `placeOrder()`'s own in-flight branch and\n // issued nothing, for the payment root's lifetime.\n //\n // The phase this settles on is not a judgement call, but it is a three-way one, and the first\n // version of this catch read it from `handedOver` alone (fix round, Critical finding). A throw\n // before the card was handed over provably never started the card POST, so nothing was paid and\n // `refused` is correct — a fresh full attempt stays legal (T29 Decision 4). A throw after it\n // with no order resolved means the card's fate is unknown, which is what `unresolved` means\n // (Decision 5) and what `resume()` exists for. But the `settled` write above is a throw site\n // too, and at that one site `gatewayCallback` has already returned a real order while `settled`\n // has not yet landed — so `writeAttemptIfCurrent`'s own `settled` guard does not refuse the\n // write either, and `unresolved` there made `resumable` (`place-order.tsx`) true for an order\n // that was already paid: one click on the re-enabled CTA routed through `resume()` into a second\n // `gatewayCallback` on the same order group, undeduplicated at every layer (no ownership check,\n // no idempotency key in `PaymentGatewayCallbackInput`, and VTEX's behaviour on a repeat carried\n // as unmeasured residual risk under PEXP-2049), doubling pressure on the account-wide\n // `vtex.checkout` breaker as well. Hence `placedAttempt` first: re-asserting the terminal record\n // the failed write was carrying is the only phase that is true there, and it can never be a\n // downgrade, because `writeAttemptIfCurrent` refuses it outright once `settled` has landed\n // (INV-6). Since T176 the `track?.()` call sits before that write, so for a tracker that throws\n // this recovery write is the one that lands `settled`.\n //\n // `writeAttemptIfCurrent`, never a plain write (INV-1): this closure may be running after its\n // own CTA unmounted (NFR-3), so a newer completion for this same attempt must win instead. That\n // write is itself wrapped, because it goes through the same caller-supplied `setAttempt` that may\n // be what threw in the first place (fix round, High finding): `writeAttemptIfCurrent` passes the\n // setter a *function* updater, so a writer that rejects that form — one that serializes,\n // schema-validates or persists what it is given — lands the plain genesis write and throws on\n // every write after it, this one included. Unguarded, that second throw escaped `run()` and left\n // this hook's contract (FR-6: resolve, never reject) resting on `settleSequence` catching it a\n // frame away. Nothing further can be tried once the writer itself is broken — a plain\n // `setAttempt` fallback is exactly the stale-closure clobber INV-1 forbids — so it is logged and\n // the result is returned either way: the `outcome` a branch already fixed (T176, FR-5), or\n // NETWORK_ERROR when the throw came before any branch fixed one. Only a sanitised error *name* is\n // logged, on both lines (FR-7): the closure that threw may hold the card, and a message, a stack,\n // or the raw writable `.name` could echo it. See `loggableErrorName()`.\n console.error('[payment.placeOrder] the place-order sequence threw', attemptId, loggableErrorName(error));\n writeRecoveryRecord(\n setAttempt,\n attemptId,\n recoveryRecord(attemptId, placedAttempt, handedOver, settledOrderGroup),\n );\n // `handedOver` is the one fact that says whether the card may have left: a throw before the hand-over\n // (or after a pre-network refusal cleared it) cannot have reached the gateway, a throw after it cannot\n // be shown not to have.\n return outcome ?? { orderId: null, userErrors: [NETWORK_ERROR], reachedGateway: handedOver };\n }\n }, [\n startTransaction,\n gatewayCallback,\n track,\n path,\n resolve,\n resolvePreflight,\n resolveMissingMethodExplainer,\n selectedMethod,\n selectedInstallment,\n billingAddressDifferent,\n submitGate,\n setAttempt,\n ]);\n\n const placeOrder = useCallback<UsePlaceOrder['placeOrder']>(() => {\n // A second click lands before T14's disabled state commits a render, and duplicating a\n // transaction is the worst outcome this surface has — so the guard is the in-flight promise\n // itself, not the `placing` flag. Both callers observe the one attempt that ran.\n if (inFlight.current !== null) return inFlight.current;\n\n // FR-1 (T56): `setPlacing(true)` moved into `run()`/`resume()`, immediately before their own first\n // `await`, so a synchronous refusal never commits a `placing: true` frame. `run()`'s and `resume()`'s\n // outcomes share this one settle wrapper (T29).\n const settleSequence = (sequence: Promise<PlaceOrderResult>): Promise<PlaceOrderResult> => {\n setUserErrors([]);\n setOrderId(null);\n setReachedGateway(null);\n\n const settle = (result: PlaceOrderResult): PlaceOrderResult => {\n inFlight.current = null;\n setPlacing(false);\n setUserErrors(result.userErrors);\n setOrderId(result.orderId);\n setReachedGateway(result.reachedGateway);\n return result;\n };\n\n // `run()`/`resume()` resolve for every path this hook drives itself, but a collaborator outside\n // its control (a different $actions provider, or a mock resolving `undefined`) can still reject\n // one. Settling on both paths keeps `placing` from wedging true forever and surfaces an error, so\n // the CTA is clickable again either way. Spec correction (review F2): whether the *next* click\n // actually retries anything is decided by the attempt's phase, not by this settle —\n // `invokeStartTransaction`/`invokeGatewayCallback` each write a resumable phase before their own\n // await and normalize their result, so a throw on either leg lands on `refused`/`unresolved`\n // (T156). T170 closes the rest of that class at the source: `run()` now catches its own throws\n // and settles the record itself, so what is left is a collaborator-owned promise that never\n // settles at all — nothing has thrown, so there is nothing for either that catch or this settle\n // to act on, and bounding it needs a deadline nobody has set yet (T170's own OQ-1).\n const outcome = sequence.then(settle, () =>\n settle({ orderId: null, userErrors: [NETWORK_ERROR], reachedGateway: true }),\n );\n\n inFlight.current = outcome;\n return outcome;\n };\n\n // T29 (FR-2, FR-3): the CTA's next legal action is derived from the surviving attempt's own phase,\n // not always `run()`. `sent`/`unresolved` resume through a module-level function with no access at\n // all to `startTransaction` or the submission channel (Decision 3); `opening`/`finalizing` are\n // already running — possibly from a mount this closure has outlived (NFR-3) — and `settled` is\n // terminal (INV-6), so none of the three issues anything, independent of whatever a composing\n // CTA's own disabled state happens to do. Only the terminal one reads as a success, though: see\n // the in-flight arm's own comment below. `refused` falls through to a full run below like `null` —\n // nothing was paid, or the callback answered with an error the retry rule frees (T110), so a fresh\n // attempt is legal (Decision 4). The `'sent'` check just below is defensive-only: `run()` never leaves\n // the record resting there today (it writes `finalizing` directly, same tick — see that write site's\n // own comment); kept so a future write path resting at `sent` would still resume correctly instead of\n // silently falling through to a full run.\n if (attempt) {\n if (attempt.phase === 'settled') {\n // FR-4 (T110): through `settleSequence` like every other arm, so this hook's own state reads the\n // settled outcome.\n return settleSequence(Promise.resolve({ orderId: attempt.orderId, userErrors: [], reachedGateway: true }));\n }\n // T170 (FR-5, Decision 4): split off the two in-flight phases, which used to share this arm and\n // so resolved the empty `userErrors` this hook's own contract defines as success\n // (`PlaceOrderResult`) — a reader that did not start the sequence was handed a success shape for\n // an order still in flight, and a CTA derived from it (`place-order.tsx`'s `succeeded`) rendered\n // \"placed\" for an order nobody had paid for. Settled through `settleSequence` like every other\n // outcome, so the refusal reaches this hook's own `userErrors` rather than only the resolved\n // value a composing CTA may never read. Still issues nothing, and still reads the phase alone\n // (INV-4): waiting is the correct behaviour while the card POST is genuinely open.\n if (attempt.phase === 'opening' || attempt.phase === 'finalizing') {\n return settleSequence(\n Promise.resolve({\n orderId: attempt.orderId,\n userErrors: [PLACE_ORDER_IN_FLIGHT_ERROR],\n reachedGateway: true,\n }),\n );\n }\n if (attempt.phase === 'sent' || attempt.phase === 'unresolved') {\n return settleSequence(resume(gatewayCallback, setAttempt, setPlacing, attempt));\n }\n }\n\n return settleSequence(run());\n }, [run, attempt, gatewayCallback, setAttempt]);\n\n return useMemo(\n () => ({ placeOrder, placing, userErrors, orderId, reachedGateway }),\n [placeOrder, placing, userErrors, orderId, reachedGateway],\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-place-order.js","names":[],"sources":["../../../src/sdk/payment/use-place-order.ts"],"sourcesContent":["'use client';\n\nimport { useCallback, useMemo, useRef, useState } from 'react';\nimport { loggableErrorName } from '../../utils/loggable-error-name.js';\nimport { resolveNavigableHref } from '../../utils/safe-href.js';\nimport type { ComponentActions } from '../actions/types.js';\nimport type { AnalyticsActions } from '../analytics/use-analytics-actions.js';\nimport type { PaymentGatewayTransaction } from './gateway.js';\nimport { isTrustedGatewayOrigin } from './gateway-origin.js';\nimport { PAYMENT_NETWORK_ERROR_CODE, type PaymentMoney, type PaymentUserError } from './operations.js';\nimport { claimPurchase, emitPurchaseForOrder, type SettledOrderRead } from './resumed-purchase.js';\nimport {\n type PaymentMissingMethodExplainer,\n type PaymentPreflightCheck,\n type PaymentSubmission,\n type PaymentSubmissionChannel,\n type PaymentSubmissionResult,\n usePaymentSubmission,\n} from './submission-registry.js';\nimport type {\n PaymentActions,\n PaymentGatewayCallbackResult,\n PaymentStartTransactionResult,\n} from './use-payment-actions.js';\n\n/**\n * The place-order orchestration (specs/use-place-order.md, T13; docs/features/credit-card/rfc.md\n * § D9) — the PCI-critical core of the credit-card feature, and the only module that knows placing an\n * order is three calls rather than one mutation.\n *\n * **This hook never holds card data.** It resolves an opaque continuation from the submission channel\n * by the shopper's entry path and invokes it between the two backend legs; the card stays in the\n * holder's closure (T7 for a new card, T12 for a saved one), which calls\n * `$actions.payment.sendPayments` itself. So the actions this hook needs are `startTransaction`,\n * `gatewayCallback`, and `analytics.track` — never the gateway façade. Two tests assert the boundary\n * against this file's own source text: no card field is named in code, and the single import from\n * `./gateway.js` is the transaction descriptor's type.\n *\n * The order of the pre-flight is load-bearing, not stylistic. The path, the continuation, and the\n * selected method are all resolved *before* `startTransaction` runs, because a transaction that is\n * opened and then cannot be completed is dangling state on the shopper's order form.\n *\n * The gateway leg resolves inline and the shopper stays on the page, unless the bank asks for a 3D Secure\n * challenge. A store that authored a return page passes `challengeReturn`, so the descriptor carries a\n * `callbackUrl` and a callback that answers a challenge with a navigable bank URL ends in a redirect: the\n * result still carries `REDIRECT_REQUIRED`, `placing` stays true, and `redirectUrl` is exposed for the\n * component to navigate to (T208). A store with no return page, or an answer that cannot be followed back,\n * keeps the locked refusal.\n *\n * Every failure resolves as `userErrors`; nothing here throws for an expected outcome, so the CTA\n * (T14) renders one error path. Its one per-code branch is PLACE_ORDER_UNCONFIRMED, a pending outcome\n * it shows as a status rather than an error (T176).\n */\n\ntype TrackAction = AnalyticsActions['track'] | undefined;\n\n// ---------------------------------------------------------------------------\n// Client-synthesized error codes\n// ---------------------------------------------------------------------------\n\n/** No entry path chosen, or no holder registered for the one that was. */\nexport const PAYMENT_MISSING_SUBMISSION_CODE = 'MISSING_PAYMENT_SUBMISSION';\n\n/**\n * No payment method selected. Reuses `PaymentStartTransactionErrorCode`'s own value for the same\n * state, so a consumer matches one code whichever side detected it.\n */\nexport const PAYMENT_MISSING_METHOD_CODE = 'MISSING_PAYMENT_METHOD_SELECTION';\n\n/**\n * The transaction opened but cannot be paid — an incomplete payload, or an origin this client will\n * not post a card to. Reuses the server's code for the same meaning (T18 Decision 5).\n */\nexport const PAYMENT_INVALID_TRANSACTION_CODE = 'INVALID_TRANSACTION_DATA';\n\n/**\n * The attempt's own sequence is still running — opened or finalizing, not yet settled. Not a\n * refusal: nothing failed, the record just belongs to a sequence this caller did not start.\n */\nexport const PLACE_ORDER_IN_FLIGHT_CODE = 'PLACE_ORDER_IN_FLIGHT';\n\n/**\n * The gatewayCallback leg ran after the gateway accepted the card POST, and this client could not confirm\n * its outcome — the payment may or may not have been authorized. Not a refusal (T176).\n */\nexport const PLACE_ORDER_UNCONFIRMED_CODE = 'PLACE_ORDER_UNCONFIRMED';\n\n// The `message` of each error below is diagnostic (logs, `onResult`); `PlaceOrder` words the code.\nconst MISSING_SUBMISSION_ERROR: PaymentUserError = {\n code: PAYMENT_MISSING_SUBMISSION_CODE,\n message: 'Enter your payment details before placing the order.',\n};\n\nconst MISSING_METHOD_ERROR: PaymentUserError = {\n code: PAYMENT_MISSING_METHOD_CODE,\n message: 'Choose a payment method before placing the order.',\n};\n\nconst INVALID_TRANSACTION_ERROR: PaymentUserError = {\n code: PAYMENT_INVALID_TRANSACTION_CODE,\n message: 'Unable to start the payment. Please try again.',\n};\n\nconst PLACE_ORDER_IN_FLIGHT_ERROR: PaymentUserError = {\n code: PLACE_ORDER_IN_FLIGHT_CODE,\n message: 'This order is already being placed. Please wait.',\n};\n\n/**\n * Worded as a pending state, never a failure: it must not tell the shopper to try again (FR-1).\n * `PlaceOrder`'s `unconfirmedMessage` source copy repeats this text verbatim.\n */\nconst PLACE_ORDER_UNCONFIRMED_ERROR: PaymentUserError = {\n code: PLACE_ORDER_UNCONFIRMED_CODE,\n message: 'We could not confirm your order yet. Your payment may still be processing, so please do not pay again.',\n};\n\n/** Mirrors `usePaymentActions`' own infrastructure-failure userError, which that module keeps private. */\nconst NETWORK_ERROR: PaymentUserError = {\n code: PAYMENT_NETWORK_ERROR_CODE,\n message: 'Unable to complete the request. Please try again.',\n};\n\n// ---------------------------------------------------------------------------\n// Data the descriptor needs from the read model\n// ---------------------------------------------------------------------------\n\nexport type PlaceOrderInstallment = Readonly<{\n count: number;\n /** Per-installment amount, as a display decimal. The gateway's unit is minor units. */\n value: PaymentMoney;\n /** `null` when the order form did not say. The gateway body still gets a number: `?? 0` at the descriptor. */\n interestRate: number | null;\n}>;\n\nexport type PlaceOrderMethod = Readonly<{ id: string; groupName: string }>;\n\n/**\n * The `Payment` slice T14's fragment forwards (arity-1, ADR-0015) — the same convention as\n * `PaymentSectionData`. These five values are the part of the descriptor `paymentStartTransaction`\n * does not carry: T18 deliberately did not re-project the read model onto a write payload.\n */\nexport type PlaceOrderData = {\n selectedMethodId?: string | null;\n selectedInstallment?: PlaceOrderInstallment | null;\n methods?: readonly PlaceOrderMethod[];\n} | null;\n\nexport type PlaceOrderResult = Readonly<{\n /** The placed order's id, once the callback resolved one. */\n orderId: string | null;\n /**\n * The Relay Node ID of the placed order, opaque to this hook: the id `Query.order(id:)` takes and the\n * confirmation route carries. Set on a placed order only; absent on every other outcome.\n */\n orderNodeId?: string | null;\n /** Empty on success. Carries the server's own codes verbatim when a backend leg reported one. */\n userErrors: readonly PaymentUserError[];\n /**\n * `false` only when the card provably never left the browser: a refusal raised before the sequence\n * began, a `startTransaction` failure, or a continuation that reported it refused before issuing any\n * request. In each a fresh full attempt is legal. `true` for everything else, including every outcome\n * this hook cannot classify — a resume, a placed order, an in-flight short-circuit, a throw after the\n * card was handed over. Read this instead of inferring it from an error code (T112).\n */\n reachedGateway: boolean;\n /**\n * The bank's challenge page, set only on a callback answer that asked for a challenge with a navigable\n * absolute URL, when the descriptor carried a return URL. The result then still carries\n * `REDIRECT_REQUIRED` in `userErrors`: an empty list is the success shape, and a redirect is not one.\n */\n redirectUrl?: string | null;\n}>;\n\nexport type UsePlaceOrder = Readonly<{\n /** Runs the three-call sequence. Resolves; never rejects for an expected outcome. */\n placeOrder: () => Promise<PlaceOrderResult>;\n /** True while the sequence runs — what T14's blocking loading state renders. */\n placing: boolean;\n /** The latest attempt's userErrors. Empty before the first attempt and while one is in flight. */\n userErrors: readonly PaymentUserError[];\n /** The latest attempt's order id. */\n orderId: string | null;\n /** The latest attempt's order Node id. */\n orderNodeId: string | null;\n /** The latest attempt's `PlaceOrderResult.reachedGateway`. `null` before the first attempt and while one is in flight. */\n reachedGateway: boolean | null;\n /** The bank's challenge page while the shopper is being sent there, else `null`. `placing` stays true meanwhile. */\n redirectUrl: string | null;\n}>;\n\n// ---------------------------------------------------------------------------\n// Payload settling\n// ---------------------------------------------------------------------------\n\n/**\n * The start payload once every field the descriptor needs is known non-null. `userProfileId` is the\n * exception: a guest order legitimately has none.\n */\ntype SettledTransaction = Readonly<{\n transactionId: string;\n merchantName: string;\n gatewayUrl: string;\n orderGroup: string;\n orderNodeId: string | null;\n userProfileId: string | null;\n value: PaymentMoney;\n valueInCents: number;\n referenceValueInCents: number;\n}>;\n\n/** Identifiers, a host, and amounts — nothing here is sensitive, so naming them in a log is safe. */\nfunction missingTransactionFields(payload: PaymentStartTransactionResult): string[] {\n return Object.entries({\n transactionId: payload.transactionId,\n merchantName: payload.merchantName,\n orderGroup: payload.orderGroup,\n value: payload.value,\n valueInCents: payload.valueInCents,\n referenceValueInCents: payload.referenceValueInCents,\n })\n .filter(([, field]) => field === null)\n .map(([name]) => name);\n}\n\n/**\n * Narrows a payload that reported no userErrors, or refuses it. The origin is checked first and\n * separately: it is the field that decides where a card is sent, and a refusal here happens before\n * any holder is asked to assemble one.\n */\nfunction settleTransaction(payload: PaymentStartTransactionResult): SettledTransaction | null {\n if (!isTrustedGatewayOrigin(payload.gatewayUrl)) {\n console.error('[payment.placeOrder] the opened transaction carries no trusted https gateway origin');\n return null;\n }\n\n const { transactionId, merchantName, orderGroup, userProfileId, value, valueInCents, referenceValueInCents } =\n payload;\n\n if (\n transactionId === null ||\n merchantName === null ||\n orderGroup === null ||\n value === null ||\n valueInCents === null ||\n referenceValueInCents === null\n ) {\n console.error(\n '[payment.placeOrder] the opened transaction payload is incomplete',\n missingTransactionFields(payload).join(', '),\n );\n return null;\n }\n\n return {\n gatewayUrl: payload.gatewayUrl,\n transactionId,\n merchantName,\n orderGroup,\n orderNodeId: payload.orderNodeId ?? null,\n userProfileId,\n value,\n valueInCents,\n referenceValueInCents,\n };\n}\n\n/**\n * The one derived amount on the descriptor. VTEX's `payments[0]` carries no per-installment minor-unit\n * value, so T18 could not expose one and the conversion stays here — rounded, because\n * `19.99 * 100 === 1998.9999999999998`. `Number()` is not redundant: the components codegen maps the\n * `Decimal` scalar to `number` while the resolver serializes it as a string (see the spec's Open items).\n */\nfunction toMinorUnits(amount: number): number {\n return Math.round(Number(amount) * 100);\n}\n\n/**\n * The one call into holder-owned code. A holder's continuation is specified to resolve a\n * `PaymentSubmissionResult` — `sendPayments` already collapses every gateway outcome to one — so a throw,\n * or a result that is not that shape, means its contract broke. Only a sanitised error *name* is logged\n * (T160): the closure that threw holds the card, and a message or stack could echo it — and so,\n * unsanitised, could `.name` itself, since it is a writable own property. See `loggableErrorName()`.\n *\n * T112 (Decision 6): the result is totalled here and fails closed. Anything that is not an object with an\n * array `userErrors` — a nullish resolution, or the bare array the contract returned before this change —\n * collapses to `{ reachedGateway: true, userErrors: [NETWORK_ERROR] }`, and a `reachedGateway` that is not\n * a boolean is read as `true`: only a literal `false` may free the attempt, so a holder that omits the\n * signal locks it rather than inviting a second payment. An empty `userErrors` is a success whatever the\n * flag says. Each field is read once, into a local, and the result handed back is a fresh object — a\n * getter that answers differently across reads has no second read to answer differently on. T39: the one\n * field kept beyond those two is `completesWithoutGateway`, and only as the literal `true` on a success.\n */\nasync function invokeSubmission(\n submit: PaymentSubmission,\n transaction: PaymentGatewayTransaction,\n): Promise<PaymentSubmissionResult> {\n try {\n // T160: totals the awaited resolution inside this `try`, mirroring `invokeStartTransaction`'s and\n // `invokeGatewayCallback`'s own T156 fix shape — a bare `return await submit(transaction)` let a\n // nullish resolution escape untouched, so the caller's own `submissionErrors.length` read threw\n // one call site away, latching the attempt at `opening` past the point `resume()` can reach it.\n const result = await submit(transaction);\n if (result == null) {\n console.error('[payment.placeOrder] the registered submission resolved with no result');\n return { reachedGateway: true, userErrors: [NETWORK_ERROR] };\n }\n\n const { reachedGateway, userErrors, completesWithoutGateway } = result;\n if (!Array.isArray(userErrors)) {\n console.error('[payment.placeOrder] the registered submission resolved a result with no userErrors list');\n return { reachedGateway: true, userErrors: [NETWORK_ERROR] };\n }\n\n if (userErrors.length === 0) {\n // T39: kept only as the literal `true` on a success, and only then. Every other value, including a\n // truthy non-boolean, keeps the callback leg.\n return completesWithoutGateway === true\n ? { reachedGateway: true, userErrors: [], completesWithoutGateway: true }\n : { reachedGateway: true, userErrors: [] };\n }\n\n if (typeof reachedGateway !== 'boolean') {\n console.error('[payment.placeOrder] the registered submission resolved a non-boolean reachedGateway');\n return { reachedGateway: true, userErrors };\n }\n\n return { reachedGateway, userErrors };\n } catch (error) {\n console.error('[payment.placeOrder] the registered submission threw', loggableErrorName(error));\n return { reachedGateway: true, userErrors: [NETWORK_ERROR] };\n }\n}\n\n/**\n * The pre-flight counterpart of `invokeSubmission`, and it inherits that function's one rule: the\n * closure that throws holds the card, so only a sanitised error *name* is logged (T160) — never a\n * message, a stack, or the raw writable `.name` property. See `loggableErrorName()`.\n */\nfunction runPreflight(check: PaymentPreflightCheck): readonly PaymentUserError[] {\n try {\n return check();\n } catch (error) {\n console.error('[payment.placeOrder] the registered pre-flight threw', loggableErrorName(error));\n return [NETWORK_ERROR];\n }\n}\n\n/**\n * Asks the registered missing-method explainer (T47) why nothing is selected. A hint, never a check\n * (Decision 5): a throw, a non-object, or an object whose `code` or `message` is not a string all answer\n * `null`, so the caller keeps the generic refusal. A throw logs only the sanitised error name — the\n * closure that throws can see the typed number, so never its message.\n */\nfunction runMissingMethodExplainer(explain: PaymentMissingMethodExplainer): PaymentUserError | null {\n try {\n const explained: unknown = explain();\n if (typeof explained !== 'object' || explained === null) return null;\n const { code, message } = explained as { code?: unknown; message?: unknown };\n return typeof code === 'string' && typeof message === 'string' ? { code, message } : null;\n } catch (error) {\n console.error('[payment.placeOrder] the registered explainer threw', loggableErrorName(error));\n return null;\n }\n}\n\n/**\n * The `startTransaction` counterpart of `invokeSubmission`/`runPreflight` (fix pass, Medium finding):\n * before this, `run()` awaited `startTransaction()` directly, so a **rejection** — as opposed to a\n * resolved payload carrying `userErrors` — escaped past every one of `run()`'s own outcome branches\n * and `settleSequence`'s rejection handler never touches the attempt record, leaving it stuck at\n * `opening` for the payment root's lifetime. `opening` is the one phase `placeOrder()` treats as\n * \"already running\" with no failure exit, so every later click resolves `{ orderId: null, userErrors:\n * [] }` without running anything — a false \"Order placed successfully.\" with the CTA disabled forever.\n * `usePlaceOrder` is exported public API and `$actions` is compiler-injected and pluggable, so a\n * provider that rejects here is reachable, not hypothetical. Collapsing the rejection to a resolved\n * payload with one `userErrors` entry means `run()`'s own existing `payload.userErrors.length > 0`\n * branch is what catches it — the same `refused` outcome a server-reported refusal already gets,\n * correct here too since nothing can have been paid before the transaction even opened.\n *\n * T156 fold: the result is destructured inside this `try`, not returned as `await startTransaction()`\n * untouched — a provider resolving `undefined` used to throw at the caller's own\n * `payload.userErrors.length` read, past this function's own `catch`, latching the attempt at\n * `opening` exactly like the rejection case above. Destructuring a nullish resolution throws here\n * instead, so it collapses the same way. Mirrors `invokeGatewayCallback`'s own reason for\n * destructuring, below.\n *\n * Fix pass (review F1/S1): `userErrors` is defaulted with `?? [NETWORK_ERROR]`, not `?? []` — a\n * *partial* payload (present, but missing `userErrors`, e.g. a provider whose GraphQL selection omits\n * the field) used to leave it `undefined`, reaching the caller's `payload.userErrors.length` read\n * (`:627`) and throwing past this function's own `catch`, latching the attempt at `opening` exactly\n * like the cases already fixed. `userErrors` is this result's only signal of whether anything actually\n * went wrong, so a payload that omits it defaults to a *failure*, mirroring a rejection — defaulting to\n * `[]` instead would settle the attempt on a payload this function never actually saw. (The in-repo\n * default provider's own `payload?.userErrors ?? []`, `use-payment-actions.ts:202`, defaults to `[]`\n * only because there `payload` itself — not just the field — is what may be missing.)\n */\nasync function invokeStartTransaction(\n startTransaction: PaymentActions['startTransaction'],\n): Promise<PaymentStartTransactionResult> {\n try {\n const {\n transactionId,\n merchantName,\n gatewayUrl,\n orderGroup,\n orderNodeId,\n userProfileId,\n value,\n valueInCents,\n referenceValueInCents,\n userErrors,\n } = await startTransaction();\n if (userErrors == null) {\n console.error('[payment.placeOrder] startTransaction resolved with no userErrors', orderGroup);\n }\n return {\n transactionId,\n merchantName,\n gatewayUrl,\n orderGroup,\n orderNodeId,\n userProfileId,\n value,\n valueInCents,\n referenceValueInCents,\n userErrors: userErrors ?? [NETWORK_ERROR],\n };\n } catch (error) {\n console.error('[payment.placeOrder] startTransaction threw', loggableErrorName(error));\n return {\n transactionId: null,\n merchantName: null,\n gatewayUrl: null,\n orderGroup: null,\n orderNodeId: null,\n userProfileId: null,\n value: null,\n valueInCents: null,\n referenceValueInCents: null,\n userErrors: [NETWORK_ERROR],\n };\n }\n}\n\n/**\n * The `gatewayCallback` counterpart of `invokeStartTransaction` above (T156, PR #756 review R-004):\n * before this, both call sites below awaited `gatewayCallback` directly, so a **rejection**\n * escaped past every outcome branch and `settleSequence`'s rejection handler never touches the attempt\n * record, leaving it stuck at `finalizing` for the payment root's lifetime — `placeOrder()` treats\n * `finalizing` exactly like `opening`, an already-running phase with no failure exit, so every later\n * click resolved `{ orderId: attempt.orderId, userErrors: [] }` without issuing anything.\n *\n * The destination is `unresolved`, never `refused` like `invokeStartTransaction`'s own collapse: this\n * leg only runs once the card already reached the gateway, so nothing here can pretend no money moved.\n * `unresolved` is exactly the phase `PlaceOrderAttemptPhase` already documents for this case, below,\n * and is already resumable.\n *\n * Three failure shapes collapse to the same `NETWORK_ERROR` result, logged distinctly (fix pass,\n * review F1/S1/F3): a **rejection**, caught below; a **nullish resolution** (`undefined`/`null`),\n * caught by the explicit check that follows rather than by letting a destructure throw, so it logs a\n * line of its own instead of printing identically to a rejection; and a **partial resolution** — an\n * object present but missing `userErrors`, e.g. a provider whose GraphQL selection omits the field —\n * defaulted with `?? [NETWORK_ERROR]`, not `?? []`. That last case used to return `{ orderId,\n * userErrors }` untouched, leaving `userErrors: undefined`, so the *caller's* `callback.userErrors.length`\n * read threw one call site away from where this wrapper's whole job is to stop exactly that. `[]` would\n * default a payload this function never actually saw to a clean success; `userErrors` is the only\n * signal of whether the callback settled anything, so omitting it means treating it as a failure.\n * Both failure branches log `orderGroup` and `attemptId` — neither is card data\n * (`missingTransactionFields`'s comment above), and this is the one point in the flow where money may\n * already have moved — but never the error's message, stack, or any call argument (FR-6).\n *\n * No `gatewayCallback` call may exist in this module outside this function (INV-2, asserted against\n * this file's own source text in use-place-order.test.tsx) — the two call sites below (`resume()` and\n * `run()`) call this and nothing else.\n */\nasync function invokeGatewayCallback(\n gatewayCallback: PaymentActions['gatewayCallback'],\n orderGroup: string,\n attemptId: string,\n): Promise<PaymentGatewayCallbackResult> {\n let result: PaymentGatewayCallbackResult | null | undefined;\n try {\n result = await gatewayCallback(orderGroup);\n } catch (error) {\n console.error('[payment.placeOrder] gatewayCallback threw', orderGroup, attemptId, loggableErrorName(error));\n return { orderId: null, orderNodeId: null, userErrors: [NETWORK_ERROR] };\n }\n\n if (result == null) {\n console.error('[payment.placeOrder] gatewayCallback resolved with no result', orderGroup, attemptId);\n return { orderId: null, orderNodeId: null, userErrors: [NETWORK_ERROR] };\n }\n\n if (result.userErrors == null) {\n console.error('[payment.placeOrder] gatewayCallback resolved with no userErrors', orderGroup, attemptId);\n }\n return {\n orderId: result.orderId ?? null,\n orderNodeId: result.orderNodeId ?? null,\n redirectUrl: result.redirectUrl ?? null,\n userErrors: result.userErrors ?? [NETWORK_ERROR],\n };\n}\n\n/**\n * The codes the server sends on `gatewayCallback` that are not a verdict on the order, so\n * `callbackFreesAttempt` never frees an attempt that carries one (T110, Decision 3):\n * `REDIRECT_REQUIRED` is a bank challenge, and `MISSING_TRANSACTION_COOKIES` and `ORDER_GROUP_MISMATCH`\n * mean the card POST was already accepted while the callback never reached VTEX, so a second POST could\n * authorize the card twice.\n */\nconst REDIRECT_REQUIRED_CODE = 'REDIRECT_REQUIRED';\nconst MISSING_TRANSACTION_COOKIES_CODE = 'MISSING_TRANSACTION_COOKIES';\nconst ORDER_GROUP_MISMATCH_CODE = 'ORDER_GROUP_MISMATCH';\n\n/** The entry's `code`, or `undefined` when it cannot be read (a `null` entry, a getter that throws). */\nfunction readCode(userError: PaymentUserError): unknown {\n try {\n return userError.code;\n } catch {\n return undefined;\n }\n}\n\n/** True when the entry carries a string `code` other than this package's own `NETWORK_ERROR`. Never throws. */\nfunction carriesServerCode(userError: PaymentUserError): boolean {\n const code = readCode(userError);\n return typeof code === 'string' && code !== PAYMENT_NETWORK_ERROR_CODE;\n}\n\n/**\n * T110: whether a `gatewayCallback` that reported `userErrors` frees the attempt for a fresh run. It does\n * when the server sent a string `code`, none of the entries is a locked code (`REDIRECT_REQUIRED`,\n * `MISSING_TRANSACTION_COOKIES`, `ORDER_GROUP_MISMATCH`), and the result carries no `orderId`: an order the\n * callback confirmed is never freed. Everything else keeps the attempt resume-only, because a payment that may have gone through\n * must never be paid twice. Total, and never throws: a non-array, an empty list or an entry nobody can read\n * frees nothing.\n *\n * Live since T203: the server answers a refused callback with `PAYMENT_DECLINED`, which frees the attempt,\n * while the locked codes it also sends stay resume-only. The rule fails open on a code it does not\n * know (spec AC-5: no per-code table); an allowlist of the codes that really are declines is not this\n * hook's job.\n */\nfunction callbackFreesAttempt(callback: {\n readonly orderId: string | null;\n readonly userErrors: readonly PaymentUserError[];\n}): boolean {\n const { orderId, userErrors } = callback;\n if (orderId != null) return false;\n if (!Array.isArray(userErrors)) return false;\n return (\n userErrors.some((userError) => carriesServerCode(userError)) &&\n !userErrors.some((userError) => {\n const code = readCode(userError);\n return (\n code === REDIRECT_REQUIRED_CODE ||\n code === MISSING_TRANSACTION_COOKIES_CODE ||\n code === ORDER_GROUP_MISMATCH_CODE\n );\n })\n );\n}\n\n/**\n * T208 (Decision 3): the bank's challenge page, when this callback answer may be followed. Only when a return\n * URL was sent (the bank could not send the shopper back otherwise), the answer asks for a challenge and\n * places no order, and the URL is absolute `http(s)`. A site-relative URL is refused too: the only\n * navigation off the store's own pages is the one to the bank. Total, and never throws.\n */\nfunction readChallengeRedirect(\n callback: {\n readonly orderId: string | null;\n readonly redirectUrl?: string | null;\n readonly userErrors: readonly PaymentUserError[];\n },\n returnUrlSent: boolean,\n): string | null {\n if (!returnUrlSent || callback.orderId != null) return null;\n const { userErrors, redirectUrl } = callback;\n if (!Array.isArray(userErrors) || typeof redirectUrl !== 'string') return null;\n if (!userErrors.some((userError) => readCode(userError) === REDIRECT_REQUIRED_CODE)) return null;\n const destination = resolveNavigableHref(redirectUrl);\n // Character for character: a URL the gate had to trim is not the one the server sent.\n return destination.safe && destination.href === redirectUrl && !destination.href.startsWith('/') ? redirectUrl : null;\n}\n\n/** The result field a redirect adds: absent, never an explicit `undefined`, when there is nothing to follow. */\nfunction redirectField(redirectUrl: string | null): { redirectUrl?: string } {\n return redirectUrl === null ? {} : { redirectUrl };\n}\n\n/**\n * T176 (FR-2, FR-3): what the caller reads from a `gatewayCallback` that reported `userErrors`, on both\n * paths that issue it. The gateway already accepted the card POST by then, so a result made only of this\n * package's own collapse — `PAYMENT_NETWORK_ERROR_CODE`, which `usePaymentActions` and\n * `invokeGatewayCallback` resolve for every thrown, lost or shapeless response — says nothing about\n * whether VTEX processed the order. It becomes PLACE_ORDER_UNCONFIRMED rather than a \"try again\" that\n * invites a second payment. A code the server sent is an answer, and comes back unchanged (INV-10).\n *\n * Total, and never throws: a non-array, or an entry with no readable string `code`, carries no server\n * code, so it reads as unconfirmed too. A server code elsewhere in the same list still wins, whatever\n * order the entries come in.\n */\nfunction reconcileCallbackUserErrors(userErrors: readonly PaymentUserError[]): readonly PaymentUserError[] {\n try {\n if (!Array.isArray(userErrors)) return [PLACE_ORDER_UNCONFIRMED_ERROR];\n if (userErrors.length === 0) return userErrors;\n return userErrors.some((userError) => carriesServerCode(userError)) ? userErrors : [PLACE_ORDER_UNCONFIRMED_ERROR];\n } catch {\n return [PLACE_ORDER_UNCONFIRMED_ERROR];\n }\n}\n\n/**\n * `crypto.randomUUID()` is secure-context-only (fix pass, Low finding) — on a plain-HTTP preview or\n * staging origin the method does not exist on `crypto` at all, so calling it throws, and every click\n * collapses to a generic `NETWORK_ERROR` with no diagnostic. `localhost` is always a secure context,\n * so dev never reproduces it. Falls back to a timestamp+random string, which only needs to be unique\n * for one payment root's lifetime (the staleness guard below is its one reader) — not a real UUID.\n */\nfunction generateAttemptId(): string {\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n }\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;\n}\n\n/**\n * Fix pass (High finding, race #2): guards every write past an attempt's genesis against a stale,\n * out-of-order completion clobbering a newer outcome. `run()`/`resume()` mint one write *before* their\n * own first await (safe by construction — nothing can have superseded an attempt this call itself just\n * created) and one or more writes *after* an await, once `startTransaction`/the continuation/\n * `gatewayCallback` resolve. Because the writing closure keeps running after its own CTA unmounts\n * (NFR-3), that later write cannot assume the shared attempt is still the one it started with — a\n * remounted or sibling instance may have already resumed it to a newer phase in the meantime. Using\n * the setter's own functional-update form (`SetPlaceOrderAttempt`) checks against whatever the shared\n * attempt actually is *when the write is applied*, not the value this closure captured before its\n * await — mirrors `selectionToken`'s own precedent (`matter/payment/use-payment-method-selection.ts`)\n * for the identical shape of problem, keyed on the attempt's own id instead of a second counter.\n * `settled` is additionally excluded because INV-6 forbids any transition away from it.\n */\nfunction writeAttemptIfCurrent(\n setAttempt: SetPlaceOrderAttempt | null | undefined,\n attemptId: string,\n next: PlaceOrderAttempt | null,\n): void {\n setAttempt?.((prev) => (prev !== null && prev.attemptId === attemptId && prev.phase !== 'settled' ? next : prev));\n}\n\n/** The record an attempt carries once nothing was paid: a fresh full sequence is legal next time. */\nfunction refusedRecord(attemptId: string): PlaceOrderAttempt {\n return { attemptId, phase: 'refused', orderGroup: null, orderId: null };\n}\n\n/**\n * The record `resume()` writes once its one `gatewayCallback` answered: `refused` when the retry rule\n * freed the attempt, `unresolved` for any other error, `settled` with the order id on success.\n */\nfunction resumedRecord(\n current: PlaceOrderAttempt,\n callback: PaymentGatewayCallbackResult,\n failed: boolean,\n freed: boolean,\n): PlaceOrderAttempt {\n if (freed) return refusedRecord(current.attemptId);\n if (failed) return { ...current, phase: 'unresolved' };\n return { ...current, phase: 'settled', orderId: callback.orderId, orderNodeId: callback.orderNodeId ?? null };\n}\n\n/**\n * The terminal record `run()`'s `catch` re-asserts: the `settled` one a throw may have interrupted, else\n * `unresolved` once the card was handed over, else `refused`. A failed leg writes the same pair, with\n * no placed attempt: `refused` when it freed the attempt, `unresolved` (keeping the order group) otherwise.\n */\nfunction recoveryRecord(\n attemptId: string,\n placedAttempt: PlaceOrderAttempt | null,\n handedOver: boolean,\n settledOrderGroup: string | null,\n): PlaceOrderAttempt {\n if (placedAttempt !== null) return placedAttempt;\n return handedOver\n ? { attemptId, phase: 'unresolved', orderGroup: settledOrderGroup, orderId: null }\n : refusedRecord(attemptId);\n}\n\n/**\n * `run()`'s recovery write, guarded because it goes through the same caller-supplied `setAttempt` that\n * may be what threw in the first place: nothing further can be tried once the writer itself is broken, so\n * it is logged (name only, FR-7) and the caller returns its result either way.\n */\nfunction writeRecoveryRecord(\n setAttempt: SetPlaceOrderAttempt | null | undefined,\n attemptId: string,\n record: PlaceOrderAttempt,\n): void {\n try {\n writeAttemptIfCurrent(setAttempt, attemptId, record);\n } catch (writeError) {\n console.error(\n '[payment.placeOrder] the place-order recovery write threw',\n attemptId,\n loggableErrorName(writeError),\n );\n }\n}\n\n/** `run()`'s refusal before any request: nothing was paid and the card never left the browser. */\nfunction refusalBeforeOpening(userErrors: readonly PaymentUserError[]): PlaceOrderResult {\n return { orderId: null, userErrors, reachedGateway: false };\n}\n\ntype SubmissionVerdict =\n | { kind: 'refused'; result: PlaceOrderResult }\n | { kind: 'ready'; submit: PaymentSubmission; method: PlaceOrderMethod };\n\nfunction refused(userErrors: readonly PaymentUserError[]): SubmissionVerdict {\n return { kind: 'refused', result: refusalBeforeOpening(userErrors) };\n}\n\ntype SubmissionChannelFacts = Pick<\n PaymentSubmissionChannel,\n 'path' | 'resolve' | 'resolvePreflight' | 'resolveMissingMethodExplainer'\n>;\n\n/**\n * T47 (Decision 2/4/5): the holder may know a more specific reason than \"choose a method\". Asked only\n * with nothing selected, and never before the checks that follow, so the order of checks cannot change.\n * Only the opaque refusal comes back, and only its code is logged.\n */\nfunction refuseMissingMethod({ path, resolveMissingMethodExplainer }: SubmissionChannelFacts): SubmissionVerdict {\n const explain = path === null ? null : resolveMissingMethodExplainer(path);\n const explained = explain === null ? null : runMissingMethodExplainer(explain);\n if (explained !== null) {\n console.error(\n '[payment.placeOrder] no payment method is selected, and the registered explainer named why',\n explained.code,\n );\n return refused([explained]);\n }\n console.error('[payment.placeOrder] no payment method is selected');\n return refused([MISSING_METHOD_ERROR]);\n}\n\n/**\n * Every synchronous refusal `run()` resolves before anything is opened, in their original order: no\n * registered submission, no selected method, the submit gate, then the holder's own pre-flight. A\n * transaction nothing can complete is dangling state on the order form. The first two causes are one\n * state to a shopper and differ only in the log.\n */\nfunction checkSubmissionPreconditions({\n channel,\n selectedMethod,\n submitGate,\n}: {\n channel: SubmissionChannelFacts;\n selectedMethod: PlaceOrderMethod | null;\n submitGate: PaymentUserError | null | undefined;\n}): SubmissionVerdict {\n const { path, resolve, resolvePreflight } = channel;\n const submit = path === null ? null : resolve(path);\n if (submit === null) {\n console.error('[payment.placeOrder] no submission is registered for the entry path', path ?? 'none');\n return refused([MISSING_SUBMISSION_ERROR]);\n }\n\n // `paymentSystem` and `groupName` come from the read model, not from the start payload, so a\n // missing selection would otherwise surface only after the transaction was already open.\n if (selectedMethod === null) return refuseMissingMethod(channel);\n\n // T24's submit gate: an already-resolved client-side refusal (`matter/payment/\n // usePaymentSubmitGate()`), joining the pre-flight so no transaction opens for a submission this\n // client already knows would be refused. Only the opaque verdict is logged — never a fact behind\n // it — keeping this module's own PCI boundary intact (Decision 2).\n if (submitGate) {\n console.error('[payment.placeOrder] a client-side gate refused the submission', submitGate.code);\n return refused([submitGate]);\n }\n\n // The holder's own pre-flight on the draft it holds (T27). Only the opaque refusal crosses back —\n // this module learns nothing about what was wrong, exactly as with the gate above.\n //\n // Absence is a silent no-op, not a refusal (FR-6): a holder that registered no check (the\n // two-argument overload) contributes nothing here, exactly as if this branch did not exist. Logging on\n // this branch would fire on the overwhelming majority of calls, diluting the signal this file's other\n // console.error calls carry for genuine anomalies; failing closed would refuse every one of those\n // callers, breaking `register`'s own optional third argument (NFR-6, Decision 2). A caller that wants\n // this protection must register a check.\n const preflight = path === null ? null : resolvePreflight(path);\n if (preflight) {\n const refusals = runPreflight(preflight);\n if (refusals.length > 0) {\n console.error('[payment.placeOrder] the registered pre-flight refused the submission', refusals[0]?.code);\n return refused(refusals);\n }\n }\n\n return { kind: 'ready', submit, method: selectedMethod };\n}\n\ntype OpenedTransaction =\n | { kind: 'refused'; result: PlaceOrderResult }\n | { kind: 'opened'; settled: SettledTransaction };\n\n/**\n * `run()`'s first backend leg: opens the transaction and settles its payload. Either refusal records\n * `refused` — nothing was paid, so a fresh full sequence stays legal next time — through\n * `writeAttemptIfCurrent`, not a plain write: the caller's closure may be running after its own CTA\n * unmounted (NFR-3), so a newer completion for the same attempt must win instead.\n */\nasync function openTransaction(\n startTransaction: PaymentActions['startTransaction'],\n setAttempt: SetPlaceOrderAttempt | null | undefined,\n attemptId: string,\n): Promise<OpenedTransaction> {\n const payload = await invokeStartTransaction(startTransaction);\n\n // Before the completeness check, so a server diagnosis is never replaced by a synthesized one —\n // T18 returns INVALID_TRANSACTION_DATA with every other field null for a transaction VTEX opened\n // with no merchant to pay, which a null-field check would otherwise swallow.\n if (payload.userErrors.length > 0) {\n writeAttemptIfCurrent(setAttempt, attemptId, refusedRecord(attemptId));\n return { kind: 'refused', result: refusalBeforeOpening(payload.userErrors) };\n }\n\n const settled = settleTransaction(payload);\n if (settled === null) {\n writeAttemptIfCurrent(setAttempt, attemptId, refusedRecord(attemptId));\n return { kind: 'refused', result: refusalBeforeOpening([INVALID_TRANSACTION_ERROR]) };\n }\n\n return { kind: 'opened', settled };\n}\n\n/** The descriptor `run()` hands to the registered submission. Nothing in it is sensitive. */\nfunction buildTransaction({\n settled,\n method,\n selectedInstallment,\n billingAddressDifferent,\n callbackUrl,\n}: {\n settled: SettledTransaction;\n method: PlaceOrderMethod;\n selectedInstallment: PlaceOrderInstallment | null;\n billingAddressDifferent: boolean | null | undefined;\n callbackUrl: string | null;\n}): PaymentGatewayTransaction {\n return {\n gatewayUrl: settled.gatewayUrl,\n transactionId: settled.transactionId,\n merchantName: settled.merchantName,\n orderGroup: settled.orderGroup,\n userProfileId: settled.userProfileId,\n paymentSystem: method.id,\n groupName: method.groupName,\n currencyCode: settled.value.currencyCode,\n valueInCents: settled.valueInCents,\n referenceValueInCents: settled.referenceValueInCents,\n installments: selectedInstallment?.count ?? 1,\n // A count: 1 installment is the full transaction value by definition. The read model's\n // selectedInstallment.value for a synthesized 1x tier is option.value (mappers.ts:597),\n // which can differ from the transaction's own value (Decision 8, spec:232) — e.g. an option\n // priced at 10000 on an order form whose value is 8997. Using settled.valueInCents for the\n // 1x case avoids charging option.value instead of the transaction value. For count > 1 the\n // tier value is upstream data, never synthesized, so the per-installment amount is correct.\n installmentsValueInCents:\n selectedInstallment && selectedInstallment.count !== 1\n ? toMinorUnits(selectedInstallment.value.amount)\n : settled.valueInCents,\n interestRate: selectedInstallment?.interestRate ?? 0,\n // Omitted entirely (never an explicit `undefined` key) when the caller passed none, matching\n // `callbackUrl`'s own absent-when-not-provided convention above (fix pass, review F1).\n ...(billingAddressDifferent == null ? {} : { billingAddressDifferent }),\n // Omitted entirely when the store authored no return page, so the gateway request is the one sent\n // before a challenge could be followed back.\n ...(callbackUrl === null ? {} : { callbackUrl }),\n };\n}\n\n/** The return URL for a group, or `null` when none was authored or the builder gave none. */\nfunction returnUrlFor(\n challengeReturn: ((orderGroup: string) => string | null) | null | undefined,\n orderGroup: string,\n): string | null {\n const url = challengeReturn?.(orderGroup);\n return typeof url === 'string' && url !== '' ? url : null;\n}\n\n/**\n * T29 (Decision 3, FR-3): the only path back into a `sent`/`unresolved` attempt — `gatewayCallback` is\n * the sole call it ever issues. Module scope, not a `useCallback` inside the hook (fix pass, Low\n * finding): the previous version of this doc comment claimed `startTransaction`, the submission\n * channel, and `PaymentGatewayTransaction` were \"not in scope,\" so a careless future edit could not\n * make a resume re-pay — true at runtime today (verified by identifier sweep) but false as a\n * structural guarantee, since all three were merely unreferenced identifiers a nested closure could\n * still have read. Hoisting here makes the claim actually true: none of those three is even\n * *importable* from a bare module-level function that takes only `gatewayCallback`/`setAttempt`/\n * `setPlacing`/`current` as arguments — a resume is safe by construction, not by convention.\n *\n * T110: a callback the retry rule frees records `refused`, as `run()` does, so the next click is a fresh\n * full run. This function still issues nothing but `gatewayCallback`.\n *\n * T208: a callback that answers a challenge redirects here too, when `returnUrlSent` says the original request\n * carried a return URL (the attempt record does not carry that fact). A resume that finishes the order reports\n * its purchase, reading value and currency from the settled order: the record may hold neither.\n */\nasync function resume(\n gatewayCallback: PaymentActions['gatewayCallback'] | undefined,\n setAttempt: SetPlaceOrderAttempt | null | undefined,\n setPlacing: (placing: boolean) => void,\n current: PlaceOrderAttempt,\n {\n returnUrlSent,\n track,\n readSettledOrder,\n }: {\n returnUrlSent: boolean;\n track: TrackAction;\n readSettledOrder: SettledOrderRead | null | undefined;\n },\n): Promise<PlaceOrderResult> {\n if (!gatewayCallback || current.orderGroup === null) {\n // Not reachable through this hook's own writes — §3's own invariant keeps `orderGroup` non-null\n // past `opening` — only through a caller-constructed attempt or a missing action namespace. Fails\n // the same way `run()`'s own first guard does, above.\n console.error('[payment.placeOrder] cannot resume an attempt with no order group');\n return { orderId: null, userErrors: [NETWORK_ERROR], reachedGateway: true };\n }\n\n // Mirrors `run()`'s own `setPlacing(true)` placement, immediately before its one `await`, so the\n // shared placing state stays accurate across a remount too. A plain write, guarded by\n // `writeAttemptIfCurrent` rather than an unconditional one, since `current` is a caller-supplied\n // snapshot that may already be stale by the time this async function actually runs.\n writeAttemptIfCurrent(setAttempt, current.attemptId, { ...current, phase: 'finalizing' });\n setPlacing(true);\n\n const callback = await invokeGatewayCallback(gatewayCallback, current.orderGroup, current.attemptId);\n const failed = callback.userErrors.length > 0;\n // T176 (FR-4, FR-7): the result is fixed by the callback before the write, so a caller-supplied\n // writer that throws can no longer turn a confirmed order into a rejection — `settleSequence` would\n // resolve that as NETWORK_ERROR. The record may stay at `finalizing` then; nothing here can repair a\n // broken writer. Only a sanitised error name is logged, as on every other catch in this module.\n const redirectUrl = failed ? readChallengeRedirect(callback, returnUrlSent) : null;\n const result: PlaceOrderResult = failed\n ? {\n orderId: null,\n userErrors: reconcileCallbackUserErrors(callback.userErrors),\n reachedGateway: true,\n ...redirectField(redirectUrl),\n }\n : { orderId: callback.orderId, orderNodeId: callback.orderNodeId ?? null, userErrors: [], reachedGateway: true };\n\n // T208: before the record write, like `run()`'s own event: the order is confirmed by now, and the record is\n // local bookkeeping that a throwing writer must not turn into a lost event. Never throws, never rejects.\n if (!failed && callback.orderNodeId != null) {\n await emitPurchaseForOrder({ track, readOrder: readSettledOrder, orderNodeId: callback.orderNodeId });\n }\n\n // T110: a callback the rule frees (see `callbackFreesAttempt`) records `refused`, exactly as in `run()`;\n // any other stays resume-only. `orderGroup` is cleared on `refused`, so a stale group is never resumed.\n const freed = failed && callbackFreesAttempt(callback);\n try {\n writeAttemptIfCurrent(setAttempt, current.attemptId, resumedRecord(current, callback, failed, freed));\n } catch (error) {\n console.error('[payment.placeOrder] the resume record write threw', current.attemptId, loggableErrorName(error));\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// The order-attempt record (T29, specs/make-an-order-attempt-survive-a.md) — an identity and a phase\n// that outlive the CTA's own mount, so a remount or a second click resumes a sequence instead of\n// re-running its dangerous half. Defined here, not in matter/payment/use-payment-method-selection.ts:\n// this is the only module that knows the sequence is three calls, and the layering stays\n// matter → sdk (submission-registry.tsx's own rule) — usePaymentMethodSelection imports this type,\n// mirroring `PaymentPath`'s own alias of `PaymentSubmissionPath`.\n// ---------------------------------------------------------------------------\n\n/**\n * How far an order attempt got. The phase, not an error code, is what says whether money may already\n * have moved — an aborted card POST and a refused transaction both surface as the same userError, and\n * only this distinguishes them.\n */\nexport type PlaceOrderAttemptPhase =\n /** `startTransaction` issued or resolved. Nothing has been paid. */\n | 'opening'\n /** A leg refused before the card left the browser, or `gatewayCallback` answered with a server-coded\n * error the retry rule frees (T110; dormant today, the server sends no such code yet). A fresh full\n * attempt is legal. */\n | 'refused'\n /** The continuation resolved with no userErrors — the card reached the gateway. */\n | 'sent'\n /** The card's fate is unknown: the continuation threw, the POST was aborted, or a finalization got no\n * answer or only a locked code (`REDIRECT_REQUIRED`, `MISSING_TRANSACTION_COOKIES`,\n * `ORDER_GROUP_MISMATCH`). Resume-only.\n * Treated exactly like `sent`. */\n | 'unresolved'\n /** `gatewayCallback` is in flight for this attempt's order group. */\n | 'finalizing'\n /** `gatewayCallback` resolved with no userErrors. Terminal. */\n | 'settled';\n\nexport type PlaceOrderAttempt = Readonly<{\n /** Identity that outlives any one mount. Not `useId()` — that is per-instance and resets. */\n attemptId: string;\n phase: PlaceOrderAttemptPhase;\n /** Resolved by `startTransaction`; the only key a resume needs. Null before `opening` settles. */\n orderGroup: string | null;\n /**\n * Resolved by `gatewayCallback`. Not an independent identifier: the resolver returns\n * `orderId: input.orderGroup` (`checkout/index.ts:1965`), echoing the argument back, because the\n * upstream success response body is discarded. So `orderId === orderGroup` on every success today.\n */\n orderId: string | null;\n /**\n * The Relay Node ID of the placed order, from `gatewayCallback` (or from `startTransaction` when no\n * callback runs). Absent until the attempt settles; opaque to this module.\n */\n orderNodeId?: string | null;\n}>;\n\n/**\n * Fix pass (High finding, race #2): widened past a plain value so a write can be made conditional on\n * whatever the shared attempt actually is *at write time* — the primitive `useState` already exposes\n * this (its setter has always accepted an updater function; only this package's own declared type\n * stopped a caller from using that form). A plain value remains valid; every existing caller that\n * never needed the guard is unaffected.\n */\nexport type SetPlaceOrderAttempt = (\n update: PlaceOrderAttempt | null | ((prev: PlaceOrderAttempt | null) => PlaceOrderAttempt | null),\n) => void;\n\n// ---------------------------------------------------------------------------\n// The hook\n// ---------------------------------------------------------------------------\n\nexport type UsePlaceOrderArgs = {\n /** `Payment` slice from the caller's fragment. */\n $data?: PlaceOrderData;\n /** Compiler-injected action surface; carries `payment.startTransaction`, `payment.gatewayCallback`, and `analytics.track`. */\n $actions?: ComponentActions;\n /**\n * T29's order-attempt record and its writer — `usePayment().attempt` / `.setAttempt`, forwarded by\n * the caller exactly like `billingAddressDifferent` below. **Optional by necessity, not by safety**\n * (mirrors `submitGate`'s own doc note): a caller that omits either gets today's behaviour, silently\n * — `placeOrder()` always takes the full `run()` path, with no resume and no phase written anywhere.\n * `PlaceOrder` always sources both from `usePayment()`.\n */\n attempt?: PlaceOrderAttempt | null;\n /** See `attempt` above. Identity must be stable across renders (NFR-3) — `usePayment().setAttempt` already is. */\n setAttempt?: SetPlaceOrderAttempt | null;\n /**\n * `usePayment().draft.billingAddressDifferent` (T11's widening of `PaymentDraft`) — an explicit\n * argument, not read from `usePayment()`'s context in this hook, so the hook keeps its existing\n * shape of taking every fact it needs as a parameter rather than adding a second hard context\n * dependency alongside the submission channel below. `null`/omitted folds to the same \"not set\"\n * descriptor shape `gateway.ts`'s own `transaction.billingAddressDifferent ?? false` already expects\n * (fix pass, PR #544 review F1). The caller is responsible for sourcing this from `usePayment()` and\n * forwarding it here.\n */\n billingAddressDifferent?: boolean | null;\n /**\n * A client-side refusal the caller already resolved, or `null` to proceed. Deliberately an opaque\n * `PaymentUserError` rather than the facts behind it: this module may name no identifier matching\n * `CARD_FIELD_PATTERN`, which includes `document`. The caller owns resolving it —\n * `matter/payment/usePaymentSubmitGate()` is the one in this package.\n *\n * **Optional by necessity, not by safety.** This hook is exported public API; `PlaceOrder` is the\n * only in-repo caller, and it always sources this from `usePaymentSubmitGate()`, but nothing stops a\n * storefront from composing its own place-order button directly on this hook. Omitting this argument\n * is silent and behaves exactly as if this whole pre-flight branch did not exist — no cardholder or\n * billing-address refusal ever reaches a CTA built that way, even though the same client-side gate\n * would have caught it. There is no compile-time signal for that omission (making the type mandatory\n * would be a breaking change to a hook other code may already call, and a caller can still opt out\n * explicitly with `null` regardless of the type), so a store composing its own CTA on this hook must\n * source this argument from `usePaymentSubmitGate()` deliberately, not by copying an example that\n * happened to include it.\n */\n submitGate?: PaymentUserError | null;\n /**\n * The absolute return URL for an order group, or `null` when this store has no return page. The gateway\n * sends the shopper to it after a bank challenge. Omitted, no `callbackUrl` is sent and a challenge answer\n * keeps the locked refusal. `PlaceOrder` builds it from its `challengeReturnHref` prop.\n */\n challengeReturn?: ((orderGroup: string) => string | null) | null;\n /**\n * Reads value and currency of a settled order, for the resume paths' `purchase` event (the attempt record\n * holds neither). Omitted, a resume reports no purchase and nothing throws. `PlaceOrder` passes\n * `useSettledOrderRead()`, the way it passes `submitGate`.\n */\n readSettledOrder?: SettledOrderRead | null;\n};\n\nexport function usePlaceOrder({\n $data,\n $actions,\n attempt,\n setAttempt,\n billingAddressDifferent,\n submitGate,\n challengeReturn,\n readSettledOrder,\n}: UsePlaceOrderArgs): UsePlaceOrder {\n const { path, resolve, resolvePreflight, resolveMissingMethodExplainer } = usePaymentSubmission();\n\n const [placing, setPlacing] = useState(false);\n const [userErrors, setUserErrors] = useState<readonly PaymentUserError[]>([]);\n const [orderId, setOrderId] = useState<string | null>(null);\n const [orderNodeId, setOrderNodeId] = useState<string | null>(null);\n const [reachedGateway, setReachedGateway] = useState<boolean | null>(null);\n const [redirectUrl, setRedirectUrl] = useState<string | null>(null);\n const inFlight = useRef<Promise<PlaceOrderResult> | null>(null);\n\n const startTransaction = $actions?.payment?.startTransaction;\n const gatewayCallback = $actions?.payment?.gatewayCallback;\n const track = $actions?.analytics?.track;\n\n const selectedMethodId = $data?.selectedMethodId ?? null;\n const methods = $data?.methods;\n const selectedInstallment = $data?.selectedInstallment ?? null;\n\n const selectedMethod = useMemo(\n () => methods?.find((method) => method.id === selectedMethodId) ?? null,\n [methods, selectedMethodId],\n );\n\n const run = useCallback(async (): Promise<PlaceOrderResult> => {\n if (!startTransaction || !gatewayCallback) {\n console.error('[payment.placeOrder] the payment action namespace is not injected');\n return { orderId: null, userErrors: [NETWORK_ERROR], reachedGateway: false };\n }\n\n // Resolved before anything is opened (epic, T13): a transaction nothing can complete is dangling\n // state on the order form. See `checkSubmissionPreconditions`.\n const verdict = checkSubmissionPreconditions({\n channel: { path, resolve, resolvePreflight, resolveMissingMethodExplainer },\n selectedMethod,\n submitGate,\n });\n if (verdict.kind === 'refused') return verdict.result;\n const { submit, method } = verdict;\n\n // T170 (FR-2/FR-3): the three facts this function's own `catch` needs and cannot read from inside\n // its `try`. `handedOver` records whether the card had already been handed to the registered\n // submission when a throw happened — the record's own phase cannot answer that, since it reads\n // `opening` on both sides of the order-group write below. `settledOrderGroup` carries the one\n // field an `unresolved` record must keep for `resume()` to be able to act on it at all, since the\n // settled payload itself is scoped inside the `try`.\n //\n // `placedAttempt` is the terminal record the success tail below is about to write, captured\n // *before* that write (fix round, Critical finding). `handedOver` cannot stand in for it: it is\n // equally true for the whole span from the card POST onwards, including the window *after*\n // `gatewayCallback` has already resolved a real order — and in that window neither `refused` nor\n // `unresolved` is the truth, because the order is placed. The sequence is the only thing that\n // knows it got there, so it carries the fact rather than letting the `catch` infer a phase the\n // record cannot distinguish.\n //\n // T176 (FR-5, Decision 4): `outcome` is the result each branch past the card hand-over fixes\n // *before* its own record write, so the `catch` can return it. From that point a throw — by a\n // caller-supplied writer or by the tracker — may still need the record recovered, but it no longer\n // changes what the caller reads: a confirmed order stays confirmed, and a refusal stays that refusal.\n // It is still null for a throw raised before any branch fixed a result, which keeps T170's\n // NETWORK_ERROR.\n //\n // Locals, not fields on `PlaceOrderAttempt` (NFR-1): only this closure ever reads them, and\n // putting any of them on the exported record would re-open the question T167 closed — readers\n // second-guessing the phase (INV-4).\n let handedOver = false;\n let settledOrderGroup: string | null = null;\n let placedAttempt: PlaceOrderAttempt | null = null;\n let outcome: PlaceOrderResult | null = null;\n\n // T29 (FR-1/FR-2): a fresh attempt record, minted only once every synchronous refusal guard above\n // has passed — none of those guards ever creates one, so a pre-card refusal (Scenario 6) leaves\n // `attempt` untouched and a fresh full run stays legal next time. Not `useId()` (T28's own\n // `instanceKey`): that resets on every remount, and this identity must not. A plain write, not\n // `writeAttemptIfCurrent`: this is the genesis of the attempt, so there is nothing yet for a\n // concurrent completion to have superseded.\n const attemptId = generateAttemptId();\n setAttempt?.({ attemptId, phase: 'opening', orderGroup: null, orderId: null });\n try {\n // FR-1 (T56): raised here — after every synchronous refusal guard, immediately before the first\n // `await` — so a refusal that returns before this line never commits a `placing: true` frame.\n // `setUserErrors([])` and `setOrderId(null)` remain in `placeOrder` (the click handler), and\n // `settle`'s `setPlacing(false)` is untouched.\n setPlacing(true);\n\n const opened = await openTransaction(startTransaction, setAttempt, attemptId);\n if (opened.kind === 'refused') return opened.result;\n const { settled } = opened;\n\n // T167: orderGroup is known and validated as soon as settleTransaction() returns non-null — well\n // before invokeSubmission() (the card POST, below) resolves. Writing it now closes that gap for the\n // whole 'opening' window instead of leaving it null until a terminal write. Data-completeness only:\n // placeOrder()'s phase branch and resumable (place-order.tsx) still derive only from `phase`, which\n // stays 'opening' here, so neither becomes actionable while this record sits in this phase.\n //\n // T170: kept for the recovery path too, which cannot reach `settled` from the `catch` below.\n settledOrderGroup = settled.orderGroup;\n writeAttemptIfCurrent(setAttempt, attemptId, {\n attemptId,\n phase: 'opening',\n orderGroup: settled.orderGroup,\n orderId: null,\n });\n\n // T208: the return URL the gateway sends the shopper back to after a bank challenge, built here because\n // only now is the order group known. `returnUrlSent` is what lets a challenge answer be followed.\n const callbackUrl = returnUrlFor(challengeReturn, settled.orderGroup);\n const transaction = buildTransaction({\n settled,\n method,\n selectedInstallment,\n billingAddressDifferent,\n callbackUrl,\n });\n\n // T170 (FR-3): raised on this side of the await, never the other. From the next line on, the\n // card has left the browser and nothing may recover this attempt as `refused` — which is the\n // only fact the `catch` below cannot re-derive, since the record reads `opening` either way.\n handedOver = true;\n // Still named for the errors it carries — the card-data boundary test pins this statement's text —\n // but it is the whole `PaymentSubmissionResult`, `reachedGateway` included.\n const submissionErrors = await invokeSubmission(submit, transaction);\n // An unpaid transaction is never finalized.\n if (submissionErrors.userErrors.length > 0) {\n // T112 (INV-1): only a result whose `reachedGateway` is literally `false` frees the attempt. The\n // holder refused before issuing any request, so nothing was paid and a fresh full sequence is\n // legal — the same `refused` record the two refusals above write. `handedOver` is cleared first,\n // so a writer that throws on this very write lands `refused` in the `catch` below, not\n // `unresolved`. `invokeSubmission` has already turned every non-boolean into `true`.\n //\n // T29 (FR-2, Decision 5): otherwise a request was issued, or this hook cannot show it was not — so\n // the card's fate is unknown past this point. `gateway.ts` resolves one opaque userError whether the\n // gateway declined the card, the network failed, or the POST was aborted (FR-6) — this hook cannot\n // and must not try to tell those apart, so every one of them is `unresolved`, never `refused`.\n // Sticky for the payment root's lifetime (FR-4), until a `gatewayCallback` answers with an error the\n // retry rule frees (T110): the only call `resume()` ever issues again is `gatewayCallback(orderGroup)`.\n //\n // `handedOver` is `true` here (set on the statement before the await), so `!freed` clears it exactly\n // when the attempt is freed and leaves it set otherwise.\n const freed = submissionErrors.reachedGateway === false;\n handedOver = !freed;\n outcome = { orderId: null, userErrors: submissionErrors.userErrors, reachedGateway: !freed };\n writeAttemptIfCurrent(setAttempt, attemptId, recoveryRecord(attemptId, null, handedOver, settled.orderGroup));\n return outcome;\n }\n\n // T39: a holder whose order is placed by Start Transaction alone sent nothing to the gateway, so there is\n // no callback to finalize it with. The order is placed, and its id is the order group, the value the\n // callback itself resolves as `orderId` (`paymentGatewayCallback` echoes `input.orderGroup`). Every other\n // holder keeps the callback leg below, unchanged.\n const completesWithoutGateway = submissionErrors.completesWithoutGateway === true;\n let placedOrderId: string | null = settled.orderGroup;\n let placedOrderNodeId: string | null = settled.orderNodeId;\n\n if (!completesWithoutGateway) {\n // T29: the card reached the gateway clean. Past this line the card may be charged — `resume()`\n // (below) is the only legal path back in (Decision 3). Fix pass (High finding): written as\n // `finalizing`, not `sent` — `sent` and `finalizing` are two distinct edges in the spec's own state\n // diagram (`sent --> finalizing: gatewayCallback issued`), but this call issues `gatewayCallback`\n // with no `await` in between, so nothing can ever legally observe the record at rest in `sent`\n // here: React batches same-tick writes, so whichever value is written *last*, before the next\n // `await`, is the only one any render (or a remount reading mid-flight) ever sees. The previous\n // version of this comment argued that fact meant the choice between the two values did not matter;\n // it does — `resumable` (place-order.tsx) treats `sent` as safe to resume, so writing it here left\n // `resumable` true for the entire span of this in-flight `gatewayCallback`, and a remounted or\n // sibling CTA could issue a second, concurrent one on the same order group (this file's own \"is\n // between the card POST and gatewayCallback\" test used to pin exactly that window). `finalizing` is\n // the value that keeps `resumable` false until this call actually settles — exactly what `resume()`\n // (below) already writes for its own, separately-triggered invocation of this same call.\n writeAttemptIfCurrent(setAttempt, attemptId, {\n attemptId,\n phase: 'finalizing',\n orderGroup: settled.orderGroup,\n orderId: null,\n });\n\n const callback = await invokeGatewayCallback(gatewayCallback, settled.orderGroup, attemptId);\n if (callback.userErrors.length > 0) {\n // T208: a challenge answer that can be followed ends in a redirect. The result still carries\n // `REDIRECT_REQUIRED`, and the code is locked below, so the record stays `unresolved`.\n outcome = {\n orderId: null,\n userErrors: reconcileCallbackUserErrors(callback.userErrors),\n reachedGateway: true,\n ...redirectField(readChallengeRedirect(callback, callbackUrl !== null)),\n };\n // T110: a callback the rule frees (see `callbackFreesAttempt`) records `refused`, so the next click is\n // a fresh full run on a new order group. `handedOver` is cleared first, so a writer that throws on\n // this very write lands `refused` in the `catch` below, not `unresolved`. Any other callback error\n // (no answer, a bank challenge, a lapsed transaction cookie, a result with an `orderId`) stays\n // resume-only: that payment may have gone through.\n const freed = callbackFreesAttempt(callback);\n handedOver = !freed;\n writeAttemptIfCurrent(setAttempt, attemptId, recoveryRecord(attemptId, null, handedOver, settled.orderGroup));\n return outcome;\n }\n placedOrderId = callback.orderId;\n // A callback written before `orderNodeId` existed resolves none; the start leg's id names the same order.\n placedOrderNodeId = callback.orderNodeId ?? settled.orderNodeId;\n }\n\n // Built into a local before it is written, not inline (fix round, Critical finding): the write\n // below is itself a throw site — `setAttempt` is caller-supplied — and from the line above onwards\n // the order is placed. Handing the same value to the `catch` lets it re-assert the terminal truth\n // instead of downgrading a paid order to a resumable one.\n placedAttempt = {\n attemptId,\n phase: 'settled',\n orderGroup: settled.orderGroup,\n orderId: placedOrderId,\n orderNodeId: placedOrderNodeId,\n };\n outcome = { orderId: placedOrderId, orderNodeId: placedOrderNodeId, userErrors: [], reachedGateway: true };\n\n // GA4's transaction_id is the order a merchant reconciles against. A callback can succeed without\n // resolving one, and that is still a placed order, so the payment transaction id is the fallback.\n // `settled.value.amount` is typed `number` but arrives over the wire as a decimal string (the same\n // Decimal-scalar mismatch `toMinorUnits` coerces above), so GA4's numeric `value` needs the same\n // `Number()` coercion the rest of this package applies at the point of numeric use (e.g.\n // `sdk/cart/optimistic.ts`'s `moneyAdd`).\n //\n // T176 (Decision 5): dispatched before the terminal write, not after it. The event reports VTEX's\n // confirmation, which has already happened; the record is local bookkeeping about that fact, so a\n // writer that throws on it no longer loses the event. A tracker that throws lands in the `catch`\n // before that write, and the recovery write lands `placedAttempt` instead — never a second call.\n //\n // T208: an order is reported once, whichever path finished it: the marker for this group is claimed\n // before the event goes out, and a resume that finished the same order finds it set.\n if (track && claimPurchase(settled.orderGroup)) {\n track({\n name: 'purchase',\n transactionId: placedOrderId ?? settled.transactionId,\n value: { amount: Number(settled.value.amount), currencyCode: settled.value.currencyCode },\n });\n }\n\n writeAttemptIfCurrent(setAttempt, attemptId, placedAttempt);\n\n return outcome;\n } catch (error) {\n // T170 (FR-1): before this, `run()` had no `try`/`catch` of its own, so a throw anywhere past\n // the genesis write above — the synchronous descriptor build, or the caller-supplied `setAttempt`\n // that `writeAttemptIfCurrent` invokes — escaped every one of the outcome branches inside it.\n // `settleSequence`'s rejection handler (`placeOrder`, below) restores this hook's *local* state\n // only and never touches the shared record, so the record stayed latched at `opening` with no\n // sequence alive to move it: every later click hit `placeOrder()`'s own in-flight branch and\n // issued nothing, for the payment root's lifetime.\n //\n // The phase this settles on is not a judgement call, but it is a three-way one, and the first\n // version of this catch read it from `handedOver` alone (fix round, Critical finding). A throw\n // before the card was handed over provably never started the card POST, so nothing was paid and\n // `refused` is correct — a fresh full attempt stays legal (T29 Decision 4). A throw after it\n // with no order resolved means the card's fate is unknown, which is what `unresolved` means\n // (Decision 5) and what `resume()` exists for. But the `settled` write above is a throw site\n // too, and at that one site `gatewayCallback` has already returned a real order while `settled`\n // has not yet landed — so `writeAttemptIfCurrent`'s own `settled` guard does not refuse the\n // write either, and `unresolved` there made `resumable` (`place-order.tsx`) true for an order\n // that was already paid: one click on the re-enabled CTA routed through `resume()` into a second\n // `gatewayCallback` on the same order group, undeduplicated at every layer (no ownership check,\n // no idempotency key in `PaymentGatewayCallbackInput`, and VTEX's behaviour on a repeat carried\n // as unmeasured residual risk under PEXP-2049), doubling pressure on the account-wide\n // `vtex.checkout` breaker as well. Hence `placedAttempt` first: re-asserting the terminal record\n // the failed write was carrying is the only phase that is true there, and it can never be a\n // downgrade, because `writeAttemptIfCurrent` refuses it outright once `settled` has landed\n // (INV-6). Since T176 the `track?.()` call sits before that write, so for a tracker that throws\n // this recovery write is the one that lands `settled`.\n //\n // `writeAttemptIfCurrent`, never a plain write (INV-1): this closure may be running after its\n // own CTA unmounted (NFR-3), so a newer completion for this same attempt must win instead. That\n // write is itself wrapped, because it goes through the same caller-supplied `setAttempt` that may\n // be what threw in the first place (fix round, High finding): `writeAttemptIfCurrent` passes the\n // setter a *function* updater, so a writer that rejects that form — one that serializes,\n // schema-validates or persists what it is given — lands the plain genesis write and throws on\n // every write after it, this one included. Unguarded, that second throw escaped `run()` and left\n // this hook's contract (FR-6: resolve, never reject) resting on `settleSequence` catching it a\n // frame away. Nothing further can be tried once the writer itself is broken — a plain\n // `setAttempt` fallback is exactly the stale-closure clobber INV-1 forbids — so it is logged and\n // the result is returned either way: the `outcome` a branch already fixed (T176, FR-5), or\n // NETWORK_ERROR when the throw came before any branch fixed one. Only a sanitised error *name* is\n // logged, on both lines (FR-7): the closure that threw may hold the card, and a message, a stack,\n // or the raw writable `.name` could echo it. See `loggableErrorName()`.\n console.error('[payment.placeOrder] the place-order sequence threw', attemptId, loggableErrorName(error));\n writeRecoveryRecord(\n setAttempt,\n attemptId,\n recoveryRecord(attemptId, placedAttempt, handedOver, settledOrderGroup),\n );\n // `handedOver` is the one fact that says whether the card may have left: a throw before the hand-over\n // (or after a pre-network refusal cleared it) cannot have reached the gateway, a throw after it cannot\n // be shown not to have.\n return outcome ?? { orderId: null, userErrors: [NETWORK_ERROR], reachedGateway: handedOver };\n }\n }, [\n startTransaction,\n gatewayCallback,\n track,\n path,\n resolve,\n resolvePreflight,\n resolveMissingMethodExplainer,\n selectedMethod,\n selectedInstallment,\n billingAddressDifferent,\n submitGate,\n setAttempt,\n challengeReturn,\n ]);\n\n const placeOrder = useCallback<UsePlaceOrder['placeOrder']>(() => {\n // A second click lands before T14's disabled state commits a render, and duplicating a\n // transaction is the worst outcome this surface has — so the guard is the in-flight promise\n // itself, not the `placing` flag. Both callers observe the one attempt that ran.\n if (inFlight.current !== null) return inFlight.current;\n\n // FR-1 (T56): `setPlacing(true)` moved into `run()`/`resume()`, immediately before their own first\n // `await`, so a synchronous refusal never commits a `placing: true` frame. `run()`'s and `resume()`'s\n // outcomes share this one settle wrapper (T29).\n const settleSequence = (sequence: Promise<PlaceOrderResult>): Promise<PlaceOrderResult> => {\n setUserErrors([]);\n setOrderId(null);\n setOrderNodeId(null);\n setReachedGateway(null);\n setRedirectUrl(null);\n\n const settle = (result: PlaceOrderResult): PlaceOrderResult => {\n // T208: a result that hands the shopper to the bank is not a settled one. `placing` stays true and the\n // sequence stays in flight, so the form stays blocked and a second click issues nothing while the\n // browser leaves; the hook's own `userErrors` is empty so no alert is drawn for what is not a failure.\n const redirecting = typeof result.redirectUrl === 'string';\n if (!redirecting) {\n inFlight.current = null;\n setPlacing(false);\n }\n setUserErrors(redirecting ? [] : result.userErrors);\n setOrderId(result.orderId);\n setOrderNodeId(result.orderNodeId ?? null);\n setReachedGateway(result.reachedGateway);\n setRedirectUrl(redirecting ? (result.redirectUrl ?? null) : null);\n return result;\n };\n\n // `run()`/`resume()` resolve for every path this hook drives itself, but a collaborator outside\n // its control (a different $actions provider, or a mock resolving `undefined`) can still reject\n // one. Settling on both paths keeps `placing` from wedging true forever and surfaces an error, so\n // the CTA is clickable again either way. Spec correction (review F2): whether the *next* click\n // actually retries anything is decided by the attempt's phase, not by this settle —\n // `invokeStartTransaction`/`invokeGatewayCallback` each write a resumable phase before their own\n // await and normalize their result, so a throw on either leg lands on `refused`/`unresolved`\n // (T156). T170 closes the rest of that class at the source: `run()` now catches its own throws\n // and settles the record itself, so what is left is a collaborator-owned promise that never\n // settles at all — nothing has thrown, so there is nothing for either that catch or this settle\n // to act on, and bounding it needs a deadline nobody has set yet (T170's own OQ-1).\n const outcome = sequence.then(settle, () =>\n settle({ orderId: null, userErrors: [NETWORK_ERROR], reachedGateway: true }),\n );\n\n inFlight.current = outcome;\n return outcome;\n };\n\n // T29 (FR-2, FR-3): the CTA's next legal action is derived from the surviving attempt's own phase,\n // not always `run()`. `sent`/`unresolved` resume through a module-level function with no access at\n // all to `startTransaction` or the submission channel (Decision 3); `opening`/`finalizing` are\n // already running — possibly from a mount this closure has outlived (NFR-3) — and `settled` is\n // terminal (INV-6), so none of the three issues anything, independent of whatever a composing\n // CTA's own disabled state happens to do. Only the terminal one reads as a success, though: see\n // the in-flight arm's own comment below. `refused` falls through to a full run below like `null` —\n // nothing was paid, or the callback answered with an error the retry rule frees (T110), so a fresh\n // attempt is legal (Decision 4). The `'sent'` check just below is defensive-only: `run()` never leaves\n // the record resting there today (it writes `finalizing` directly, same tick — see that write site's\n // own comment); kept so a future write path resting at `sent` would still resume correctly instead of\n // silently falling through to a full run.\n if (attempt) {\n if (attempt.phase === 'settled') {\n // FR-4 (T110): through `settleSequence` like every other arm, so this hook's own state reads the\n // settled outcome.\n return settleSequence(\n Promise.resolve({\n orderId: attempt.orderId,\n orderNodeId: attempt.orderNodeId ?? null,\n userErrors: [],\n reachedGateway: true,\n }),\n );\n }\n // T170 (FR-5, Decision 4): split off the two in-flight phases, which used to share this arm and\n // so resolved the empty `userErrors` this hook's own contract defines as success\n // (`PlaceOrderResult`) — a reader that did not start the sequence was handed a success shape for\n // an order still in flight, and a CTA derived from it (`place-order.tsx`'s `succeeded`) rendered\n // \"placed\" for an order nobody had paid for. Settled through `settleSequence` like every other\n // outcome, so the refusal reaches this hook's own `userErrors` rather than only the resolved\n // value a composing CTA may never read. Still issues nothing, and still reads the phase alone\n // (INV-4): waiting is the correct behaviour while the card POST is genuinely open.\n if (attempt.phase === 'opening' || attempt.phase === 'finalizing') {\n return settleSequence(\n Promise.resolve({\n orderId: attempt.orderId,\n userErrors: [PLACE_ORDER_IN_FLIGHT_ERROR],\n reachedGateway: true,\n }),\n );\n }\n if (attempt.phase === 'sent' || attempt.phase === 'unresolved') {\n return settleSequence(\n resume(gatewayCallback, setAttempt, setPlacing, attempt, {\n returnUrlSent: attempt.orderGroup !== null && returnUrlFor(challengeReturn, attempt.orderGroup) !== null,\n track,\n readSettledOrder,\n }),\n );\n }\n }\n\n return settleSequence(run());\n }, [run, attempt, gatewayCallback, setAttempt, challengeReturn, track, readSettledOrder]);\n\n return useMemo(\n () => ({ placeOrder, placing, userErrors, orderId, orderNodeId, reachedGateway, redirectUrl }),\n [placeOrder, placing, userErrors, orderId, orderNodeId, reachedGateway, redirectUrl],\n );\n}\n"],"mappings":"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"}
@@ -1,17 +1,11 @@
1
1
  import { StorefrontContext } from "../../core/dist/schema/generated/schema.js";
2
2
  import "../../core/dist/index.js";
3
- import { Messages } from "../i18n/locales.js";
4
3
  import { ReactNode } from "react";
5
4
  //#region src/sdk/provider/storefront-provider.d.ts
6
5
  export type StorefrontProviderProps = Readonly<{
7
6
  apiUrl: string;
8
7
  /** The compiler-emitted context. Published to the tree and sent on every operation. */
9
8
  context: StorefrontContext;
10
- /**
11
- * The record the generated root layout resolved for this binding. Omitted means no messages —
12
- * legal, and only observable once a component renders one.
13
- */
14
- messages?: Messages;
15
9
  headers?: Record<string, string>;
16
10
  children: ReactNode;
17
11
  }>;
@@ -27,6 +21,6 @@ export type StorefrontProviderProps = Readonly<{
27
21
  * therefore frozen for the document's lifetime, which is why crossing bindings has to be a full
28
22
  * document load and why the guard below exists.
29
23
  */
30
- export declare function StorefrontProvider({ apiUrl, context, messages, headers, children }: StorefrontProviderProps): import("react/jsx-runtime").JSX.Element;
24
+ export declare function StorefrontProvider({ apiUrl, context, headers, children }: StorefrontProviderProps): import("react/jsx-runtime").JSX.Element;
31
25
  //#endregion
32
26
  //# sourceMappingURL=storefront-provider.d.ts.map
@@ -1,6 +1,5 @@
1
1
  "use client";
2
2
  import { StorefrontContextProvider } from "../context/storefront-context.js";
3
- import { MessageProvider } from "../i18n/provider.js";
4
3
  import { buildStorefrontContextHeaders } from "../context/headers.js";
5
4
  import { getCacheConfig } from "../cache/storefront-cache.js";
6
5
  import { jsx } from "react/jsx-runtime";
@@ -8,8 +7,6 @@ import { ApolloLink, HttpLink } from "@apollo/client";
8
7
  import { SetContextLink } from "@apollo/client/link/context";
9
8
  import { ApolloClient as ApolloClient$1, ApolloNextAppProvider, InMemoryCache as InMemoryCache$1 } from "@apollo/client-integration-nextjs";
10
9
  //#region src/sdk/provider/storefront-provider.tsx
11
- /** One frozen instance, so an omitted `messages` does not hand `IntlProvider` a new object per render. */
12
- const NO_MESSAGES = Object.freeze({});
13
10
  /** Where `WrapApolloProvider` memoizes the browser's one client, per document. */
14
11
  const APOLLO_CLIENT_SINGLETON = Symbol.for("ApolloClientSingleton");
15
12
  /** The binding a client was built for, stamped by `makeClient` so a later render can compare. */
@@ -71,7 +68,7 @@ const cartContextLink = new SetContextLink((prevContext) => {
71
68
  * therefore frozen for the document's lifetime, which is why crossing bindings has to be a full
72
69
  * document load and why the guard below exists.
73
70
  */
74
- function StorefrontProvider({ apiUrl, context, messages, headers, children }) {
71
+ function StorefrontProvider({ apiUrl, context, headers, children }) {
75
72
  if (typeof apiUrl !== "string" || apiUrl.length === 0) throw new Error("StorefrontProvider: `apiUrl` is required. Set `NEXT_PUBLIC_STOREFRONT_API_URL` in your environment or pass `apiUrl` explicitly.");
76
73
  const requestHeaders = {
77
74
  ...headers,
@@ -95,13 +92,9 @@ function StorefrontProvider({ apiUrl, context, messages, headers, children }) {
95
92
  assertClientMatchesBinding(context.bindingId);
96
93
  return /* @__PURE__ */ jsx(StorefrontContextProvider, {
97
94
  context,
98
- children: /* @__PURE__ */ jsx(MessageProvider, {
99
- locale: context.locale,
100
- messages: messages ?? NO_MESSAGES,
101
- children: /* @__PURE__ */ jsx(ApolloNextAppProvider, {
102
- makeClient,
103
- children
104
- })
95
+ children: /* @__PURE__ */ jsx(ApolloNextAppProvider, {
96
+ makeClient,
97
+ children
105
98
  })
106
99
  });
107
100
  }
@@ -1 +1 @@
1
- {"version":3,"file":"storefront-provider.js","names":["ApolloClient","InMemoryCache"],"sources":["../../../src/sdk/provider/storefront-provider.tsx"],"sourcesContent":["'use client';\n\nimport { ApolloLink, HttpLink } from '@apollo/client';\nimport { SetContextLink } from '@apollo/client/link/context';\nimport { ApolloClient, ApolloNextAppProvider, InMemoryCache } from '@apollo/client-integration-nextjs';\nimport type { StorefrontContext } from '@vtex/fs-core';\nimport type { ReactNode } from 'react';\nimport { getCacheConfig } from '../cache/storefront-cache.js';\nimport { buildStorefrontContextHeaders } from '../context/headers.js';\nimport { StorefrontContextProvider } from '../context/storefront-context.js';\nimport type { Messages } from '../i18n/locales.js';\nimport { MessageProvider } from '../i18n/provider.js';\n\nexport type StorefrontProviderProps = Readonly<{\n apiUrl: string;\n /** The compiler-emitted context. Published to the tree and sent on every operation. */\n context: StorefrontContext;\n /**\n * The record the generated root layout resolved for this binding. Omitted means no messages —\n * legal, and only observable once a component renders one.\n */\n messages?: Messages;\n headers?: Record<string, string>;\n children: ReactNode;\n}>;\n\n/** One frozen instance, so an omitted `messages` does not hand `IntlProvider` a new object per render. */\nconst NO_MESSAGES: Messages = Object.freeze({});\n\n/** Where `WrapApolloProvider` memoizes the browser's one client, per document. */\nexport const APOLLO_CLIENT_SINGLETON = Symbol.for('ApolloClientSingleton');\n\n/** The binding a client was built for, stamped by `makeClient` so a later render can compare. */\nconst CLIENT_BINDING = Symbol.for('faststore.apolloClientBinding');\n\n/**\n * Fails when the live client was built for a different binding than the one now rendering.\n *\n * This cannot be fixed by rebuilding: the client is memoized per document, so the cache *and* the\n * baked `x-faststore-*` headers belong to whichever binding loaded the document first. A client-side\n * navigation across bindings would therefore render a correct-looking page filled with the previous\n * binding's data — silent, and worse than a stale cache. Crossing bindings is a full document load;\n * this is the tripwire for anything that tries otherwise.\n *\n * Throws in development so it cannot be missed, warns in production so a shopper is not shown an\n * error page over it. A no-op on the server and before the first client is built.\n */\nfunction assertClientMatchesBinding(bindingId: string | null): void {\n const win: Window | undefined = globalThis.window;\n if (!win) return;\n const live = Reflect.get(win, APOLLO_CLIENT_SINGLETON);\n if (!live) return;\n const built = (Reflect.get(live as object, CLIENT_BINDING) ?? null) as string | null;\n if (built === bindingId) return;\n\n const message =\n `StorefrontProvider: the live Apollo client was built for binding ${JSON.stringify(built)} but ` +\n `this render is binding ${JSON.stringify(bindingId)}. The client is memoized per document, so ` +\n 'its cache and its `x-faststore-*` headers still belong to the previous binding. Navigate ' +\n 'between bindings with a full document load (a plain anchor, not `<Link>`).';\n if (process.env.NODE_ENV === 'production') {\n console.error(message);\n return;\n }\n throw new Error(message);\n}\n\n/**\n * Debug-only cart-context override. Injects `x-order-form-id` only when the page URL carries\n * `?debug_ofid=<id>`. In normal flow the header is absent: the server resolves the cart from the\n * HttpOnly cookie, which the browser attaches automatically on same-origin requests. No-op during\n * SSR (no `window`).\n */\nconst cartContextLink = new SetContextLink((prevContext) => {\n // Typed as optional: the DOM lib declares `window` non-nullable, but it is absent during SSR.\n const win: Window | undefined = globalThis.window;\n if (!win) return prevContext;\n const debugOfid = new URLSearchParams(win.location.search).get('debug_ofid');\n if (!debugOfid) return prevContext;\n return {\n ...prevContext,\n headers: { ...prevContext.headers, 'x-order-form-id': debugOfid },\n };\n});\n\n/**\n * Wraps a React tree in the Apollo context, using the Next.js App Router\n * integration so that `useQuery` / `useSuspenseQuery` results can be\n * streamed from the server SSR pass into the browser cache.\n *\n * On the server a fresh `ApolloClient` is built per request. In the browser it is built once per\n * **document**: `WrapApolloProvider` keeps it at `window[Symbol.for('ApolloClientSingleton')]` and\n * assigns only when that slot is empty, so `makeClient` never runs a second time no matter how the\n * provider re-renders. Everything the factory closes over — the cache and the context headers — is\n * therefore frozen for the document's lifetime, which is why crossing bindings has to be a full\n * document load and why the guard below exists.\n */\nexport function StorefrontProvider({ apiUrl, context, messages, headers, children }: StorefrontProviderProps) {\n if (typeof apiUrl !== 'string' || apiUrl.length === 0) {\n throw new Error(\n 'StorefrontProvider: `apiUrl` is required. Set ' +\n '`NEXT_PUBLIC_STOREFRONT_API_URL` in your environment or pass ' +\n '`apiUrl` explicitly.',\n );\n }\n\n // Context headers merge last, so a caller cannot send a locale, binding or sales channel that\n // disagrees with the context the app rendered. Requesting a different one is what `context` is for.\n const requestHeaders = { ...headers, ...buildStorefrontContextHeaders(context) };\n\n function makeClient() {\n const client = new ApolloClient({\n cache: new InMemoryCache(getCacheConfig()),\n link: ApolloLink.from([\n cartContextLink,\n new HttpLink({\n uri: apiUrl,\n ...(requestHeaders ? { headers: requestHeaders } : {}),\n }),\n ]),\n // `cart` is non-null (`Cart!`), so an upstream cart failure nulls the whole operation's\n // `data`. 'all' delivers `{ data, error }` instead of throwing, letting the cart subtree\n // degrade in place.\n defaultOptions: {\n watchQuery: { errorPolicy: 'all' },\n query: { errorPolicy: 'all' },\n },\n });\n // Stamp which binding this client's cache and headers belong to, so a later render can tell.\n Reflect.set(client, CLIENT_BINDING, context.bindingId);\n return client;\n }\n\n assertClientMatchesBinding(context.bindingId);\n\n return (\n <StorefrontContextProvider context={context}>\n <MessageProvider locale={context.locale} messages={messages ?? NO_MESSAGES}>\n <ApolloNextAppProvider makeClient={makeClient}>{children}</ApolloNextAppProvider>\n </MessageProvider>\n </StorefrontContextProvider>\n );\n}\n"],"mappings":";;;;;;;;;;;AA2BA,MAAM,cAAwB,OAAO,OAAO,CAAC,CAAC;;AAG9C,MAAa,0BAA0B,OAAO,IAAI,uBAAuB;;AAGzE,MAAM,iBAAiB,OAAO,IAAI,+BAA+B;;;;;;;;;;;;;AAcjE,SAAS,2BAA2B,WAAgC;CAClE,MAAM,MAA0B,WAAW;CAC3C,IAAI,CAAC,KAAK;CACV,MAAM,OAAO,QAAQ,IAAI,KAAK,uBAAuB;CACrD,IAAI,CAAC,MAAM;CACX,MAAM,QAAS,QAAQ,IAAI,MAAgB,cAAc,KAAK;CAC9D,IAAI,UAAU,WAAW;CAEzB,MAAM,UACJ,oEAAoE,KAAK,UAAU,KAAK,EAAE,8BAChE,KAAK,UAAU,SAAS,EAAE;CAGtD,IAAI,QAAQ,IAAI,aAAa,cAAc;EACzC,QAAQ,MAAM,OAAO;EACrB;CACF;CACA,MAAM,IAAI,MAAM,OAAO;AACzB;;;;;;;AAQA,MAAM,kBAAkB,IAAI,gBAAgB,gBAAgB;CAE1D,MAAM,MAA0B,WAAW;CAC3C,IAAI,CAAC,KAAK,OAAO;CACjB,MAAM,YAAY,IAAI,gBAAgB,IAAI,SAAS,MAAM,CAAC,CAAC,IAAI,YAAY;CAC3E,IAAI,CAAC,WAAW,OAAO;CACvB,OAAO;EACL,GAAG;EACH,SAAS;GAAE,GAAG,YAAY;GAAS,mBAAmB;EAAU;CAClE;AACF,CAAC;;;;;;;;;;;;;AAcD,SAAgB,mBAAmB,EAAE,QAAQ,SAAS,UAAU,SAAS,YAAqC;CAC5G,IAAI,OAAO,WAAW,YAAY,OAAO,WAAW,GAClD,MAAM,IAAI,MACR,iIAGF;CAKF,MAAM,iBAAiB;EAAE,GAAG;EAAS,GAAG,8BAA8B,OAAO;CAAE;CAE/E,SAAS,aAAa;EACpB,MAAM,SAAS,IAAIA,eAAa;GAC9B,OAAO,IAAIC,gBAAc,eAAe,CAAC;GACzC,MAAM,WAAW,KAAK,CACpB,iBACA,IAAI,SAAS;IACX,KAAK;IACL,GAAI,iBAAiB,EAAE,SAAS,eAAe,IAAI,CAAC;GACtD,CAAC,CACH,CAAC;GAID,gBAAgB;IACd,YAAY,EAAE,aAAa,MAAM;IACjC,OAAO,EAAE,aAAa,MAAM;GAC9B;EACF,CAAC;EAED,QAAQ,IAAI,QAAQ,gBAAgB,QAAQ,SAAS;EACrD,OAAO;CACT;CAEA,2BAA2B,QAAQ,SAAS;CAE5C,OACE,oBAAC,2BAAD;EAAoC;EAClC,UAAA,oBAAC,iBAAD;GAAiB,QAAQ,QAAQ;GAAQ,UAAU,YAAY;GAC7D,UAAA,oBAAC,uBAAD;IAAmC;IAAa;GAAgC,CAAA;EACjE,CAAA;CACQ,CAAA;AAE/B"}
1
+ {"version":3,"file":"storefront-provider.js","names":["ApolloClient","InMemoryCache"],"sources":["../../../src/sdk/provider/storefront-provider.tsx"],"sourcesContent":["'use client';\n\nimport { ApolloLink, HttpLink } from '@apollo/client';\nimport { SetContextLink } from '@apollo/client/link/context';\nimport { ApolloClient, ApolloNextAppProvider, InMemoryCache } from '@apollo/client-integration-nextjs';\nimport type { StorefrontContext } from '@vtex/fs-core';\nimport type { ReactNode } from 'react';\nimport { getCacheConfig } from '../cache/storefront-cache.js';\nimport { buildStorefrontContextHeaders } from '../context/headers.js';\nimport { StorefrontContextProvider } from '../context/storefront-context.js';\n\nexport type StorefrontProviderProps = Readonly<{\n apiUrl: string;\n /** The compiler-emitted context. Published to the tree and sent on every operation. */\n context: StorefrontContext;\n headers?: Record<string, string>;\n children: ReactNode;\n}>;\n\n/** Where `WrapApolloProvider` memoizes the browser's one client, per document. */\nexport const APOLLO_CLIENT_SINGLETON = Symbol.for('ApolloClientSingleton');\n\n/** The binding a client was built for, stamped by `makeClient` so a later render can compare. */\nconst CLIENT_BINDING = Symbol.for('faststore.apolloClientBinding');\n\n/**\n * Fails when the live client was built for a different binding than the one now rendering.\n *\n * This cannot be fixed by rebuilding: the client is memoized per document, so the cache *and* the\n * baked `x-faststore-*` headers belong to whichever binding loaded the document first. A client-side\n * navigation across bindings would therefore render a correct-looking page filled with the previous\n * binding's data — silent, and worse than a stale cache. Crossing bindings is a full document load;\n * this is the tripwire for anything that tries otherwise.\n *\n * Throws in development so it cannot be missed, warns in production so a shopper is not shown an\n * error page over it. A no-op on the server and before the first client is built.\n */\nfunction assertClientMatchesBinding(bindingId: string | null): void {\n const win: Window | undefined = globalThis.window;\n if (!win) return;\n const live = Reflect.get(win, APOLLO_CLIENT_SINGLETON);\n if (!live) return;\n const built = (Reflect.get(live as object, CLIENT_BINDING) ?? null) as string | null;\n if (built === bindingId) return;\n\n const message =\n `StorefrontProvider: the live Apollo client was built for binding ${JSON.stringify(built)} but ` +\n `this render is binding ${JSON.stringify(bindingId)}. The client is memoized per document, so ` +\n 'its cache and its `x-faststore-*` headers still belong to the previous binding. Navigate ' +\n 'between bindings with a full document load (a plain anchor, not `<Link>`).';\n if (process.env.NODE_ENV === 'production') {\n console.error(message);\n return;\n }\n throw new Error(message);\n}\n\n/**\n * Debug-only cart-context override. Injects `x-order-form-id` only when the page URL carries\n * `?debug_ofid=<id>`. In normal flow the header is absent: the server resolves the cart from the\n * HttpOnly cookie, which the browser attaches automatically on same-origin requests. No-op during\n * SSR (no `window`).\n */\nconst cartContextLink = new SetContextLink((prevContext) => {\n // Typed as optional: the DOM lib declares `window` non-nullable, but it is absent during SSR.\n const win: Window | undefined = globalThis.window;\n if (!win) return prevContext;\n const debugOfid = new URLSearchParams(win.location.search).get('debug_ofid');\n if (!debugOfid) return prevContext;\n return {\n ...prevContext,\n headers: { ...prevContext.headers, 'x-order-form-id': debugOfid },\n };\n});\n\n/**\n * Wraps a React tree in the Apollo context, using the Next.js App Router\n * integration so that `useQuery` / `useSuspenseQuery` results can be\n * streamed from the server SSR pass into the browser cache.\n *\n * On the server a fresh `ApolloClient` is built per request. In the browser it is built once per\n * **document**: `WrapApolloProvider` keeps it at `window[Symbol.for('ApolloClientSingleton')]` and\n * assigns only when that slot is empty, so `makeClient` never runs a second time no matter how the\n * provider re-renders. Everything the factory closes over — the cache and the context headers — is\n * therefore frozen for the document's lifetime, which is why crossing bindings has to be a full\n * document load and why the guard below exists.\n */\nexport function StorefrontProvider({ apiUrl, context, headers, children }: StorefrontProviderProps) {\n if (typeof apiUrl !== 'string' || apiUrl.length === 0) {\n throw new Error(\n 'StorefrontProvider: `apiUrl` is required. Set ' +\n '`NEXT_PUBLIC_STOREFRONT_API_URL` in your environment or pass ' +\n '`apiUrl` explicitly.',\n );\n }\n\n // Context headers merge last, so a caller cannot send a locale, binding or sales channel that\n // disagrees with the context the app rendered. Requesting a different one is what `context` is for.\n const requestHeaders = { ...headers, ...buildStorefrontContextHeaders(context) };\n\n function makeClient() {\n const client = new ApolloClient({\n cache: new InMemoryCache(getCacheConfig()),\n link: ApolloLink.from([\n cartContextLink,\n new HttpLink({\n uri: apiUrl,\n ...(requestHeaders ? { headers: requestHeaders } : {}),\n }),\n ]),\n // `cart` is non-null (`Cart!`), so an upstream cart failure nulls the whole operation's\n // `data`. 'all' delivers `{ data, error }` instead of throwing, letting the cart subtree\n // degrade in place.\n defaultOptions: {\n watchQuery: { errorPolicy: 'all' },\n query: { errorPolicy: 'all' },\n },\n });\n // Stamp which binding this client's cache and headers belong to, so a later render can tell.\n Reflect.set(client, CLIENT_BINDING, context.bindingId);\n return client;\n }\n\n assertClientMatchesBinding(context.bindingId);\n\n return (\n <StorefrontContextProvider context={context}>\n <ApolloNextAppProvider makeClient={makeClient}>{children}</ApolloNextAppProvider>\n </StorefrontContextProvider>\n );\n}\n"],"mappings":";;;;;;;;;;AAoBA,MAAa,0BAA0B,OAAO,IAAI,uBAAuB;;AAGzE,MAAM,iBAAiB,OAAO,IAAI,+BAA+B;;;;;;;;;;;;;AAcjE,SAAS,2BAA2B,WAAgC;CAClE,MAAM,MAA0B,WAAW;CAC3C,IAAI,CAAC,KAAK;CACV,MAAM,OAAO,QAAQ,IAAI,KAAK,uBAAuB;CACrD,IAAI,CAAC,MAAM;CACX,MAAM,QAAS,QAAQ,IAAI,MAAgB,cAAc,KAAK;CAC9D,IAAI,UAAU,WAAW;CAEzB,MAAM,UACJ,oEAAoE,KAAK,UAAU,KAAK,EAAE,8BAChE,KAAK,UAAU,SAAS,EAAE;CAGtD,IAAI,QAAQ,IAAI,aAAa,cAAc;EACzC,QAAQ,MAAM,OAAO;EACrB;CACF;CACA,MAAM,IAAI,MAAM,OAAO;AACzB;;;;;;;AAQA,MAAM,kBAAkB,IAAI,gBAAgB,gBAAgB;CAE1D,MAAM,MAA0B,WAAW;CAC3C,IAAI,CAAC,KAAK,OAAO;CACjB,MAAM,YAAY,IAAI,gBAAgB,IAAI,SAAS,MAAM,CAAC,CAAC,IAAI,YAAY;CAC3E,IAAI,CAAC,WAAW,OAAO;CACvB,OAAO;EACL,GAAG;EACH,SAAS;GAAE,GAAG,YAAY;GAAS,mBAAmB;EAAU;CAClE;AACF,CAAC;;;;;;;;;;;;;AAcD,SAAgB,mBAAmB,EAAE,QAAQ,SAAS,SAAS,YAAqC;CAClG,IAAI,OAAO,WAAW,YAAY,OAAO,WAAW,GAClD,MAAM,IAAI,MACR,iIAGF;CAKF,MAAM,iBAAiB;EAAE,GAAG;EAAS,GAAG,8BAA8B,OAAO;CAAE;CAE/E,SAAS,aAAa;EACpB,MAAM,SAAS,IAAIA,eAAa;GAC9B,OAAO,IAAIC,gBAAc,eAAe,CAAC;GACzC,MAAM,WAAW,KAAK,CACpB,iBACA,IAAI,SAAS;IACX,KAAK;IACL,GAAI,iBAAiB,EAAE,SAAS,eAAe,IAAI,CAAC;GACtD,CAAC,CACH,CAAC;GAID,gBAAgB;IACd,YAAY,EAAE,aAAa,MAAM;IACjC,OAAO,EAAE,aAAa,MAAM;GAC9B;EACF,CAAC;EAED,QAAQ,IAAI,QAAQ,gBAAgB,QAAQ,SAAS;EACrD,OAAO;CACT;CAEA,2BAA2B,QAAQ,SAAS;CAE5C,OACE,oBAAC,2BAAD;EAAoC;EAClC,UAAA,oBAAC,uBAAD;GAAmC;GAAa;EAAgC,CAAA;CACvD,CAAA;AAE/B"}
@@ -0,0 +1,16 @@
1
+ import { TypedDocumentNode } from "@apollo/client";
2
+ //#region src/sdk/saved-cards/operations.d.ts
3
+ /**
4
+ * Saved-card GraphQL operations: the account's writes against the shopper's saved cards.
5
+ *
6
+ * Authored with the serializable `gql` tag and excluded from the codegen `documents` glob (see
7
+ * `codegen.ts`), like the identity operations: the pluck would otherwise duplicate these
8
+ * hand-written types in `sdk/generated/`.
9
+ */
10
+ /** A single domain error returned by a saved-card write. */
11
+ export type SavedCardUserError = Readonly<{
12
+ code: string;
13
+ message: string;
14
+ }>;
15
+ //#endregion
16
+ //# sourceMappingURL=operations.d.ts.map
@@ -0,0 +1,41 @@
1
+ import { gql } from "../graphql-tag.js";
2
+ //#region src/sdk/saved-cards/operations.ts
3
+ /**
4
+ * Selects every field the read exposes: the payload replaces the cached list, so a partial
5
+ * selection would leave the remaining cards holding stale fields.
6
+ */
7
+ const CUSTOMER_SAVED_CARD_REMOVE = gql`
8
+ mutation CustomerSavedCardRemove($id: ID!) {
9
+ customerSavedCardRemove(id: $id) {
10
+ __typename
11
+ customerSavedCards {
12
+ __typename
13
+ id
14
+ cards {
15
+ __typename
16
+ id
17
+ network {
18
+ __typename
19
+ id
20
+ name
21
+ }
22
+ lastDigits
23
+ expiry {
24
+ __typename
25
+ month
26
+ year
27
+ }
28
+ status
29
+ }
30
+ }
31
+ userErrors {
32
+ code
33
+ message
34
+ }
35
+ }
36
+ }
37
+ `;
38
+ //#endregion
39
+ export { CUSTOMER_SAVED_CARD_REMOVE };
40
+
41
+ //# sourceMappingURL=operations.js.map