@vtex/faststore 0.0.7 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +102 -0
- package/dist/actions.json +194 -1
- package/dist/catalog.json +3449 -1612
- package/dist/cli/index.d.ts +512 -63
- package/dist/cli/index.js +2098 -686
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/server-runtime.js +44 -24
- package/dist/cli/server-runtime.js.map +1 -1
- package/dist/client.d.ts +5 -1
- package/dist/client.js +5 -1
- package/dist/components.d.ts +42 -10
- package/dist/components.js +47 -15
- package/dist/content-types.json +823 -0
- package/dist/core/dist/define-component.d.ts +12 -2
- package/dist/docs/account-access-gate.md +4 -4
- package/dist/docs/account-address-book.md +519 -0
- package/dist/docs/account-entry-point.md +5 -5
- package/dist/docs/account-menu.md +7 -7
- package/dist/docs/account-saved-cards.md +301 -0
- package/dist/docs/alert-dialog.md +2 -2
- package/dist/docs/billing-address.md +17 -9
- package/dist/docs/binding-switcher.md +154 -0
- package/dist/docs/card-entry.md +38 -7
- package/dist/docs/card-summary.md +175 -0
- package/dist/docs/cardholder-document.md +16 -14
- package/dist/docs/cart-destination.md +238 -0
- package/dist/docs/cart-item.md +22 -4
- package/dist/docs/cart-totalizer.md +1 -1
- package/dist/docs/cart.md +126 -19
- package/dist/docs/client-profile-fieldset.md +7 -5
- package/dist/docs/copy-text.md +84 -0
- package/dist/docs/document-field.md +8 -8
- package/dist/docs/footer-link-group.md +2 -2
- package/dist/docs/footer-social.md +1 -1
- package/dist/docs/free-shipping-progress.md +13 -9
- package/dist/docs/header-root.md +1 -1
- package/dist/docs/incentives.md +4 -4
- package/dist/docs/logo.md +6 -6
- package/dist/docs/my-account-profile.md +4 -4
- package/dist/docs/navigation-link-list.md +2 -2
- package/dist/docs/navigation-link.md +3 -3
- package/dist/docs/order-cancel.md +52 -0
- package/dist/docs/order-delivery-address.md +63 -0
- package/dist/docs/order-delivery-group.md +83 -0
- package/dist/docs/order-details-back.md +41 -0
- package/dist/docs/order-details-header.md +48 -0
- package/dist/docs/order-list-row.md +46 -0
- package/dist/docs/order-list-search.md +101 -0
- package/dist/docs/order-list-views.md +97 -0
- package/dist/docs/order-list.md +94 -0
- package/dist/docs/order-payment.md +94 -0
- package/dist/docs/order-placed-date.md +49 -0
- package/dist/docs/order-placed-payment-summary.md +13 -15
- package/dist/docs/order-placed.md +225 -0
- package/dist/docs/order-progress.md +88 -0
- package/dist/docs/order-status-badge.md +91 -0
- package/dist/docs/order-summary.md +53 -0
- package/dist/docs/payment-method-badges.md +16 -5
- package/dist/docs/payment-methods.md +1 -1
- package/dist/docs/place-order.md +13 -9
- package/dist/docs/product-information.md +2 -2
- package/dist/docs/product-shelf.md +7 -0
- package/dist/docs/saved-cards.md +2 -2
- package/dist/docs/select.md +22 -10
- package/dist/docs/session.md +1 -1
- package/dist/docs/shipping-address.md +1 -1
- package/dist/docs/sign-in-redirect.md +7 -7
- package/dist/docs/toast.md +35 -13
- package/dist/index.d.ts +7 -2
- package/dist/index.js +2 -1
- package/dist/internal/client.d.ts +9 -1
- package/dist/internal/client.js +10 -2
- package/dist/internal/server.d.ts +8 -2
- package/dist/internal/server.js +8 -2
- package/dist/matter/account-access-gate/account-access-gate.d.ts +2 -1
- package/dist/matter/account-access-gate/account-access-gate.js +19 -16
- package/dist/matter/account-access-gate/account-access-gate.js.map +1 -1
- package/dist/matter/account-address-book/account-address-book-delete.d.ts +60 -0
- package/dist/matter/account-address-book/account-address-book-delete.js +184 -0
- package/dist/matter/account-address-book/account-address-book-delete.js.map +1 -0
- package/dist/matter/account-address-book/account-address-book-deliverability.d.ts +19 -0
- package/dist/matter/account-address-book/account-address-book-deliverability.js +50 -0
- package/dist/matter/account-address-book/account-address-book-deliverability.js.map +1 -0
- package/dist/matter/account-address-book/account-address-book-form-parts.d.ts +42 -0
- package/dist/matter/account-address-book/account-address-book-form-parts.js +146 -0
- package/dist/matter/account-address-book/account-address-book-form-parts.js.map +1 -0
- package/dist/matter/account-address-book/account-address-book-profile-notice.d.ts +25 -0
- package/dist/matter/account-address-book/account-address-book-profile-notice.js +72 -0
- package/dist/matter/account-address-book/account-address-book-profile-notice.js.map +1 -0
- package/dist/matter/account-address-book/account-address-book.d.ts +120 -0
- package/dist/matter/account-address-book/account-address-book.js +469 -0
- package/dist/matter/account-address-book/account-address-book.js.map +1 -0
- package/dist/matter/account-address-book/account-address-country-switch.js +59 -0
- package/dist/matter/account-address-book/account-address-country-switch.js.map +1 -0
- package/dist/matter/account-address-book/account-address-country.d.ts +36 -0
- package/dist/matter/account-address-book/account-address-country.js +151 -0
- package/dist/matter/account-address-book/account-address-country.js.map +1 -0
- package/dist/matter/account-address-book/account-address-first-line.js +11 -0
- package/dist/matter/account-address-book/account-address-first-line.js.map +1 -0
- package/dist/matter/account-address-book/account-address-form-context.d.ts +55 -0
- package/dist/matter/account-address-book/account-address-form-context.js +14 -0
- package/dist/matter/account-address-book/account-address-form-context.js.map +1 -0
- package/dist/matter/account-address-book/account-address-form.d.ts +85 -0
- package/dist/matter/account-address-book/account-address-form.js +946 -0
- package/dist/matter/account-address-book/account-address-form.js.map +1 -0
- package/dist/matter/account-address-book/account-address-postal-autofill.js +189 -0
- package/dist/matter/account-address-book/account-address-postal-autofill.js.map +1 -0
- package/dist/matter/account-address-book/account-address-record.d.ts +26 -0
- package/dist/matter/account-address-book/account-address-record.js +138 -0
- package/dist/matter/account-address-book/account-address-record.js.map +1 -0
- package/dist/matter/account-entry-point/account-entry-point.js +11 -11
- package/dist/matter/account-entry-point/account-entry-point.js.map +1 -1
- package/dist/matter/account-menu/account-menu.d.ts +7 -3
- package/dist/matter/account-menu/account-menu.js +69 -28
- package/dist/matter/account-menu/account-menu.js.map +1 -1
- package/dist/matter/account-saved-cards/account-saved-cards.d.ts +383 -0
- package/dist/matter/account-saved-cards/account-saved-cards.js +956 -0
- package/dist/matter/account-saved-cards/account-saved-cards.js.map +1 -0
- package/dist/matter/billing-address/billing-address.d.ts +30 -17
- package/dist/matter/billing-address/billing-address.js +128 -33
- package/dist/matter/billing-address/billing-address.js.map +1 -1
- package/dist/matter/breadcrumb/breadcrumb.js +2 -2
- package/dist/matter/breadcrumb/breadcrumb.js.map +1 -1
- package/dist/matter/card-entry/card-capture.js +4 -3
- package/dist/matter/card-entry/card-capture.js.map +1 -1
- package/dist/matter/card-entry/card-entry.d.ts +5 -3
- package/dist/matter/card-entry/card-entry.js +77 -27
- package/dist/matter/card-entry/card-entry.js.map +1 -1
- package/dist/matter/cardholder-document/cardholder-document.d.ts +10 -9
- package/dist/matter/cardholder-document/cardholder-document.js +36 -16
- package/dist/matter/cardholder-document/cardholder-document.js.map +1 -1
- package/dist/matter/cart/cart-namespace.d.ts +5 -1
- package/dist/matter/cart/cart-namespace.js +5 -1
- package/dist/matter/cart/cart-namespace.js.map +1 -1
- package/dist/matter/cart/cart.d.ts +41 -6
- package/dist/matter/cart/cart.js +267 -54
- package/dist/matter/cart/cart.js.map +1 -1
- package/dist/matter/cart-destination/cart-destination.d.ts +222 -0
- package/dist/matter/cart-destination/cart-destination.js +613 -0
- package/dist/matter/cart-destination/cart-destination.js.map +1 -0
- package/dist/matter/cart-item/cart-item.d.ts +4 -2
- package/dist/matter/cart-item/cart-item.js +76 -17
- package/dist/matter/cart-item/cart-item.js.map +1 -1
- package/dist/matter/cart-totalizer/cart-totalizer.js +3 -2
- package/dist/matter/cart-totalizer/cart-totalizer.js.map +1 -1
- package/dist/matter/client-preferences/client-preferences-fieldset.js +1 -1
- package/dist/matter/client-profile-form/client-profile-fieldset.js +8 -5
- package/dist/matter/client-profile-form/client-profile-fieldset.js.map +1 -1
- package/dist/matter/free-shipping-progress/free-shipping-progress.d.ts +6 -3
- package/dist/matter/free-shipping-progress/free-shipping-progress.js +76 -33
- package/dist/matter/free-shipping-progress/free-shipping-progress.js.map +1 -1
- package/dist/matter/identity-edit/identity-edit.js +3 -3
- package/dist/matter/identity-edit/identity-edit.js.map +1 -1
- package/dist/matter/identity-summary/identity-summary.js +2 -2
- package/dist/matter/identity-summary/identity-summary.js.map +1 -1
- package/dist/matter/my-account-profile/my-account-profile.d.ts +3 -3
- package/dist/matter/my-account-profile/my-account-profile.js +4 -4
- package/dist/matter/my-account-profile/my-account-profile.js.map +1 -1
- package/dist/matter/order-cancel/order-cancel.d.ts +110 -0
- package/dist/matter/order-cancel/order-cancel.js +344 -0
- package/dist/matter/order-cancel/order-cancel.js.map +1 -0
- package/dist/matter/order-delivery-address/order-delivery-address.d.ts +73 -0
- package/dist/matter/order-delivery-address/order-delivery-address.js +172 -0
- package/dist/matter/order-delivery-address/order-delivery-address.js.map +1 -0
- package/dist/matter/order-delivery-group/order-delivery-group.d.ts +128 -0
- package/dist/matter/order-delivery-group/order-delivery-group.js +408 -0
- package/dist/matter/order-delivery-group/order-delivery-group.js.map +1 -0
- package/dist/matter/order-details-header/order-details-header.d.ts +104 -0
- package/dist/matter/order-details-header/order-details-header.js +190 -0
- package/dist/matter/order-details-header/order-details-header.js.map +1 -0
- package/dist/matter/order-list/customer-orders-filter.d.ts +12 -0
- package/dist/matter/order-list/customer-orders-filter.js +30 -0
- package/dist/matter/order-list/customer-orders-filter.js.map +1 -0
- package/dist/matter/order-list/order-list.d.ts +121 -0
- package/dist/matter/order-list/order-list.js +395 -0
- package/dist/matter/order-list/order-list.js.map +1 -0
- package/dist/matter/order-list-row/order-list-row.d.ts +44 -0
- package/dist/matter/order-list-row/order-list-row.js +127 -0
- package/dist/matter/order-list-row/order-list-row.js.map +1 -0
- package/dist/matter/order-list-search/order-list-search.d.ts +72 -0
- package/dist/matter/order-list-search/order-list-search.js +296 -0
- package/dist/matter/order-list-search/order-list-search.js.map +1 -0
- package/dist/matter/order-list-views/order-list-views.d.ts +65 -0
- package/dist/matter/order-list-views/order-list-views.js +154 -0
- package/dist/matter/order-list-views/order-list-views.js.map +1 -0
- package/dist/matter/order-payment/order-payment.d.ts +83 -0
- package/dist/matter/order-payment/order-payment.js +258 -0
- package/dist/matter/order-payment/order-payment.js.map +1 -0
- package/dist/matter/order-placed/order-placed-deliveries.d.ts +145 -0
- package/dist/matter/order-placed/order-placed-deliveries.js +380 -0
- package/dist/matter/order-placed/order-placed-deliveries.js.map +1 -0
- package/dist/matter/order-placed/order-placed.d.ts +122 -0
- package/dist/matter/order-placed/order-placed.js +452 -0
- package/dist/matter/order-placed/order-placed.js.map +1 -0
- package/dist/matter/order-placed-date/order-placed-date.d.ts +45 -0
- package/dist/matter/order-placed-date/order-placed-date.js +92 -0
- package/dist/matter/order-placed-date/order-placed-date.js.map +1 -0
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.d.ts +5 -8
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js +29 -81
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js.map +1 -1
- package/dist/matter/order-progress/order-progress.d.ts +85 -0
- package/dist/matter/order-progress/order-progress.js +203 -0
- package/dist/matter/order-progress/order-progress.js.map +1 -0
- package/dist/matter/order-summary/order-summary.d.ts +81 -0
- package/dist/matter/order-summary/order-summary.js +192 -0
- package/dist/matter/order-summary/order-summary.js.map +1 -0
- package/dist/matter/payment/cardholder-document-rule.d.ts +35 -0
- package/dist/matter/payment/cardholder-document-rule.js +35 -0
- package/dist/matter/payment/cardholder-document-rule.js.map +1 -0
- package/dist/matter/payment/payment-card-capture.js +38 -23
- package/dist/matter/payment/payment-card-capture.js.map +1 -1
- package/dist/matter/payment/payment-controller.js +11 -0
- package/dist/matter/payment/payment-controller.js.map +1 -1
- package/dist/matter/payment/payment-method-badges.js +4 -2
- package/dist/matter/payment/payment-method-badges.js.map +1 -1
- package/dist/matter/payment/payment.js +11 -0
- package/dist/matter/payment/payment.js.map +1 -1
- package/dist/matter/payment/use-payment-method-selection.d.ts +39 -0
- package/dist/matter/payment/use-payment-method-selection.js +30 -2
- package/dist/matter/payment/use-payment-method-selection.js.map +1 -1
- package/dist/matter/payment/use-payment-submit-gate.d.ts +16 -2
- package/dist/matter/payment/use-payment-submit-gate.js +42 -14
- package/dist/matter/payment/use-payment-submit-gate.js.map +1 -1
- package/dist/matter/place-order/place-order.d.ts +12 -7
- package/dist/matter/place-order/place-order.js +126 -103
- package/dist/matter/place-order/place-order.js.map +1 -1
- package/dist/matter/pricing/pricing.js +5 -5
- package/dist/matter/pricing/pricing.js.map +1 -1
- package/dist/matter/product-shelf/product-shelf.d.ts +7 -0
- package/dist/matter/product-shelf/product-shelf.js +115 -1
- package/dist/matter/product-shelf/product-shelf.js.map +1 -1
- package/dist/matter/region/region.js +1 -1
- package/dist/matter/saved-cards/saved-cards.js +26 -15
- package/dist/matter/saved-cards/saved-cards.js.map +1 -1
- package/dist/matter/session/session.js +2 -2
- package/dist/matter/session/session.js.map +1 -1
- package/dist/matter/shipping-address/country-descriptors.d.ts +1 -0
- package/dist/matter/shipping-address/country-descriptors.js +23 -1
- package/dist/matter/shipping-address/country-descriptors.js.map +1 -1
- package/dist/matter/shipping-address/shipping-address.js +62 -14
- package/dist/matter/shipping-address/shipping-address.js.map +1 -1
- package/dist/matter/shipping-simulator/shipping-simulator.d.ts +2 -2
- package/dist/matter/shipping-simulator/shipping-simulator.js +6 -2
- package/dist/matter/shipping-simulator/shipping-simulator.js.map +1 -1
- package/dist/matter/sign-in-redirect/sign-in-redirect.js +48 -10
- package/dist/matter/sign-in-redirect/sign-in-redirect.js.map +1 -1
- package/dist/matter/variant-selector/variant-selector.js +2 -2
- package/dist/matter/variant-selector/variant-selector.js.map +1 -1
- package/dist/particles/address-summary/address-summary.js +2 -2
- package/dist/particles/address-summary/address-summary.js.map +1 -1
- package/dist/particles/alert-dialog/alert-dialog.d.ts +5 -1
- package/dist/particles/alert-dialog/alert-dialog.js +5 -2
- package/dist/particles/alert-dialog/alert-dialog.js.map +1 -1
- package/dist/particles/binding-switcher/binding-switcher.d.ts +38 -0
- package/dist/particles/binding-switcher/binding-switcher.js +183 -0
- package/dist/particles/binding-switcher/binding-switcher.js.map +1 -0
- package/dist/particles/card-summary/card-network.d.ts +17 -0
- package/dist/particles/card-summary/card-network.js +25 -0
- package/dist/particles/card-summary/card-network.js.map +1 -0
- package/dist/particles/card-summary/card-summary.d.ts +83 -0
- package/dist/particles/card-summary/card-summary.js +188 -0
- package/dist/particles/card-summary/card-summary.js.map +1 -0
- package/dist/particles/copy-text/copy-text.d.ts +28 -0
- package/dist/particles/copy-text/copy-text.js +140 -0
- package/dist/particles/copy-text/copy-text.js.map +1 -0
- package/dist/particles/document-field/document-field.d.ts +34 -5
- package/dist/particles/document-field/document-field.js +115 -19
- package/dist/particles/document-field/document-field.js.map +1 -1
- package/dist/particles/field/field.js +1 -1
- package/dist/particles/field/field.js.map +1 -1
- package/dist/particles/footer-link-group/footer-link-group.d.ts +6 -2
- package/dist/particles/footer-link-group/footer-link-group.js +18 -7
- package/dist/particles/footer-link-group/footer-link-group.js.map +1 -1
- package/dist/particles/footer-social/footer-social.d.ts +1 -1
- package/dist/particles/footer-social/footer-social.js +1 -1
- package/dist/particles/footer-social/footer-social.js.map +1 -1
- package/dist/particles/incentives/incentives.d.ts +7 -3
- package/dist/particles/incentives/incentives.js +8 -3
- package/dist/particles/incentives/incentives.js.map +1 -1
- package/dist/particles/logo/logo.d.ts +3 -2
- package/dist/particles/logo/logo.js +20 -8
- package/dist/particles/logo/logo.js.map +1 -1
- package/dist/particles/navigation-link/navigation-link.d.ts +2 -3
- package/dist/particles/navigation-link/navigation-link.js +4 -4
- package/dist/particles/navigation-link/navigation-link.js.map +1 -1
- package/dist/particles/navigation-link-list/navigation-link-list.d.ts +6 -2
- package/dist/particles/navigation-link-list/navigation-link-list.js +18 -7
- package/dist/particles/navigation-link-list/navigation-link-list.js.map +1 -1
- package/dist/particles/order-details-back/order-details-back.d.ts +28 -0
- package/dist/particles/order-details-back/order-details-back.js +52 -0
- package/dist/particles/order-details-back/order-details-back.js.map +1 -0
- package/dist/particles/order-status-badge/order-status-badge.d.ts +19 -0
- package/dist/particles/order-status-badge/order-status-badge.js +50 -0
- package/dist/particles/order-status-badge/order-status-badge.js.map +1 -0
- package/dist/particles/payment-methods/payment-methods.d.ts +1 -1
- package/dist/particles/payment-methods/payment-methods.js +1 -1
- package/dist/particles/payment-methods/payment-methods.js.map +1 -1
- package/dist/particles/phone-field/phone-field.js +2 -2
- package/dist/particles/phone-field/phone-field.js.map +1 -1
- package/dist/particles/select/select.js +6 -6
- package/dist/particles/select/select.js.map +1 -1
- package/dist/particles/toast/toast.d.ts +21 -7
- package/dist/particles/toast/toast.js +76 -8
- package/dist/particles/toast/toast.js.map +1 -1
- package/dist/schema.graphql +1376 -126
- package/dist/sdk/actions/address-action-provider.d.ts +16 -0
- package/dist/sdk/actions/address-action-provider.js +20 -0
- package/dist/sdk/actions/address-action-provider.js.map +1 -0
- package/dist/sdk/actions/analytics-action-provider.js +1 -1
- package/dist/sdk/actions/index.d.ts +5 -1
- package/dist/sdk/actions/index.js +5 -1
- package/dist/sdk/actions/locale-action-provider.d.ts +16 -0
- package/dist/sdk/actions/locale-action-provider.js +20 -0
- package/dist/sdk/actions/locale-action-provider.js.map +1 -0
- package/dist/sdk/actions/order-action-provider.d.ts +12 -0
- package/dist/sdk/actions/order-action-provider.js +16 -0
- package/dist/sdk/actions/order-action-provider.js.map +1 -0
- package/dist/sdk/actions/saved-card-action-provider.d.ts +12 -0
- package/dist/sdk/actions/saved-card-action-provider.js +16 -0
- package/dist/sdk/actions/saved-card-action-provider.js.map +1 -0
- package/dist/sdk/actions/types.d.ts +10 -2
- package/dist/sdk/address/address-fields.js +38 -0
- package/dist/sdk/address/address-fields.js.map +1 -0
- package/dist/sdk/address/checkout-readiness.js +17 -0
- package/dist/sdk/address/checkout-readiness.js.map +1 -0
- package/dist/sdk/address/constants.js +60 -0
- package/dist/sdk/address/constants.js.map +1 -0
- package/dist/sdk/address/country-display.js +19 -0
- package/dist/sdk/address/country-display.js.map +1 -0
- package/dist/sdk/address/country-rules.js +30 -0
- package/dist/sdk/address/country-rules.js.map +1 -0
- package/dist/sdk/address/format-address.js +18 -0
- package/dist/sdk/address/format-address.js.map +1 -0
- package/dist/sdk/address/iso-countries.js +262 -0
- package/dist/sdk/address/iso-countries.js.map +1 -0
- package/dist/sdk/address/layouts.js +56 -0
- package/dist/sdk/address/layouts.js.map +1 -0
- package/dist/sdk/address/messages.js +97 -0
- package/dist/sdk/address/messages.js.map +1 -0
- package/dist/sdk/address/operations.d.ts +54 -0
- package/dist/sdk/address/operations.js +83 -0
- package/dist/sdk/address/operations.js.map +1 -0
- package/dist/sdk/address/postal-code-lookup-operations.js +23 -0
- package/dist/sdk/address/postal-code-lookup-operations.js.map +1 -0
- package/dist/sdk/address/postal-code.js +11 -0
- package/dist/sdk/address/postal-code.js.map +1 -0
- package/dist/sdk/address/schema.js +14 -0
- package/dist/sdk/address/schema.js.map +1 -0
- package/dist/sdk/address/server-fields.js +25 -0
- package/dist/sdk/address/server-fields.js.map +1 -0
- package/dist/sdk/address/switch-country.js +57 -0
- package/dist/sdk/address/switch-country.js.map +1 -0
- package/dist/sdk/address/types.d.ts +26 -0
- package/dist/sdk/address/use-address-actions.d.ts +29 -0
- package/dist/sdk/address/use-address-actions.js +247 -0
- package/dist/sdk/address/use-address-actions.js.map +1 -0
- package/dist/sdk/address/use-address-by-postal-code.js +57 -0
- package/dist/sdk/address/use-address-by-postal-code.js.map +1 -0
- package/dist/sdk/address/validate-address.js +62 -0
- package/dist/sdk/address/validate-address.js.map +1 -0
- package/dist/sdk/cache/storefront-cache.js +30 -4
- package/dist/sdk/cache/storefront-cache.js.map +1 -1
- package/dist/sdk/cart/operations.d.ts +2 -1
- package/dist/sdk/cart/operations.js +55 -1
- package/dist/sdk/cart/operations.js.map +1 -1
- package/dist/sdk/cart/optimistic.d.ts +30 -7
- package/dist/sdk/cart/optimistic.js +78 -41
- package/dist/sdk/cart/optimistic.js.map +1 -1
- package/dist/sdk/cart/use-cart-actions.d.ts +7 -0
- package/dist/sdk/cart/use-cart-actions.js +61 -15
- package/dist/sdk/cart/use-cart-actions.js.map +1 -1
- package/dist/sdk/cart/use-cart-sync-sales-channel.js +70 -0
- package/dist/sdk/cart/use-cart-sync-sales-channel.js.map +1 -0
- package/dist/sdk/client/fetch-page-data.d.ts +11 -0
- package/dist/sdk/client/fetch-page-data.js +12 -1
- package/dist/sdk/client/fetch-page-data.js.map +1 -1
- package/dist/sdk/content/create-content-runtime.js +1 -8
- package/dist/sdk/content/create-content-runtime.js.map +1 -1
- package/dist/sdk/data/failed-fields.js +23 -0
- package/dist/sdk/data/failed-fields.js.map +1 -0
- package/dist/sdk/data/index.js +2 -2
- package/dist/sdk/data/page-reactive-data-root.d.ts +45 -7
- package/dist/sdk/data/page-reactive-data-root.js +88 -12
- package/dist/sdk/data/page-reactive-data-root.js.map +1 -1
- package/dist/sdk/data/reactive-list-item-data-provider.js +2 -2
- package/dist/sdk/generated/fragment-types.js +2 -0
- package/dist/sdk/generated/fragment-types.js.map +1 -1
- package/dist/sdk/generated/gql.js +17 -11
- package/dist/sdk/generated/gql.js.map +1 -1
- package/dist/sdk/generated/graphql.d.ts +185 -46
- package/dist/sdk/generated/graphql.js +2587 -155
- package/dist/sdk/generated/graphql.js.map +1 -1
- package/dist/sdk/i18n/messages/en-US.js +157 -1
- package/dist/sdk/i18n/messages/en-US.json +157 -1
- package/dist/sdk/i18n/messages/en-US.json.d.ts +156 -0
- package/dist/sdk/i18n/messages/es-ES.js +157 -1
- package/dist/sdk/i18n/messages/es-ES.json +157 -1
- package/dist/sdk/i18n/messages/pt-BR.js +157 -1
- package/dist/sdk/i18n/messages/pt-BR.json +157 -1
- package/dist/sdk/i18n/provider.js +1 -1
- package/dist/sdk/i18n/provider.js.map +1 -1
- package/dist/sdk/locale/binding-switch-provider.d.ts +13 -0
- package/dist/sdk/locale/binding-switch-provider.js +20 -0
- package/dist/sdk/locale/binding-switch-provider.js.map +1 -0
- package/dist/sdk/locale/use-locale-actions.d.ts +16 -0
- package/dist/sdk/locale/use-locale-actions.js +70 -0
- package/dist/sdk/locale/use-locale-actions.js.map +1 -0
- package/dist/sdk/navigation/binding-switch.d.ts +39 -0
- package/dist/sdk/navigation/binding-switch.js +129 -0
- package/dist/sdk/navigation/binding-switch.js.map +1 -0
- package/dist/sdk/navigation/current-page.js +30 -0
- package/dist/sdk/navigation/current-page.js.map +1 -0
- package/dist/sdk/navigation/decode-route-params.d.ts +11 -0
- package/dist/sdk/navigation/decode-route-params.js +32 -0
- package/dist/sdk/navigation/decode-route-params.js.map +1 -0
- package/dist/sdk/navigation/not-found-log.d.ts +28 -0
- package/dist/sdk/navigation/not-found-log.js +133 -0
- package/dist/sdk/navigation/not-found-log.js.map +1 -0
- package/dist/sdk/navigation/path.js +25 -0
- package/dist/sdk/navigation/path.js.map +1 -0
- package/dist/sdk/navigation/redirect.d.ts +11 -0
- package/dist/sdk/navigation/redirect.js +22 -0
- package/dist/sdk/navigation/redirect.js.map +1 -0
- package/dist/sdk/navigation/report.js +70 -0
- package/dist/sdk/navigation/report.js.map +1 -0
- package/dist/sdk/navigation/resolve-storefront-href.d.ts +12 -0
- package/dist/sdk/navigation/resolve-storefront-href.js +214 -0
- package/dist/sdk/navigation/resolve-storefront-href.js.map +1 -0
- package/dist/sdk/navigation/route-identity-provider.d.ts +11 -0
- package/dist/sdk/navigation/route-identity-provider.js +20 -0
- package/dist/sdk/navigation/route-identity-provider.js.map +1 -0
- package/dist/sdk/navigation/route-map-provider.d.ts +19 -0
- package/dist/sdk/navigation/route-map-provider.js +29 -0
- package/dist/sdk/navigation/route-map-provider.js.map +1 -0
- package/dist/sdk/navigation/storefront-link.d.ts +16 -0
- package/dist/sdk/navigation/storefront-link.js +66 -0
- package/dist/sdk/navigation/storefront-link.js.map +1 -0
- package/dist/sdk/navigation/types.d.ts +45 -0
- package/dist/sdk/navigation/use-storefront-navigation.d.ts +26 -0
- package/dist/sdk/navigation/use-storefront-navigation.js +73 -0
- package/dist/sdk/navigation/use-storefront-navigation.js.map +1 -0
- package/dist/sdk/navigation/use-storefront-route.d.ts +26 -0
- package/dist/sdk/navigation/use-storefront-route.js +87 -0
- package/dist/sdk/navigation/use-storefront-route.js.map +1 -0
- package/dist/sdk/navigation/use-storefront-search-params.d.ts +11 -0
- package/dist/sdk/navigation/use-storefront-search-params.js +16 -0
- package/dist/sdk/navigation/use-storefront-search-params.js.map +1 -0
- package/dist/sdk/orders/completion-badge.d.ts +8 -0
- package/dist/sdk/orders/completion-badge.js +47 -0
- package/dist/sdk/orders/completion-badge.js.map +1 -0
- package/dist/sdk/orders/order-operations.d.ts +19 -0
- package/dist/sdk/orders/order-operations.js +21 -0
- package/dist/sdk/orders/order-operations.js.map +1 -0
- package/dist/sdk/orders/use-order-actions.d.ts +23 -0
- package/dist/sdk/orders/use-order-actions.js +62 -0
- package/dist/sdk/orders/use-order-actions.js.map +1 -0
- package/dist/sdk/payment/gateway-origin.js +26 -13
- package/dist/sdk/payment/gateway-origin.js.map +1 -1
- package/dist/sdk/payment/gateway.d.ts +14 -2
- package/dist/sdk/payment/gateway.js +32 -14
- package/dist/sdk/payment/gateway.js.map +1 -1
- package/dist/sdk/payment/operations.js +11 -0
- package/dist/sdk/payment/operations.js.map +1 -1
- package/dist/sdk/payment/submission-registry.d.ts +2 -1
- package/dist/sdk/payment/submission-registry.js +12 -8
- package/dist/sdk/payment/submission-registry.js.map +1 -1
- package/dist/sdk/payment/use-payment-actions.d.ts +5 -3
- package/dist/sdk/payment/use-payment-actions.js +5 -2
- package/dist/sdk/payment/use-payment-actions.js.map +1 -1
- package/dist/sdk/payment/use-place-order.d.ts +6 -3
- package/dist/sdk/payment/use-place-order.js +333 -143
- package/dist/sdk/payment/use-place-order.js.map +1 -1
- package/dist/sdk/saved-cards/operations.d.ts +16 -0
- package/dist/sdk/saved-cards/operations.js +41 -0
- package/dist/sdk/saved-cards/operations.js.map +1 -0
- package/dist/sdk/saved-cards/use-saved-card-actions.d.ts +26 -0
- package/dist/sdk/saved-cards/use-saved-card-actions.js +86 -0
- package/dist/sdk/saved-cards/use-saved-card-actions.js.map +1 -0
- package/dist/sdk/seo/page-alternates.d.ts +57 -0
- package/dist/sdk/seo/page-alternates.js +127 -0
- package/dist/sdk/seo/page-alternates.js.map +1 -0
- package/dist/sdk/shipping/shipping-operations.d.ts +1 -1
- package/dist/sdk/shipping/shipping-operations.js +13 -0
- package/dist/sdk/shipping/shipping-operations.js.map +1 -1
- package/dist/sdk/state/cart.d.ts +9 -2
- package/dist/sdk/state/cart.js +9 -2
- package/dist/sdk/state/cart.js.map +1 -1
- package/dist/utils/own-value.js +19 -0
- package/dist/utils/own-value.js.map +1 -0
- package/dist/utils/safe-href.d.ts +4 -4
- package/dist/utils/safe-href.js +31 -22
- package/dist/utils/safe-href.js.map +1 -1
- package/package.json +5 -1
- package/skills/storefront-asset/SKILL.md +111 -0
- package/skills/storefront-asset/references/api-side.md +153 -0
- package/skills/storefront-asset/references/findings.md +40 -0
- package/skills/storefront-asset/references/ui-side.md +94 -0
- package/src/styles/tailwind.css +28 -2
|
@@ -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 { type PaymentPreflightCheck, type PaymentSubmission, usePaymentSubmission } 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\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}>;\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 `userErrors` —\n * `sendPayments` already collapses every gateway outcome to one — so a throw means its contract broke.\n * Only a sanitised error *name* is logged (T160): the closure that threw holds the card, and a message\n * or stack could echo it — and so, unsanitised, could `.name` itself, since it is a writable own\n * property. See `loggableErrorName()`.\n */\nasync function invokeSubmission(\n submit: PaymentSubmission,\n transaction: PaymentGatewayTransaction,\n): Promise<readonly PaymentUserError[]> {\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 [NETWORK_ERROR];\n }\n return result;\n } catch (error) {\n console.error('[payment.placeOrder] the registered submission threw', loggableErrorName(error));\n return [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 * 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 * 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 const carriesServerCode = userErrors.some((userError) => {\n try {\n const code = userError.code;\n return typeof code === 'string' && code !== PAYMENT_NETWORK_ERROR_CODE;\n } catch {\n return false;\n }\n });\n return carriesServerCode ? 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/**\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 */\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] };\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) }\n : { orderId: callback.orderId, userErrors: [] };\n\n try {\n writeAttemptIfCurrent(\n setAttempt,\n current.attemptId,\n failed ? { ...current, phase: 'unresolved' } : { ...current, phase: 'settled', orderId: callback.orderId },\n );\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. A fresh full 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, or the POST was aborted, or a finalization\n * did not resolve. 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 } = 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 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] };\n }\n\n // Resolved before anything is opened (epic, T13): a transaction nothing can complete is dangling\n // state on the order form. The two causes are one state to a shopper and differ only in the log.\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 { orderId: null, userErrors: [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) {\n console.error('[payment.placeOrder] no payment method is selected');\n return { orderId: null, userErrors: [MISSING_METHOD_ERROR] };\n }\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 { orderId: null, userErrors: [submitGate] };\n }\n\n // The holder's own pre-flight on the draft it holds (T27). It resolves the same verdict its\n // submission would have resolved at :348, one network leg earlier, so an avoidable input mistake\n // never opens a transaction that is then left dangling. 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) — the same choice `submitGate` two branches\n // above makes on its own absence, staying quiet when it is simply falsy too. The parallel stops\n // there: `submitGate`'s truthiness *is* the refusal, while `preflight`'s only means a check was\n // registered. Deliberate, not overlooked (fix pass, review S1): a holder that registered no check\n // (the two-argument overload) contributes nothing here, exactly as if this branch did not exist.\n // Logging on this branch would fire on the overwhelming majority of calls — it is every pre-T27\n // holder's default, and most of this hook's own test suite exercises it as the baseline — diluting\n // the signal this file's other console.error calls carry for genuine anomalies. Failing closed\n // instead would refuse every one of those callers, breaking `register`'s own optional third\n // argument (NFR-6, Decision 2). A caller that wants 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 { orderId: null, userErrors: refusals };\n }\n }\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 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 // T29 (Decision 4, Scenario 6): nothing was paid, so the attempt records `refused` — a fresh\n // full sequence stays legal next time (`placeOrder()`'s own branch falls through to `run()` for\n // any phase other than opening/finalizing/settled/sent/unresolved, `refused` included). Fix\n // pass (Critical finding): this used to reset straight to `null`, which is indistinguishable\n // from \"no attempt ever started\" to a *different*, still-mounted or freshly-remounted instance —\n // `usePaymentMethodSelection`'s shared `placing` derivation reads this phase specifically to\n // release a lock a CTA that unmounted mid-flight can never release itself (FR-5, INV-5).\n // `writeAttemptIfCurrent`, not a plain write: this closure may be running after its own CTA\n // unmounted (NFR-3), so a newer completion for this same attempt must win instead.\n writeAttemptIfCurrent(setAttempt, attemptId, { attemptId, phase: 'refused', orderGroup: null, orderId: null });\n return { orderId: null, userErrors: payload.userErrors };\n }\n\n const settled = settleTransaction(payload);\n if (settled === null) {\n writeAttemptIfCurrent(setAttempt, attemptId, { attemptId, phase: 'refused', orderGroup: null, orderId: null });\n return { orderId: null, userErrors: [INVALID_TRANSACTION_ERROR] };\n }\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: PaymentGatewayTransaction = {\n gatewayUrl: settled.gatewayUrl,\n transactionId: settled.transactionId,\n merchantName: settled.merchantName,\n orderGroup: settled.orderGroup,\n userProfileId: settled.userProfileId,\n paymentSystem: selectedMethod.id,\n groupName: selectedMethod.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 // 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 const submissionErrors = await invokeSubmission(submit, transaction);\n // An unpaid transaction is never finalized.\n if (submissionErrors.length > 0) {\n // T29 (FR-2, Decision 5): the card's fate is unknown past this point. `gateway.ts` resolves one\n // opaque userError whether the gateway declined the card, the network failed, or the POST was\n // aborted (FR-6) — this hook cannot and must not try to tell those apart, so every one of them is\n // `unresolved`, never `refused`. Sticky for the payment root's lifetime (FR-4): the only call\n // `resume()` ever issues again is `gatewayCallback(orderGroup)`.\n outcome = { orderId: null, userErrors: submissionErrors };\n writeAttemptIfCurrent(setAttempt, attemptId, {\n attemptId,\n phase: 'unresolved',\n orderGroup: settled.orderGroup,\n orderId: null,\n });\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 = { orderId: null, userErrors: reconcileCallbackUserErrors(callback.userErrors) };\n writeAttemptIfCurrent(setAttempt, attemptId, {\n attemptId,\n phase: 'unresolved',\n orderGroup: settled.orderGroup,\n orderId: null,\n });\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: [] };\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 try {\n writeAttemptIfCurrent(\n setAttempt,\n attemptId,\n placedAttempt ??\n (handedOver\n ? { attemptId, phase: 'unresolved', orderGroup: settledOrderGroup, orderId: null }\n : { attemptId, phase: 'refused', orderGroup: null, orderId: null }),\n );\n } catch (writeError) {\n console.error(\n '[payment.placeOrder] the place-order recovery write threw',\n attemptId,\n loggableErrorName(writeError),\n );\n }\n return outcome ?? { orderId: null, userErrors: [NETWORK_ERROR] };\n }\n }, [\n startTransaction,\n gatewayCallback,\n track,\n path,\n resolve,\n resolvePreflight,\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\n const settle = (result: PlaceOrderResult): PlaceOrderResult => {\n inFlight.current = null;\n setPlacing(false);\n setUserErrors(result.userErrors);\n setOrderId(result.orderId);\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, () => settle({ orderId: null, userErrors: [NETWORK_ERROR] }));\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, so a fresh attempt is legal (Decision 4). The `'sent'` check just below is\n // defensive-only: `run()` never leaves the record resting there today (it writes `finalizing`\n // directly, same tick — see that write site's own comment); kept so a future write path resting at\n // `sent` would still resume correctly instead of silently falling through to a full run.\n if (attempt) {\n if (attempt.phase === 'settled') {\n return Promise.resolve({ orderId: attempt.orderId, userErrors: [] });\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(Promise.resolve({ orderId: attempt.orderId, userErrors: [PLACE_ORDER_IN_FLIGHT_ERROR] }));\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(() => ({ placeOrder, placing, userErrors, orderId }), [placeOrder, placing, userErrors, orderId]);\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 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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"}
|
|
@@ -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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.js","names":[],"sources":["../../../src/sdk/saved-cards/operations.ts"],"sourcesContent":["import type { TypedDocumentNode } from '@apollo/client';\nimport { gql } from '../graphql-tag.js';\n\n/**\n * Saved-card GraphQL operations: the account's writes against the shopper's saved cards.\n *\n * Authored with the serializable `gql` tag and excluded from the codegen `documents` glob (see\n * `codegen.ts`), like the identity operations: the pluck would otherwise duplicate these\n * hand-written types in `sdk/generated/`.\n */\n\n/** A single domain error returned by a saved-card write. */\nexport type SavedCardUserError = Readonly<{ code: string; message: string }>;\n\n/** The account's list of saved cards, as `customerSavedCards` reads it. */\nexport type CustomerSavedCardsEntity = Readonly<{\n __typename?: 'CustomerSavedCards';\n id: string;\n cards: ReadonlyArray<{\n __typename?: 'CustomerSavedCard';\n id: string;\n network: { __typename?: 'CardNetwork'; id: string; name: string };\n lastDigits: string;\n expiry: { __typename?: 'CardExpiry'; month: number; year: number } | null;\n status: 'USABLE' | 'EXPIRED' | 'UNUSABLE';\n }>;\n}>;\n\nexport type CustomerSavedCardRemoveMutation = {\n customerSavedCardRemove: {\n __typename?: 'CustomerSavedCardRemovePayload';\n customerSavedCards: CustomerSavedCardsEntity | null;\n userErrors: SavedCardUserError[];\n };\n};\n\nexport type CustomerSavedCardRemoveMutationVariables = {\n id: string;\n};\n\n/**\n * Selects every field the read exposes: the payload replaces the cached list, so a partial\n * selection would leave the remaining cards holding stale fields.\n */\nexport const CUSTOMER_SAVED_CARD_REMOVE: TypedDocumentNode<\n CustomerSavedCardRemoveMutation,\n CustomerSavedCardRemoveMutationVariables\n> = gql`\n mutation CustomerSavedCardRemove($id: ID!) {\n customerSavedCardRemove(id: $id) {\n __typename\n customerSavedCards {\n __typename\n id\n cards {\n __typename\n id\n network {\n __typename\n id\n name\n }\n lastDigits\n expiry {\n __typename\n month\n year\n }\n status\n }\n }\n userErrors {\n code\n message\n }\n }\n }\n`;\n"],"mappings":";;;;;;AA4CA,MAAa,6BAGT,GAAG"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import "../../core/dist/index.js";
|
|
2
|
+
import { SavedCardUserError } from "./operations.js";
|
|
3
|
+
//#region src/sdk/saved-cards/use-saved-card-actions.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* The `savedCard` action namespace — the account's writes against the shopper's saved cards
|
|
6
|
+
* (docs/storefront-core/adr-0010-actions-own-the-write-side.md).
|
|
7
|
+
*
|
|
8
|
+
* Every write is pessimistic: the caller awaits the server, and the returned `CustomerSavedCards`
|
|
9
|
+
* replaces the cached list, so the removed row leaves every subscriber with no refetch. A failure
|
|
10
|
+
* leaves the cache as it was.
|
|
11
|
+
*
|
|
12
|
+
* The removal prompt cannot close while a removal is pending, and the browser client has no request
|
|
13
|
+
* timeout, so `remove` bounds its own request. Past the bound it aborts through the `AbortSignal` in
|
|
14
|
+
* `context.fetchOptions` (honored by `HttpLink`) and resolves `NETWORK_ERROR`. The mutation skips
|
|
15
|
+
* the cache, and `remove` writes only an answer that arrived inside the bound, so a late answer from a
|
|
16
|
+
* link that ignores the abort cannot change the list after the shopper was told it failed.
|
|
17
|
+
*/
|
|
18
|
+
export type SavedCardRemovePayload = Readonly<{
|
|
19
|
+
id: string;
|
|
20
|
+
}>;
|
|
21
|
+
export type SavedCardActions = Readonly<{
|
|
22
|
+
/** Removes one saved card. Resolves to the payload's `userErrors` (empty on success). */
|
|
23
|
+
remove: (payload: SavedCardRemovePayload) => Promise<readonly SavedCardUserError[]>;
|
|
24
|
+
}>;
|
|
25
|
+
//#endregion
|
|
26
|
+
//# sourceMappingURL=use-saved-card-actions.d.ts.map
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { defineActions } from "../../core/dist/actions/registry.js";
|
|
3
|
+
import "../../core/dist/index.js";
|
|
4
|
+
import { loggableErrorName } from "../../utils/loggable-error-name.js";
|
|
5
|
+
import { CUSTOMER_SAVED_CARD_REMOVE } from "./operations.js";
|
|
6
|
+
import { useApolloClient, useMutation } from "@apollo/client/react";
|
|
7
|
+
import { useCallback, useMemo } from "react";
|
|
8
|
+
import { CombinedGraphQLErrors } from "@apollo/client/errors";
|
|
9
|
+
//#region src/sdk/saved-cards/use-saved-card-actions.ts
|
|
10
|
+
const SAVED_CARD_REMOVE_TIMEOUT_MS = 45e3;
|
|
11
|
+
var SavedCardRemoveTimeoutError = class extends Error {
|
|
12
|
+
constructor() {
|
|
13
|
+
super("Saved card removal timed out.");
|
|
14
|
+
this.name = "SavedCardRemoveTimeoutError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const NETWORK_ERROR = {
|
|
18
|
+
code: "NETWORK_ERROR",
|
|
19
|
+
message: "Unable to remove the card. Please try again."
|
|
20
|
+
};
|
|
21
|
+
const UNAUTHORIZED_ERROR = {
|
|
22
|
+
code: "UNAUTHORIZED",
|
|
23
|
+
message: "Your session has expired. Please sign in again."
|
|
24
|
+
};
|
|
25
|
+
/** An expired session needs sign-in, not a retry, so it keeps its own code. */
|
|
26
|
+
function toSavedCardError(error) {
|
|
27
|
+
if (CombinedGraphQLErrors.is(error)) {
|
|
28
|
+
if (error.errors.some(({ extensions }) => extensions?.code === "UNAUTHORIZED")) return UNAUTHORIZED_ERROR;
|
|
29
|
+
}
|
|
30
|
+
return NETWORK_ERROR;
|
|
31
|
+
}
|
|
32
|
+
function useSavedCardActions() {
|
|
33
|
+
const client = useApolloClient();
|
|
34
|
+
const [removeMutation] = useMutation(CUSTOMER_SAVED_CARD_REMOVE);
|
|
35
|
+
const remove = useCallback(async ({ id }) => {
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
let timer;
|
|
38
|
+
const timeout = new Promise((_, reject) => {
|
|
39
|
+
timer = setTimeout(() => {
|
|
40
|
+
controller.abort();
|
|
41
|
+
reject(new SavedCardRemoveTimeoutError());
|
|
42
|
+
}, SAVED_CARD_REMOVE_TIMEOUT_MS);
|
|
43
|
+
});
|
|
44
|
+
try {
|
|
45
|
+
const result = await Promise.race([removeMutation({
|
|
46
|
+
variables: { id },
|
|
47
|
+
context: { fetchOptions: { signal: controller.signal } },
|
|
48
|
+
fetchPolicy: "no-cache"
|
|
49
|
+
}), timeout]);
|
|
50
|
+
if (result.data) client.cache.writeQuery({
|
|
51
|
+
id: "ROOT_MUTATION",
|
|
52
|
+
query: CUSTOMER_SAVED_CARD_REMOVE,
|
|
53
|
+
variables: { id },
|
|
54
|
+
data: result.data
|
|
55
|
+
});
|
|
56
|
+
const payload = result.data?.customerSavedCardRemove;
|
|
57
|
+
return payload ? payload.userErrors : [NETWORK_ERROR];
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error("[savedCard.remove] customerSavedCardRemove failed", loggableErrorName(error));
|
|
60
|
+
return [toSavedCardError(error)];
|
|
61
|
+
} finally {
|
|
62
|
+
clearTimeout(timer);
|
|
63
|
+
}
|
|
64
|
+
}, [client, removeMutation]);
|
|
65
|
+
return useMemo(() => ({ remove }), [remove]);
|
|
66
|
+
}
|
|
67
|
+
defineActions({
|
|
68
|
+
namespace: "savedCard",
|
|
69
|
+
description: "Mutation-backed writes against the signed-in shopper's saved cards.",
|
|
70
|
+
provider: {
|
|
71
|
+
name: "SavedCardActionProvider",
|
|
72
|
+
importPath: "@vtex/faststore/internal/client"
|
|
73
|
+
},
|
|
74
|
+
methods: { remove: {
|
|
75
|
+
description: "Remove one saved card (async strategy). The row stays until the server answers.",
|
|
76
|
+
payload: [{
|
|
77
|
+
name: "id",
|
|
78
|
+
type: "string"
|
|
79
|
+
}]
|
|
80
|
+
} },
|
|
81
|
+
use: useSavedCardActions
|
|
82
|
+
});
|
|
83
|
+
//#endregion
|
|
84
|
+
export { SAVED_CARD_REMOVE_TIMEOUT_MS, useSavedCardActions };
|
|
85
|
+
|
|
86
|
+
//# sourceMappingURL=use-saved-card-actions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-saved-card-actions.js","names":[],"sources":["../../../src/sdk/saved-cards/use-saved-card-actions.ts"],"sourcesContent":["'use client';\n\nimport { CombinedGraphQLErrors } from '@apollo/client/errors';\nimport { useApolloClient, useMutation } from '@apollo/client/react';\nimport { defineActions } from '@vtex/fs-core';\nimport { useCallback, useMemo } from 'react';\nimport { loggableErrorName } from '../../utils/loggable-error-name.js';\nimport { CUSTOMER_SAVED_CARD_REMOVE, type SavedCardUserError } from './operations.js';\n\n/**\n * The `savedCard` action namespace — the account's writes against the shopper's saved cards\n * (docs/storefront-core/adr-0010-actions-own-the-write-side.md).\n *\n * Every write is pessimistic: the caller awaits the server, and the returned `CustomerSavedCards`\n * replaces the cached list, so the removed row leaves every subscriber with no refetch. A failure\n * leaves the cache as it was.\n *\n * The removal prompt cannot close while a removal is pending, and the browser client has no request\n * timeout, so `remove` bounds its own request. Past the bound it aborts through the `AbortSignal` in\n * `context.fetchOptions` (honored by `HttpLink`) and resolves `NETWORK_ERROR`. The mutation skips\n * the cache, and `remove` writes only an answer that arrived inside the bound, so a late answer from a\n * link that ignores the abort cannot change the list after the shopper was told it failed.\n */\n\nexport type SavedCardRemovePayload = Readonly<{ id: string }>;\n\nexport type SavedCardActions = Readonly<{\n /** Removes one saved card. Resolves to the payload's `userErrors` (empty on success). */\n remove: (payload: SavedCardRemovePayload) => Promise<readonly SavedCardUserError[]>;\n}>;\n\n// Above the resolver's own upstream budget (10 s per call, up to four in series), so the bound fires\n// only on a genuine hang.\nexport const SAVED_CARD_REMOVE_TIMEOUT_MS = 45_000;\n\nclass SavedCardRemoveTimeoutError extends Error {\n constructor() {\n super('Saved card removal timed out.');\n this.name = 'SavedCardRemoveTimeoutError';\n }\n}\n\nconst NETWORK_ERROR: SavedCardUserError = {\n code: 'NETWORK_ERROR',\n message: 'Unable to remove the card. Please try again.',\n};\n\nconst UNAUTHORIZED_ERROR: SavedCardUserError = {\n code: 'UNAUTHORIZED',\n message: 'Your session has expired. Please sign in again.',\n};\n\n/** An expired session needs sign-in, not a retry, so it keeps its own code. */\nfunction toSavedCardError(error: unknown): SavedCardUserError {\n if (CombinedGraphQLErrors.is(error)) {\n const unauthorized = error.errors.some(({ extensions }) => extensions?.code === 'UNAUTHORIZED');\n if (unauthorized) return UNAUTHORIZED_ERROR;\n }\n return NETWORK_ERROR;\n}\n\nexport function useSavedCardActions(): SavedCardActions {\n const client = useApolloClient();\n const [removeMutation] = useMutation(CUSTOMER_SAVED_CARD_REMOVE);\n\n const remove = useCallback<SavedCardActions['remove']>(\n async ({ id }) => {\n const controller = new AbortController();\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n controller.abort();\n reject(new SavedCardRemoveTimeoutError());\n }, SAVED_CARD_REMOVE_TIMEOUT_MS);\n });\n\n try {\n const result = await Promise.race([\n removeMutation({\n variables: { id },\n context: { fetchOptions: { signal: controller.signal } },\n fetchPolicy: 'no-cache',\n }),\n timeout,\n ]);\n if (result.data) {\n client.cache.writeQuery({\n id: 'ROOT_MUTATION',\n query: CUSTOMER_SAVED_CARD_REMOVE,\n variables: { id },\n data: result.data,\n });\n }\n // A missing payload removed nothing, so it must never read as success.\n const payload = result.data?.customerSavedCardRemove;\n return payload ? payload.userErrors : [NETWORK_ERROR];\n } catch (error) {\n console.error('[savedCard.remove] customerSavedCardRemove failed', loggableErrorName(error));\n return [toSavedCardError(error)];\n } finally {\n clearTimeout(timer);\n }\n },\n [client, removeMutation],\n );\n\n return useMemo(() => ({ remove }), [remove]);\n}\n\n/**\n * Declares the `savedCard` action namespace. The catalog builder reads this call to emit\n * `actions.json`; at runtime `SavedCardActionProvider` calls `useSavedCardActions` directly.\n */\nexport const savedCardActions = defineActions({\n namespace: 'savedCard',\n description: \"Mutation-backed writes against the signed-in shopper's saved cards.\",\n provider: { name: 'SavedCardActionProvider', importPath: '@vtex/faststore/internal/client' },\n methods: {\n remove: {\n description: 'Remove one saved card (async strategy). The row stays until the server answers.',\n payload: [{ name: 'id', type: 'string' }],\n },\n },\n use: useSavedCardActions,\n});\n"],"mappings":";;;;;;;;;AAiCA,MAAa,+BAA+B;AAE5C,IAAM,8BAAN,cAA0C,MAAM;CAC9C,cAAc;EACZ,MAAM,+BAA+B;EACrC,KAAK,OAAO;CACd;AACF;AAEA,MAAM,gBAAoC;CACxC,MAAM;CACN,SAAS;AACX;AAEA,MAAM,qBAAyC;CAC7C,MAAM;CACN,SAAS;AACX;;AAGA,SAAS,iBAAiB,OAAoC;CAC5D,IAAI,sBAAsB,GAAG,KAAK,GACX;MAAA,MAAM,OAAO,MAAM,EAAE,iBAAiB,YAAY,SAAS,cACjE,GAAG,OAAO;CAAA;CAE3B,OAAO;AACT;AAEA,SAAgB,sBAAwC;CACtD,MAAM,SAAS,gBAAgB;CAC/B,MAAM,CAAC,kBAAkB,YAAY,0BAA0B;CAE/D,MAAM,SAAS,YACb,OAAO,EAAE,SAAS;EAChB,MAAM,aAAa,IAAI,gBAAgB;EACvC,IAAI;EACJ,MAAM,UAAU,IAAI,SAAgB,GAAG,WAAW;GAChD,QAAQ,iBAAiB;IACvB,WAAW,MAAM;IACjB,OAAO,IAAI,4BAA4B,CAAC;GAC1C,GAAG,4BAA4B;EACjC,CAAC;EAED,IAAI;GACF,MAAM,SAAS,MAAM,QAAQ,KAAK,CAChC,eAAe;IACb,WAAW,EAAE,GAAG;IAChB,SAAS,EAAE,cAAc,EAAE,QAAQ,WAAW,OAAO,EAAE;IACvD,aAAa;GACf,CAAC,GACD,OACF,CAAC;GACD,IAAI,OAAO,MACT,OAAO,MAAM,WAAW;IACtB,IAAI;IACJ,OAAO;IACP,WAAW,EAAE,GAAG;IAChB,MAAM,OAAO;GACf,CAAC;GAGH,MAAM,UAAU,OAAO,MAAM;GAC7B,OAAO,UAAU,QAAQ,aAAa,CAAC,aAAa;EACtD,SAAS,OAAO;GACd,QAAQ,MAAM,qDAAqD,kBAAkB,KAAK,CAAC;GAC3F,OAAO,CAAC,iBAAiB,KAAK,CAAC;EACjC,UAAU;GACR,aAAa,KAAK;EACpB;CACF,GACA,CAAC,QAAQ,cAAc,CACzB;CAEA,OAAO,eAAe,EAAE,OAAO,IAAI,CAAC,MAAM,CAAC;AAC7C;AAMgC,cAAc;CAC5C,WAAW;CACX,aAAa;CACb,UAAU;EAAE,MAAM;EAA2B,YAAY;CAAkC;CAC3F,SAAS,EACP,QAAQ;EACN,aAAa;EACb,SAAS,CAAC;GAAE,MAAM;GAAM,MAAM;EAAS,CAAC;CAC1C,EACF;CACA,KAAK;AACP,CAAC"}
|