@vtex/faststore 0.0.10 → 0.0.12
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 +55 -0
- package/README.md +0 -1
- package/dist/_virtual/_rolldown/runtime.js +4 -0
- package/dist/actions.json +6 -1
- package/dist/catalog.json +789 -1701
- package/dist/cli/index.d.ts +66 -28
- package/dist/cli/index.js +7371 -512
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/server-runtime.js +41 -60
- package/dist/cli/server-runtime.js.map +1 -1
- package/dist/client.d.ts +3 -3
- package/dist/client.js +2 -2
- package/dist/components.d.ts +63 -57
- package/dist/components.js +60 -54
- package/dist/content-types.json +5785 -754
- package/dist/core/dist/define-component.d.ts +1 -1
- package/dist/core/dist/define-content-type.d.ts +50 -2
- package/dist/core/dist/define-content-type.js +107 -3
- package/dist/core/dist/define-content-type.js.map +1 -1
- package/dist/core/dist/index.d.ts +2 -2
- package/dist/docs/access-code-field.md +1 -1
- package/dist/docs/access-code-sign-in.md +126 -77
- package/dist/docs/account-address-book.md +210 -95
- package/dist/docs/account-menu.md +14 -15
- package/dist/docs/account-saved-cards.md +44 -39
- package/dist/docs/alert-dialog.md +3 -1
- package/dist/docs/avatar.md +4 -2
- package/dist/docs/billing-address.md +99 -30
- package/dist/docs/binding-switcher.md +5 -2
- package/dist/docs/buy-button.md +7 -8
- package/dist/docs/card-entry.md +39 -26
- package/dist/docs/card-summary.md +18 -8
- package/dist/docs/cardholder-document.md +5 -3
- package/dist/docs/carousel.md +5 -4
- package/dist/docs/cart-destination.md +39 -26
- package/dist/docs/cart-item.md +29 -14
- package/dist/docs/cart-totalizer.md +7 -5
- package/dist/docs/cart.md +135 -41
- package/dist/docs/challenge-return.md +135 -0
- package/dist/docs/client-preferences-fieldset.md +10 -5
- package/dist/docs/client-preferences.md +31 -8
- package/dist/docs/client-profile-fieldset.md +20 -6
- package/dist/docs/client-profile.md +12 -3
- package/dist/docs/copy-text.md +9 -7
- package/dist/docs/document-field.md +1 -1
- package/dist/docs/email-field.md +2 -1
- package/dist/docs/email-identification.md +12 -2
- package/dist/docs/footer-copyright.md +4 -2
- package/dist/docs/form.md +54 -18
- package/dist/docs/free-shipping-progress.md +6 -5
- package/dist/docs/identity-edit.md +8 -2
- package/dist/docs/identity-summary.md +18 -5
- package/dist/docs/installments.md +46 -32
- package/dist/docs/my-account-profile.md +16 -11
- package/dist/docs/navigation-link.md +5 -3
- package/dist/docs/order-cancel.md +3 -1
- package/dist/docs/order-delivery-address.md +7 -5
- package/dist/docs/order-delivery-group.md +1 -3
- package/dist/docs/order-details-back.md +1 -1
- package/dist/docs/order-details-header.md +1 -4
- package/dist/docs/order-invoice-download.md +60 -0
- package/dist/docs/order-list-row.md +1 -1
- package/dist/docs/order-list-search.md +13 -20
- package/dist/docs/order-list-views.md +11 -17
- package/dist/docs/order-list.md +12 -5
- package/dist/docs/order-payment.md +9 -7
- package/dist/docs/order-placed-date.md +1 -1
- package/dist/docs/order-placed-payment-summary.md +28 -15
- package/dist/docs/order-placed.md +49 -14
- package/dist/docs/order-progress.md +1 -1
- package/dist/docs/order-status-badge.md +20 -9
- package/dist/docs/order-summary.md +2 -2
- package/dist/docs/order-tracking-link.md +58 -0
- package/dist/docs/payment-method-badges.md +30 -4
- package/dist/docs/payment-methods.md +1 -1
- package/dist/docs/payment.md +36 -17
- package/dist/docs/phone-field.md +4 -4
- package/dist/docs/place-order.md +84 -36
- package/dist/docs/postal-code-field.md +3 -1
- package/dist/docs/pricing.md +21 -23
- package/dist/docs/product-gallery.md +19 -20
- package/dist/docs/product-information.md +13 -9
- package/dist/docs/product-list.md +7 -6
- package/dist/docs/product-shelf.md +0 -1
- package/dist/docs/promotion.md +4 -2
- package/dist/docs/region.md +23 -15
- package/dist/docs/save-card.md +7 -6
- package/dist/docs/saved-cards.md +65 -36
- package/dist/docs/shipping-address.md +57 -21
- package/dist/docs/shipping-simulator.md +53 -15
- package/dist/docs/sign-in-disclosure.md +176 -0
- package/dist/docs/sign-in-form.md +78 -47
- package/dist/docs/sign-in-redirect.md +4 -2
- package/dist/docs/sign-in.md +173 -0
- package/dist/docs/toast.md +15 -4
- package/dist/docs/variant-selector.md +15 -11
- package/dist/domain-logic/dist/checkout/rules.js +33 -0
- package/dist/domain-logic/dist/checkout/rules.js.map +1 -0
- package/dist/graphql/types.d.ts +4 -14
- package/dist/index.d.ts +2 -2
- package/dist/internal/client.d.ts +1 -3
- package/dist/internal/client.js +1 -1
- package/dist/internal/server.d.ts +7 -4
- package/dist/internal/server.js +7 -4
- package/dist/matter/access-code-sign-in/access-code-sign-in.d.ts +250 -66
- package/dist/matter/access-code-sign-in/access-code-sign-in.js +459 -188
- package/dist/matter/access-code-sign-in/access-code-sign-in.js.map +1 -1
- package/dist/matter/account-address-book/account-address-book-delete.d.ts +78 -40
- package/dist/matter/account-address-book/account-address-book-delete.js +86 -50
- package/dist/matter/account-address-book/account-address-book-delete.js.map +1 -1
- package/dist/matter/account-address-book/account-address-book-deliverability.d.ts +21 -4
- package/dist/matter/account-address-book/account-address-book-deliverability.js +45 -17
- package/dist/matter/account-address-book/account-address-book-deliverability.js.map +1 -1
- package/dist/matter/account-address-book/account-address-book-form-parts.d.ts +74 -19
- package/dist/matter/account-address-book/account-address-book-form-parts.js +77 -26
- package/dist/matter/account-address-book/account-address-book-form-parts.js.map +1 -1
- package/dist/matter/account-address-book/account-address-book-profile-notice.d.ts +27 -6
- package/dist/matter/account-address-book/account-address-book-profile-notice.js +55 -28
- package/dist/matter/account-address-book/account-address-book-profile-notice.js.map +1 -1
- package/dist/matter/account-address-book/account-address-book.d.ts +67 -14
- package/dist/matter/account-address-book/account-address-book.js +74 -33
- package/dist/matter/account-address-book/account-address-book.js.map +1 -1
- package/dist/matter/account-address-book/account-address-country-switch.js +3 -3
- package/dist/matter/account-address-book/account-address-country-switch.js.map +1 -1
- package/dist/matter/account-address-book/account-address-country.d.ts +66 -20
- package/dist/matter/account-address-book/account-address-country.js +83 -49
- package/dist/matter/account-address-book/account-address-country.js.map +1 -1
- package/dist/matter/account-address-book/account-address-form-context.d.ts +8 -4
- package/dist/matter/account-address-book/account-address-form-context.js.map +1 -1
- package/dist/matter/account-address-book/account-address-form.d.ts +468 -45
- package/dist/matter/account-address-book/account-address-form.js +518 -155
- package/dist/matter/account-address-book/account-address-form.js.map +1 -1
- package/dist/matter/account-address-book/account-address-record.d.ts +27 -6
- package/dist/matter/account-address-book/account-address-record.js +32 -17
- package/dist/matter/account-address-book/account-address-record.js.map +1 -1
- package/dist/matter/account-menu/account-menu.d.ts +34 -17
- package/dist/matter/account-menu/account-menu.js +46 -54
- package/dist/matter/account-menu/account-menu.js.map +1 -1
- package/dist/matter/account-menu-identity/account-menu-identity.js +1 -1
- package/dist/matter/account-saved-cards/account-saved-cards.d.ts +108 -28
- package/dist/matter/account-saved-cards/account-saved-cards.js +177 -122
- package/dist/matter/account-saved-cards/account-saved-cards.js.map +1 -1
- package/dist/matter/billing-address/billing-address.d.ts +156 -74
- package/dist/matter/billing-address/billing-address.js +227 -156
- package/dist/matter/billing-address/billing-address.js.map +1 -1
- package/dist/matter/breadcrumb/breadcrumb.d.ts +21 -3
- package/dist/matter/breadcrumb/breadcrumb.js +24 -9
- package/dist/matter/breadcrumb/breadcrumb.js.map +1 -1
- package/dist/matter/buy-button/buy-button.d.ts +8 -11
- package/dist/matter/buy-button/buy-button.js +10 -19
- package/dist/matter/buy-button/buy-button.js.map +1 -1
- package/dist/matter/card-entry/card-capture.js +12 -4
- package/dist/matter/card-entry/card-capture.js.map +1 -1
- package/dist/matter/card-entry/card-entry.d.ts +123 -32
- package/dist/matter/card-entry/card-entry.js +348 -221
- package/dist/matter/card-entry/card-entry.js.map +1 -1
- package/dist/matter/cart/cart-namespace.d.ts +7 -1
- package/dist/matter/cart/cart-namespace.js +7 -1
- package/dist/matter/cart/cart-namespace.js.map +1 -1
- package/dist/matter/cart/cart.d.ts +402 -61
- package/dist/matter/cart/cart.js +624 -153
- package/dist/matter/cart/cart.js.map +1 -1
- package/dist/matter/cart-destination/cart-destination.d.ts +30 -19
- package/dist/matter/cart-destination/cart-destination.js +160 -70
- package/dist/matter/cart-destination/cart-destination.js.map +1 -1
- package/dist/matter/cart-item/cart-item.d.ts +144 -17
- package/dist/matter/cart-item/cart-item.js +166 -49
- package/dist/matter/cart-item/cart-item.js.map +1 -1
- package/dist/matter/cart-item-compositions/cart-item-compositions.js +1 -1
- package/dist/matter/cart-item-compositions/cart-item-compositions.js.map +1 -1
- package/dist/matter/cart-item-customizations/cart-item-customizations.js +1 -1
- package/dist/matter/cart-item-customizations/cart-item-customizations.js.map +1 -1
- package/dist/matter/cart-item-discounts/cart-item-discounts.js +1 -1
- package/dist/matter/cart-item-discounts/cart-item-discounts.js.map +1 -1
- package/dist/matter/cart-item-seller-info/cart-item-seller-info.js +1 -1
- package/dist/matter/cart-item-seller-info/cart-item-seller-info.js.map +1 -1
- package/dist/matter/cart-item-variations/cart-item-variations.js +1 -1
- package/dist/matter/cart-item-variations/cart-item-variations.js.map +1 -1
- package/dist/matter/cart-totalizer/cart-totalizer.d.ts +43 -9
- package/dist/matter/cart-totalizer/cart-totalizer.js +46 -17
- package/dist/matter/cart-totalizer/cart-totalizer.js.map +1 -1
- package/dist/matter/challenge-return/challenge-return.d.ts +62 -0
- package/dist/matter/challenge-return/challenge-return.js +241 -0
- package/dist/matter/challenge-return/challenge-return.js.map +1 -0
- package/dist/matter/client-preferences/client-preferences-fieldset.d.ts +86 -17
- package/dist/matter/client-preferences/client-preferences-fieldset.js +126 -54
- package/dist/matter/client-preferences/client-preferences-fieldset.js.map +1 -1
- package/dist/matter/client-preferences/client-preferences.d.ts +50 -7
- package/dist/matter/client-preferences/client-preferences.js +84 -15
- package/dist/matter/client-preferences/client-preferences.js.map +1 -1
- package/dist/matter/client-profile/client-profile.d.ts +44 -9
- package/dist/matter/client-profile/client-profile.js +64 -24
- package/dist/matter/client-profile/client-profile.js.map +1 -1
- package/dist/matter/client-profile-form/client-profile-fieldset.d.ts +119 -22
- package/dist/matter/client-profile-form/client-profile-fieldset.js +161 -73
- package/dist/matter/client-profile-form/client-profile-fieldset.js.map +1 -1
- package/dist/matter/delivery-options/delivery-options-accordion.d.ts +49 -4
- package/dist/matter/delivery-options/delivery-options-accordion.js +67 -14
- package/dist/matter/delivery-options/delivery-options-accordion.js.map +1 -1
- package/dist/matter/delivery-options/delivery-options-controller.d.ts +69 -9
- package/dist/matter/delivery-options/delivery-options-controller.js +80 -14
- package/dist/matter/delivery-options/delivery-options-controller.js.map +1 -1
- package/dist/matter/delivery-options/delivery-options.d.ts +37 -2
- package/dist/matter/delivery-options/delivery-options.js +75 -12
- package/dist/matter/delivery-options/delivery-options.js.map +1 -1
- package/dist/matter/delivery-options/use-delivery-option-selection.d.ts +1 -0
- package/dist/matter/delivery-options/use-delivery-option-selection.js +1 -1
- package/dist/matter/delivery-options/use-delivery-option-selection.js.map +1 -1
- package/dist/matter/email-identification/email-identification.d.ts +25 -3
- package/dist/matter/email-identification/email-identification.js +49 -12
- package/dist/matter/email-identification/email-identification.js.map +1 -1
- package/dist/matter/free-shipping-progress/free-shipping-progress.d.ts +79 -7
- package/dist/matter/free-shipping-progress/free-shipping-progress.js +77 -20
- package/dist/matter/free-shipping-progress/free-shipping-progress.js.map +1 -1
- package/dist/matter/identity-edit/identity-edit.d.ts +52 -10
- package/dist/matter/identity-edit/identity-edit.js +47 -19
- package/dist/matter/identity-edit/identity-edit.js.map +1 -1
- package/dist/matter/identity-summary/identity-summary.d.ts +76 -22
- package/dist/matter/identity-summary/identity-summary.js +95 -45
- package/dist/matter/identity-summary/identity-summary.js.map +1 -1
- package/dist/matter/installments/installments-select.d.ts +98 -16
- package/dist/matter/installments/installments-select.js +156 -48
- package/dist/matter/installments/installments-select.js.map +1 -1
- package/dist/matter/installments/installments.d.ts +96 -16
- package/dist/matter/installments/installments.js +115 -40
- package/dist/matter/installments/installments.js.map +1 -1
- package/dist/matter/installments/use-installments-selection.d.ts +1 -0
- package/dist/matter/installments/use-installments-selection.js +8 -3
- package/dist/matter/installments/use-installments-selection.js.map +1 -1
- package/dist/matter/my-account-profile/my-account-profile.d.ts +358 -103
- package/dist/matter/my-account-profile/my-account-profile.js +370 -174
- package/dist/matter/my-account-profile/my-account-profile.js.map +1 -1
- package/dist/matter/order-cancel/order-cancel.d.ts +65 -19
- package/dist/matter/order-cancel/order-cancel.js +118 -51
- package/dist/matter/order-cancel/order-cancel.js.map +1 -1
- package/dist/matter/order-delivery-address/order-delivery-address.d.ts +8 -18
- package/dist/matter/order-delivery-address/order-delivery-address.js +14 -23
- package/dist/matter/order-delivery-address/order-delivery-address.js.map +1 -1
- package/dist/matter/order-delivery-group/order-delivery-group.d.ts +40 -24
- package/dist/matter/order-delivery-group/order-delivery-group.js +64 -74
- package/dist/matter/order-delivery-group/order-delivery-group.js.map +1 -1
- package/dist/matter/order-details-header/order-details-header.d.ts +32 -15
- package/dist/matter/order-details-header/order-details-header.js +66 -58
- package/dist/matter/order-details-header/order-details-header.js.map +1 -1
- package/dist/matter/order-invoice-download/order-invoice-download.d.ts +78 -0
- package/dist/matter/order-invoice-download/order-invoice-download.js +173 -0
- package/dist/matter/order-invoice-download/order-invoice-download.js.map +1 -0
- package/dist/matter/order-list/order-list.d.ts +164 -19
- package/dist/matter/order-list/order-list.js +229 -87
- package/dist/matter/order-list/order-list.js.map +1 -1
- package/dist/matter/order-list-row/order-list-row.d.ts +6 -1
- package/dist/matter/order-list-row/order-list-row.js +3 -2
- package/dist/matter/order-list-row/order-list-row.js.map +1 -1
- package/dist/matter/order-list-search/order-list-search.d.ts +18 -26
- package/dist/matter/order-list-search/order-list-search.js +30 -69
- package/dist/matter/order-list-search/order-list-search.js.map +1 -1
- package/dist/matter/order-list-views/order-list-views.d.ts +15 -22
- package/dist/matter/order-list-views/order-list-views.js +22 -68
- package/dist/matter/order-list-views/order-list-views.js.map +1 -1
- package/dist/matter/order-payment/order-payment.d.ts +33 -10
- package/dist/matter/order-payment/order-payment.js +44 -39
- package/dist/matter/order-payment/order-payment.js.map +1 -1
- package/dist/matter/order-placed/order-placed-deliveries.d.ts +144 -12
- package/dist/matter/order-placed/order-placed-deliveries.js +115 -40
- package/dist/matter/order-placed/order-placed-deliveries.js.map +1 -1
- package/dist/matter/order-placed/order-placed.d.ts +198 -31
- package/dist/matter/order-placed/order-placed.js +230 -139
- package/dist/matter/order-placed/order-placed.js.map +1 -1
- package/dist/matter/order-placed-date/order-placed-date.d.ts +5 -8
- package/dist/matter/order-placed-date/order-placed-date.js +7 -8
- package/dist/matter/order-placed-date/order-placed-date.js.map +1 -1
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.d.ts +90 -17
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js +93 -117
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js.map +1 -1
- package/dist/matter/order-progress/order-progress.d.ts +19 -8
- package/dist/matter/order-progress/order-progress.js +37 -45
- package/dist/matter/order-progress/order-progress.js.map +1 -1
- package/dist/matter/order-summary/order-summary.d.ts +19 -9
- package/dist/matter/order-summary/order-summary.js +38 -44
- package/dist/matter/order-summary/order-summary.js.map +1 -1
- package/dist/matter/order-tracking-link/order-tracking-link.d.ts +43 -0
- package/dist/matter/order-tracking-link/order-tracking-link.js +107 -0
- package/dist/matter/order-tracking-link/order-tracking-link.js.map +1 -0
- package/dist/matter/payment/payment-card-capture.js +17 -10
- package/dist/matter/payment/payment-card-capture.js.map +1 -1
- package/dist/matter/payment/payment-controller.d.ts +4 -2
- package/dist/matter/payment/payment-controller.js +30 -9
- package/dist/matter/payment/payment-controller.js.map +1 -1
- package/dist/matter/payment/payment-method-badges.js +1 -1
- package/dist/matter/payment/payment.d.ts +69 -19
- package/dist/matter/payment/payment.js +101 -35
- package/dist/matter/payment/payment.js.map +1 -1
- package/dist/matter/payment/use-payment-method-selection.d.ts +25 -4
- package/dist/matter/payment/use-payment-method-selection.js +94 -32
- package/dist/matter/payment/use-payment-method-selection.js.map +1 -1
- package/dist/matter/payment/use-payment-submit-gate.d.ts +14 -3
- package/dist/matter/payment/use-payment-submit-gate.js +47 -8
- package/dist/matter/payment/use-payment-submit-gate.js.map +1 -1
- package/dist/matter/place-order/place-order.d.ts +171 -57
- package/dist/matter/place-order/place-order.js +376 -185
- package/dist/matter/place-order/place-order.js.map +1 -1
- package/dist/matter/pricing/pricing.d.ts +152 -13
- package/dist/matter/pricing/pricing.js +153 -52
- package/dist/matter/pricing/pricing.js.map +1 -1
- package/dist/matter/product-information/product-information.d.ts +27 -4
- package/dist/matter/product-information/product-information.js +32 -15
- package/dist/matter/product-information/product-information.js.map +1 -1
- package/dist/matter/product-list/product-list.d.ts +24 -6
- package/dist/matter/product-list/product-list.js +32 -26
- package/dist/matter/product-list/product-list.js.map +1 -1
- package/dist/matter/product-shelf/product-shelf.d.ts +1 -3
- package/dist/matter/product-shelf/product-shelf.js +2 -15
- package/dist/matter/product-shelf/product-shelf.js.map +1 -1
- package/dist/matter/region/region.d.ts +33 -16
- package/dist/matter/region/region.js +59 -52
- package/dist/matter/region/region.js.map +1 -1
- package/dist/matter/save-card/save-card.d.ts +43 -8
- package/dist/matter/save-card/save-card.js +61 -23
- package/dist/matter/save-card/save-card.js.map +1 -1
- package/dist/matter/saved-cards/saved-cards.d.ts +113 -21
- package/dist/matter/saved-cards/saved-cards.js +349 -77
- package/dist/matter/saved-cards/saved-cards.js.map +1 -1
- package/dist/matter/shipping-address/address-payload.js +25 -0
- package/dist/matter/shipping-address/address-payload.js.map +1 -0
- package/dist/matter/shipping-address/country-descriptors.js +71 -71
- package/dist/matter/shipping-address/country-descriptors.js.map +1 -1
- package/dist/matter/shipping-address/library-country-descriptors.js +136 -0
- package/dist/matter/shipping-address/library-country-descriptors.js.map +1 -0
- package/dist/matter/shipping-address/shipping-address.d.ts +312 -45
- package/dist/matter/shipping-address/shipping-address.js +443 -188
- package/dist/matter/shipping-address/shipping-address.js.map +1 -1
- package/dist/matter/shipping-simulator/shipping-simulator.d.ts +250 -24
- package/dist/matter/shipping-simulator/shipping-simulator.js +257 -51
- package/dist/matter/shipping-simulator/shipping-simulator.js.map +1 -1
- package/dist/matter/sign-in/sign-in.d.ts +108 -0
- package/dist/matter/sign-in/sign-in.js +285 -0
- package/dist/matter/sign-in/sign-in.js.map +1 -0
- package/dist/matter/sign-in-disclosure/sign-in-disclosure.d.ts +81 -0
- package/dist/matter/sign-in-disclosure/sign-in-disclosure.js +232 -0
- package/dist/matter/sign-in-disclosure/sign-in-disclosure.js.map +1 -0
- package/dist/matter/sign-in-form/sign-in-form.d.ts +93 -62
- package/dist/matter/sign-in-form/sign-in-form.js +134 -144
- package/dist/matter/sign-in-form/sign-in-form.js.map +1 -1
- package/dist/particles/binding-switcher/binding-switcher.d.ts +29 -8
- package/dist/particles/binding-switcher/binding-switcher.js +37 -26
- package/dist/particles/binding-switcher/binding-switcher.js.map +1 -1
- package/dist/particles/card-summary/card-summary.d.ts +38 -8
- package/dist/particles/card-summary/card-summary.js +55 -22
- package/dist/particles/card-summary/card-summary.js.map +1 -1
- package/dist/particles/carousel/carousel.d.ts +99 -11
- package/dist/particles/carousel/carousel.js +103 -35
- package/dist/particles/carousel/carousel.js.map +1 -1
- package/dist/particles/copy-text/copy-text.js +5 -15
- package/dist/particles/copy-text/copy-text.js.map +1 -1
- package/dist/particles/dialog/dialog.d.ts +14 -6
- package/dist/particles/dialog/dialog.js +6 -3
- package/dist/particles/dialog/dialog.js.map +1 -1
- package/dist/particles/drawer/drawer.js +1 -1
- package/dist/particles/drawer/drawer.js.map +1 -1
- package/dist/particles/email-field/email-field.d.ts +2 -1
- package/dist/particles/email-field/email-field.js +2 -1
- package/dist/particles/email-field/email-field.js.map +1 -1
- package/dist/particles/footer-link-group/footer-link-group.js +1 -0
- package/dist/particles/footer-link-group/footer-link-group.js.map +1 -1
- package/dist/particles/form/form.d.ts +85 -13
- package/dist/particles/form/form.js +91 -42
- package/dist/particles/form/form.js.map +1 -1
- package/dist/particles/gallery/gallery.d.ts +129 -12
- package/dist/particles/gallery/gallery.js +121 -34
- package/dist/particles/gallery/gallery.js.map +1 -1
- package/dist/particles/order-details-back/order-details-back.d.ts +5 -3
- package/dist/particles/order-details-back/order-details-back.js +8 -10
- package/dist/particles/order-details-back/order-details-back.js.map +1 -1
- package/dist/particles/order-status-badge/order-status-badge.d.ts +3 -3
- package/dist/particles/order-status-badge/order-status-badge.js +4 -20
- package/dist/particles/order-status-badge/order-status-badge.js.map +1 -1
- package/dist/particles/password-field/password-field.d.ts +1 -1
- package/dist/particles/password-field/password-field.js +2 -4
- package/dist/particles/password-field/password-field.js.map +1 -1
- package/dist/particles/phone-field/phone-field.d.ts +4 -4
- package/dist/particles/phone-field/phone-field.js +8 -11
- package/dist/particles/phone-field/phone-field.js.map +1 -1
- package/dist/particles/spinner/spinner.d.ts +24 -4
- package/dist/particles/spinner/spinner.js +25 -9
- package/dist/particles/spinner/spinner.js.map +1 -1
- package/dist/particles/switch/switch.d.ts +4 -1
- package/dist/particles/switch/switch.js +3 -1
- package/dist/particles/switch/switch.js.map +1 -1
- package/dist/particles/toast/toast.d.ts +29 -3
- package/dist/particles/toast/toast.js +34 -10
- package/dist/particles/toast/toast.js.map +1 -1
- package/dist/schema.graphql +164 -56
- package/dist/sdk/address/address-fields.js +4 -4
- package/dist/sdk/address/address-fields.js.map +1 -1
- package/dist/sdk/address/constants.js +1 -6
- package/dist/sdk/address/constants.js.map +1 -1
- package/dist/sdk/address/messages.js +44 -44
- package/dist/sdk/address/messages.js.map +1 -1
- package/dist/sdk/address/postal-code.js +15 -2
- package/dist/sdk/address/postal-code.js.map +1 -1
- package/dist/sdk/address/types.d.ts +5 -3
- package/dist/sdk/address/validate-address.js +3 -3
- package/dist/sdk/address/validate-address.js.map +1 -1
- package/dist/sdk/analytics/use-analytics-actions.d.ts +1 -1
- package/dist/sdk/analytics/use-analytics-actions.js.map +1 -1
- package/dist/sdk/cart/use-cart-actions.js.map +1 -1
- package/dist/sdk/data/index.js +2 -2
- package/dist/sdk/data/reactive-list-item-data-provider.js +2 -2
- package/dist/sdk/generated/gql.js +1 -1
- package/dist/sdk/generated/gql.js.map +1 -1
- package/dist/sdk/generated/graphql.d.ts +20 -5
- package/dist/sdk/generated/graphql.js +6 -0
- package/dist/sdk/generated/graphql.js.map +1 -1
- package/dist/sdk/i18n/format-error-code.js +23 -0
- package/dist/sdk/i18n/format-error-code.js.map +1 -0
- package/dist/sdk/i18n/message-formatter.d.ts +22 -0
- package/dist/sdk/i18n/message-formatter.js +140 -0
- package/dist/sdk/i18n/message-formatter.js.map +1 -0
- package/dist/sdk/i18n/use-message-formatter.d.ts +8 -0
- package/dist/sdk/i18n/use-message-formatter.js +18 -0
- package/dist/sdk/i18n/use-message-formatter.js.map +1 -0
- package/dist/sdk/identity/use-identity-actions.js.map +1 -1
- package/dist/sdk/navigation/redirect.d.ts +2 -0
- package/dist/sdk/navigation/redirect.js +19 -1
- package/dist/sdk/navigation/redirect.js.map +1 -1
- package/dist/sdk/navigation/types.d.ts +13 -0
- package/dist/sdk/payment/bin-format.js +21 -0
- package/dist/sdk/payment/bin-format.js.map +1 -0
- package/dist/sdk/payment/challenge-return.js +37 -0
- package/dist/sdk/payment/challenge-return.js.map +1 -0
- package/dist/sdk/payment/confirmation-last-four.js +39 -0
- package/dist/sdk/payment/confirmation-last-four.js.map +1 -0
- package/dist/sdk/payment/gateway.d.ts +1 -1
- package/dist/sdk/payment/gateway.js +8 -2
- package/dist/sdk/payment/gateway.js.map +1 -1
- package/dist/sdk/payment/operations.d.ts +1 -0
- package/dist/sdk/payment/operations.js +28 -6
- package/dist/sdk/payment/operations.js.map +1 -1
- package/dist/sdk/payment/resumed-purchase.d.ts +1 -0
- package/dist/sdk/payment/resumed-purchase.js +119 -0
- package/dist/sdk/payment/resumed-purchase.js.map +1 -0
- package/dist/sdk/payment/security-code-mask.js +16 -23
- package/dist/sdk/payment/security-code-mask.js.map +1 -1
- package/dist/sdk/payment/submission-registry.js +6 -2
- package/dist/sdk/payment/submission-registry.js.map +1 -1
- package/dist/sdk/payment/use-payment-actions.d.ts +18 -3
- package/dist/sdk/payment/use-payment-actions.js +23 -4
- package/dist/sdk/payment/use-payment-actions.js.map +1 -1
- package/dist/sdk/payment/use-place-order.d.ts +10 -2
- package/dist/sdk/payment/use-place-order.js +138 -76
- package/dist/sdk/payment/use-place-order.js.map +1 -1
- package/dist/sdk/provider/storefront-provider.d.ts +1 -7
- package/dist/sdk/provider/storefront-provider.js +4 -11
- package/dist/sdk/provider/storefront-provider.js.map +1 -1
- package/dist/sdk/seo/entity-landing.d.ts +24 -0
- package/dist/sdk/seo/entity-landing.js +30 -0
- package/dist/sdk/seo/entity-landing.js.map +1 -0
- package/dist/sdk/seo/page-alternates.d.ts +3 -2
- package/dist/sdk/seo/page-alternates.js +4 -2
- package/dist/sdk/seo/page-alternates.js.map +1 -1
- package/dist/sdk/seo/search-landing.d.ts +42 -0
- package/dist/sdk/seo/search-landing.js +85 -0
- package/dist/sdk/seo/search-landing.js.map +1 -0
- package/dist/sdk/session/mock-session.d.ts +9 -0
- package/dist/sdk/session/mock-session.js +31 -0
- package/dist/sdk/session/mock-session.js.map +1 -0
- package/dist/sdk/session/read-server-session.d.ts +3 -0
- package/dist/sdk/session/read-server-session.js +5 -0
- package/dist/sdk/session/read-server-session.js.map +1 -1
- package/dist/sdk/session/return-path-segment.d.ts +10 -0
- package/dist/sdk/session/return-path-segment.js +15 -0
- package/dist/sdk/session/return-path-segment.js.map +1 -0
- package/dist/sdk/session/use-session-actions.js.map +1 -1
- package/dist/sdk/shipping/use-cart-shipping-simulation.js.map +1 -1
- package/dist/sdk/shipping/use-shipping-actions.js.map +1 -1
- package/dist/sdk/shipping/use-shipping-simulation.js.map +1 -1
- package/dist/utils/safe-href.d.ts +2 -0
- package/package.json +4 -3
- package/skills/storefront-asset/SKILL.md +2 -2
- package/skills/storefront-asset/references/api-side.md +27 -14
- package/skills/storefront-asset/references/findings.md +5 -2
- package/src/styles/tailwind.css +8 -0
- package/dist/matter/shipping-address/country-descriptors.d.ts +0 -1
- package/dist/message-locales.json +0 -7
- package/dist/sdk/i18n/client.d.ts +0 -4
- package/dist/sdk/i18n/client.js +0 -4
- package/dist/sdk/i18n/locales.d.ts +0 -8
- package/dist/sdk/i18n/locales.js +0 -57
- package/dist/sdk/i18n/locales.js.map +0 -1
- package/dist/sdk/i18n/messages/en-US.js +0 -183
- package/dist/sdk/i18n/messages/en-US.js.map +0 -1
- package/dist/sdk/i18n/messages/en-US.json +0 -178
- package/dist/sdk/i18n/messages/en-US.json.d.ts +0 -182
- package/dist/sdk/i18n/messages/es-ES.js +0 -183
- package/dist/sdk/i18n/messages/es-ES.js.map +0 -1
- package/dist/sdk/i18n/messages/es-ES.json +0 -178
- package/dist/sdk/i18n/messages/pt-BR.js +0 -183
- package/dist/sdk/i18n/messages/pt-BR.js.map +0 -1
- package/dist/sdk/i18n/messages/pt-BR.json +0 -178
- package/dist/sdk/i18n/provider.d.ts +0 -1
- package/dist/sdk/i18n/provider.js +0 -30
- package/dist/sdk/i18n/provider.js.map +0 -1
- package/dist/sdk/i18n/server.d.ts +0 -28
- package/dist/sdk/i18n/server.js +0 -59
- package/dist/sdk/i18n/server.js.map +0 -1
- package/dist/utils/resolve-authored-copy.js +0 -18
- package/dist/utils/resolve-authored-copy.js.map +0 -1
- package/dist/utils/substitute-template.js +0 -33
- package/dist/utils/substitute-template.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gateway.js","names":[],"sources":["../../../src/sdk/payment/gateway.ts"],"sourcesContent":["/**\n * The direct-to-gateway card POST (specs/payment-action-namespace.md, US-6) — the PCI boundary of the\n * credit-card feature. A new card's number and CVV, or a saved card's CVV, go from the browser straight\n * to the VTEX payment gateway and never transit our GraphQL layer or the normalized Apollo cache\n * (RFC D13; ADR-0010's sanctioned façade flavor).\n *\n * This module imports no Apollo and no GraphQL symbol on purpose: the code that holds card data has no\n * route to the cache, and that property is checkable by reading the imports above. Card data arrives as\n * a runtime argument from the card-entry component's local state (T7) — it is never a declared action\n * payload, a fragment field, or a schema-bindable prop, so the AI Store Builder and the visual editor\n * have no name to map a value onto.\n *\n * Nothing on this path is ever logged, serialized, or stored anywhere but the request body: the caller\n * in `use-payment-actions.ts` logs a static message plus the error's `name` only.\n *\n * The request shape is behavior evidence from the legacy checkout\n * (`fastcheckout@cd09bec3`, `packages/apps/client/src/screens/Payment/components/CardForm/services/gateway.service.ts`)\n * — never a port. Where the legacy hardcoded a value under a `TODO` (installment count, interest rate,\n * reference value) this builder reads it off the transaction descriptor instead.\n */\n\nimport { loggableErrorName } from '../../utils/loggable-error-name.js';\nimport { isTrustedGatewayOrigin } from './gateway-origin.js';\nimport type { PaymentUserError } from './operations.js';\n\n/** Client-synthesized code for a failed gateway leg — distinct from the BFF's `NETWORK_ERROR`. */\nexport const PAYMENT_GATEWAY_ERROR_CODE = 'GATEWAY_ERROR';\n\n/** The single userError a rejected request, a non-2xx response and a request the builder refused all resolve to. */\nexport const PAYMENT_GATEWAY_USER_ERROR: PaymentUserError = {\n code: PAYMENT_GATEWAY_ERROR_CODE,\n message: 'Unable to complete the payment. Please try again.',\n};\n\n/**\n * Non-sensitive descriptor of the open transaction. Every field maps to a value the caller already\n * holds — `paymentStartTransaction`'s payload (T3) or the `Payment` read model (T1) — except\n * `gatewayUrl` and `merchantName`, which that payload does not carry today (Gaps 1 and 2 in the spec).\n * Amounts are in the currency's **minor units**; `PaymentStartTransactionPayload.value.amount` is a\n * decimal, so the caller converts once, visibly, rather than having this module assume an exponent.\n */\nexport type PaymentGatewayTransaction = Readonly<{\n /**\n * Gateway origin, e.g. `https://acme.vtexvault.com`. Server-resolved by T18 and read off\n * `paymentStartTransaction`'s payload; never derived here. It is nullable there and required here,\n * so the builder re-checks it (`isTrustedGatewayOrigin`) rather than trusting the type.\n */\n gatewayUrl: string;\n /** `PaymentStartTransactionPayload.transactionId`. */\n transactionId: string;\n /** VTEX merchant name for this transaction. Resolved server-side by T3 but not exposed on its payload. */\n merchantName: string;\n /** `PaymentStartTransactionPayload.orderGroup` — the gateway's `orderId` query parameter. */\n orderGroup: string;\n /** `PaymentStartTransactionPayload.userProfileId`; null for a guest order, so the query parameter is left out. */\n userProfileId: string | null;\n /** `Payment.selectedMethodId` — the VTEX payment-system id. */\n paymentSystem: string;\n /** `PaymentMethod.groupName`, e.g. `creditCardPaymentGroup`. */\n groupName: string;\n /** `PaymentStartTransactionPayload.value.currencyCode`. */\n currencyCode: string;\n /** Transaction total, in minor units. */\n valueInCents: number;\n /** Pre-interest base (`PaymentInstallmentOption.baseValue`), in minor units. */\n referenceValueInCents: number;\n /** `Payment.selectedInstallment.count`; 1 when the shopper never picked a plan. */\n installments: number;\n /** Per-installment amount (`PaymentInstallment.value`), in minor units. */\n installmentsValueInCents: number;\n /** `PaymentInstallment.interestRate`; 0 for the interest-free 1x default. */\n interestRate: number;\n /** Absolute return URL. Phase-2 redirect mode only; omitted in v1's direct mode. */\n callbackUrl?: string;\n /**\n * Billing differs from the delivery address. Defaults to `false`, because `setBillingAddress` persists\n * billing on the order form's `invoiceData` (T3) and the gateway reads it from there rather than from\n * this body. The call site that would override it is T13's `usePlaceOrder`, the one place the\n * descriptor is assembled; T11 owns the live verification of the assumption and publishes the flag to\n * T5's `draft` for T13 to read. Confirming it never reopens this module.\n */\n billingAddressDifferent?: boolean;\n}>;\n\n/** A newly entered card. Runtime argument only — never declared, never logged, never cached. */\nexport type PaymentGatewayNewCard = Readonly<{\n kind: 'new';\n /** Full card number. The builder strips whatever separators the method's `numberMask` inserted. */\n number: string;\n /** Security code (CVV). */\n securityCode: string;\n /** Expiry. Normalized to `MM/YY`, so a `MM/YYYY` mask is accepted. */\n expirationDate: string;\n holderName: string;\n /** Shopper document (a CPF). The builder strips its punctuation. */\n document?: string;\n /** Shopper-assigned label, when the save-card control collected one. */\n cardLabel?: string;\n /** The card-save flag itself — the other half of `payment.saveCard`'s consent write (RFC D5). */\n saveCard?: boolean;\n}>;\n\n/** A previously saved card: its account reference plus the CVV the shopper re-enters. */\nexport type PaymentGatewaySavedCard = Readonly<{\n kind: 'saved';\n /** `SavedCard.accountId`. */\n accountId: string;\n /** `SavedCard.bin` — not sensitive; the read model exposes it for installment lookup. */\n bin: string;\n /** Security code (CVV). */\n securityCode: string;\n}>;\n\nexport type PaymentGatewayCard = PaymentGatewayNewCard | PaymentGatewaySavedCard;\n\nexport type SendPaymentsRequest = Readonly<{\n transaction: PaymentGatewayTransaction;\n card: PaymentGatewayCard;\n}>;\n\n/** The assembled request. Exported so the body shape is unit-testable without issuing a POST. */\nexport type GatewayPaymentsRequest = Readonly<{ url: string; body: readonly unknown[] }>;\n\n/** VTEX bins the leading eight digits, not six — the legacy's own `sanitizedBin` slice. */\nconst BIN_LENGTH = 8;\n\n/**\n * Strips the separators a mask inserted. `PaymentMethodValidator.numberMask` is server-driven (T1) and the\n * card-entry component (T7) renders the input through it, so its local state holds `4111 1111 1111 1111`\n * rather than the digits. The legacy normalized here, in the builder, before its own BIN slice\n * (`card.cardNumber = card.cardNumber.replace(/ /g, '')`) — the boundary is the one place the obligation\n * cannot be forgotten, and a spaced BIN is a lookup the gateway fails with no diagnosable error.\n */\nfunction toDigits(value: string): string {\n return value.replace(/\\D/g, '');\n}\n\n/** `MM/YY` whatever the mask produced (`MM/YYYY`, `MMYY`) — the legacy's own two-slice normalization. */\nfunction toDueDate(expirationDate: string): string {\n const digits = toDigits(expirationDate);\n return `${digits.slice(0, 2)}/${digits.slice(-2)}`;\n}\n\n/** The card half of the body — the BIN and the `fields` map, both derived from one normalization pass. */\nfunction toCardBody(card: PaymentGatewayCard): { bin: string; fields: Record<string, unknown> } {\n if (card.kind === 'saved') {\n return { bin: card.bin, fields: { bin: card.bin, validationCode: card.securityCode } };\n }\n\n const number = toDigits(card.number);\n const bin = number.slice(0, BIN_LENGTH);\n\n return {\n bin,\n fields: {\n bin,\n cardNumber: number,\n // The legacy's own `\\W` strip, so a CPF typed `123.456.789-00` reaches the gateway unpunctuated.\n document: card.document === undefined ? null : card.document.replace(/\\W/g, ''),\n dueDate: toDueDate(card.expirationDate),\n holderName: card.holderName,\n validationCode: card.securityCode,\n cardLabel: card.cardLabel,\n savePaymentData: card.saveCard ?? false,\n },\n };\n}\n\n/**\n * Builds the gateway URL and body. Billing is deliberately absent from the body: `setBillingAddress`\n * already persisted it on the order form's `invoiceData` (T3), so the gateway reads it from there — hence\n * the `billingAddressDifferent: false` default the two billing flags derive from. `originalPaymentIndex`\n * is the legacy's own constant for a single-payment order.\n *\n * The origin check is the last line of defence, not the first: `usePlaceOrder` already refuses an\n * untrusted origin *before* asking a holder for the card (T13, Decision 4). A builder has no userError\n * channel, so it throws — `sendPayments` catches, collapses it to `GATEWAY_ERROR`, and logs the error's\n * name only, so nothing about the card reaches a log. The throw happens before the body is assembled.\n */\nexport function buildGatewayPaymentsRequest({ transaction, card }: SendPaymentsRequest): GatewayPaymentsRequest {\n if (!isTrustedGatewayOrigin(transaction.gatewayUrl)) {\n throw new Error('Refusing to post card data: the transaction carries no trusted https gateway origin.');\n }\n\n const params = new URLSearchParams({ orderId: transaction.orderGroup });\n if (transaction.userProfileId !== null) params.append('userProfileId', transaction.userProfileId);\n if (transaction.callbackUrl !== undefined) params.append('callbackUrl', transaction.callbackUrl);\n\n const transactionId = encodeURIComponent(transaction.transactionId);\n const url = `${transaction.gatewayUrl}/api/payments/transactions/${transactionId}/payments?${params}`;\n\n const { bin, fields } = toCardBody(card);\n const billingAddressDifferent = transaction.billingAddressDifferent ?? false;\n\n const payment = {\n accountId: card.kind === 'saved' ? card.accountId : null,\n bin,\n chooseToUseNewCard: card.kind === 'new',\n currencyCode: transaction.currencyCode,\n fields,\n groupName: transaction.groupName,\n hasDefaultBillingAddress: !billingAddressDifferent,\n id: transaction.merchantName,\n installmentValue: transaction.installmentsValueInCents,\n installments: transaction.installments,\n installmentsInterestRate: transaction.interestRate,\n installmentsValue: transaction.installmentsValueInCents,\n interestRate: transaction.interestRate,\n isBillingAddressDifferent: billingAddressDifferent,\n originalPaymentIndex: 1,\n paymentSystem: transaction.paymentSystem,\n referenceValue: transaction.referenceValueInCents,\n tokenId: null,\n transaction: { id: transaction.transactionId, merchantName: transaction.merchantName },\n value: transaction.valueInCents,\n };\n\n return { url, body: [payment] };\n}\n\n/**\n * T29 (FR-6, OQ-7): this leg's own abort budget. Before this task the direct-to-gateway POST carried no\n * `AbortSignal` and no timeout at all, so a gateway that never answered left `placing` true and the CTA\n * dead for the life of the mount (Route C, `place-order.md`'s Known limitations). The BFF's own default\n * is 10s (`apps/storefront-api-core/.../api-timeout.ts:9`), but a card authorization can legitimately\n * run longer than a GraphQL round trip, and this exact number is not confirmed with payments (spec\n * OQ-7) — a placeholder pending that answer, not a tuned production value.\n *\n * Exported so `gateway.test.ts`'s own abort-budget test can assert the abort fires at exactly this\n * duration, rather than against `vi.runAllTimersAsync()`, which fires any pending timer regardless of\n * duration and would stay green even if this were edited to `300_000`. That mechanism test alone is\n * still self-referential about this constant's own *value* — it advances the fake clock by whatever\n * `GATEWAY_REQUEST_TIMEOUT_MS` currently holds, so on its own it cannot catch an edit to the constant\n * either (fix round 2, verification finding V2). `gateway.test.ts` also asserts this constant against\n * the literal `30_000` directly, in its own dedicated test — that is what actually fails if this\n * constant's value ever changes.\n */\nexport const GATEWAY_REQUEST_TIMEOUT_MS = 30_000;\n\n/**\n * What a card POST resolves (T112). `reachedGateway` is `false` only when this module can show no request\n * was issued — the request builder refused before any `fetch`. Anything it cannot classify (a rejection, a\n * timeout, an abort, a malformed result) is `true`: a browser cannot tell whether a request that never\n * answered was processed, and a `false` frees the shopper to post the card again. Beside it, `userErrors`\n * keeps the `{ ..., userErrors }` payload shape (ADR-00005); it carries no card value.\n */\nexport type PaymentGatewayResult = Readonly<{\n /** false only when no request was issued to the gateway. Anything unclassified is true. */\n reachedGateway: boolean;\n userErrors: readonly PaymentUserError[];\n}>;\n\n/**\n * Posts the card straight to the gateway and resolves a `PaymentGatewayResult`. A non-2xx response\n * resolves `{ reachedGateway: true, userErrors: [GATEWAY_ERROR] }`; a request the builder refuses\n * (an untrusted origin, a malformed card) resolves `{ reachedGateway: false, userErrors: [GATEWAY_ERROR] }`\n * without issuing a `fetch` or arming a timer, logging only the error's name. A rejected request —\n * including this leg's own timeout firing, or `options.signal` aborting — propagates, and the handler in\n * `use-payment-actions.ts` collapses it to a `reachedGateway: true` result: nothing here can prove it\n * never left. Neither outcome reads the response body: it may echo the request, and it tells the shopper\n * nothing more actionable than \"try again\".\n *\n * `options.signal`, when given, aborts this request in *addition to* — never instead of — this module's\n * own timeout: a caller opts into an earlier or externally triggered abort, but cannot opt out of the\n * budget that keeps this leg from hanging forever (FR-6, US-3). Composed with a plain `AbortController`\n * rather than `AbortSignal.any`/`AbortSignal.timeout` so the timeout is driven by an ordinary\n * `setTimeout`, the one implementation a test can advance deterministically with fake timers.\n *\n * No caller in this package passes `options.signal` today — it exists to satisfy the spec's own\n * Technical Contract (`specs/make-an-order-attempt-survive-a.md`) ahead of a future integration. Fix\n * pass, Low finding: the listener this composes onto `externalSignal` is now removed in the `finally`\n * below on every exit path, not only when it fires — previously, a caller that supplied a long-lived\n * signal (as opposed to a fresh one per call) would accumulate one listener, and this function's own\n * dead `AbortController`, per call that resolved normally.\n */\nexport async function postPaymentsToGateway(\n request: SendPaymentsRequest,\n options?: { signal?: AbortSignal },\n): Promise<PaymentGatewayResult> {\n let built: GatewayPaymentsRequest;\n try {\n built = buildGatewayPaymentsRequest(request);\n } catch (error) {\n // Name only: the builder's message and the request it rejected can carry the card.\n console.error(\n '[payment.sendPayments] the gateway request was refused before it was sent',\n loggableErrorName(error),\n );\n return { reachedGateway: false, userErrors: [PAYMENT_GATEWAY_USER_ERROR] };\n }\n const { url, body } = built;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(\n () => controller.abort(new DOMException('The gateway request timed out.', 'TimeoutError')),\n GATEWAY_REQUEST_TIMEOUT_MS,\n );\n const externalSignal = options?.signal;\n const onExternalAbort = () => controller.abort(externalSignal?.reason);\n if (externalSignal) {\n if (externalSignal.aborted) {\n controller.abort(externalSignal.reason);\n } else {\n externalSignal.addEventListener('abort', onExternalAbort, { once: true });\n }\n }\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n return { reachedGateway: true, userErrors: response.ok ? [] : [PAYMENT_GATEWAY_USER_ERROR] };\n } finally {\n clearTimeout(timeoutId);\n externalSignal?.removeEventListener('abort', onExternalAbort);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,6BAA6B;;AAG1C,MAAa,6BAA+C;CAC1D,MAAM;CACN,SAAS;AACX;;AA4FA,MAAM,aAAa;;;;;;;;AASnB,SAAS,SAAS,OAAuB;CACvC,OAAO,MAAM,QAAQ,OAAO,EAAE;AAChC;;AAGA,SAAS,UAAU,gBAAgC;CACjD,MAAM,SAAS,SAAS,cAAc;CACtC,OAAO,GAAG,OAAO,MAAM,GAAG,CAAC,EAAE,GAAG,OAAO,MAAM,EAAE;AACjD;;AAGA,SAAS,WAAW,MAA4E;CAC9F,IAAI,KAAK,SAAS,SAChB,OAAO;EAAE,KAAK,KAAK;EAAK,QAAQ;GAAE,KAAK,KAAK;GAAK,gBAAgB,KAAK;EAAa;CAAE;CAGvF,MAAM,SAAS,SAAS,KAAK,MAAM;CACnC,MAAM,MAAM,OAAO,MAAM,GAAG,UAAU;CAEtC,OAAO;EACL;EACA,QAAQ;GACN;GACA,YAAY;GAEZ,UAAU,KAAK,aAAa,KAAA,IAAY,OAAO,KAAK,SAAS,QAAQ,OAAO,EAAE;GAC9E,SAAS,UAAU,KAAK,cAAc;GACtC,YAAY,KAAK;GACjB,gBAAgB,KAAK;GACrB,WAAW,KAAK;GAChB,iBAAiB,KAAK,YAAY;EACpC;CACF;AACF;;;;;;;;;;;;AAaA,SAAgB,4BAA4B,EAAE,aAAa,QAAqD;CAC9G,IAAI,CAAC,uBAAuB,YAAY,UAAU,GAChD,MAAM,IAAI,MAAM,sFAAsF;CAGxG,MAAM,SAAS,IAAI,gBAAgB,EAAE,SAAS,YAAY,WAAW,CAAC;CACtE,IAAI,YAAY,kBAAkB,MAAM,OAAO,OAAO,iBAAiB,YAAY,aAAa;CAChG,IAAI,YAAY,gBAAgB,KAAA,GAAW,OAAO,OAAO,eAAe,YAAY,WAAW;CAE/F,MAAM,gBAAgB,mBAAmB,YAAY,aAAa;CAClE,MAAM,MAAM,GAAG,YAAY,WAAW,6BAA6B,cAAc,YAAY;CAE7F,MAAM,EAAE,KAAK,WAAW,WAAW,IAAI;CACvC,MAAM,0BAA0B,YAAY,2BAA2B;CAyBvE,OAAO;EAAE;EAAK,MAAM,CAAC;GAtBnB,WAAW,KAAK,SAAS,UAAU,KAAK,YAAY;GACpD;GACA,oBAAoB,KAAK,SAAS;GAClC,cAAc,YAAY;GAC1B;GACA,WAAW,YAAY;GACvB,0BAA0B,CAAC;GAC3B,IAAI,YAAY;GAChB,kBAAkB,YAAY;GAC9B,cAAc,YAAY;GAC1B,0BAA0B,YAAY;GACtC,mBAAmB,YAAY;GAC/B,cAAc,YAAY;GAC1B,2BAA2B;GAC3B,sBAAsB;GACtB,eAAe,YAAY;GAC3B,gBAAgB,YAAY;GAC5B,SAAS;GACT,aAAa;IAAE,IAAI,YAAY;IAAe,cAAc,YAAY;GAAa;GACrF,OAAO,YAAY;EAGM,CAAC;CAAE;AAChC;;;;;;;;;;;;;;;;;;AAmBA,MAAa,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;AAsC1C,eAAsB,sBACpB,SACA,SAC+B;CAC/B,IAAI;CACJ,IAAI;EACF,QAAQ,4BAA4B,OAAO;CAC7C,SAAS,OAAO;EAEd,QAAQ,MACN,6EACA,kBAAkB,KAAK,CACzB;EACA,OAAO;GAAE,gBAAgB;GAAO,YAAY,CAAC,0BAA0B;EAAE;CAC3E;CACA,MAAM,EAAE,KAAK,SAAS;CAEtB,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,YAAY,iBACV,WAAW,MAAM,IAAI,aAAa,kCAAkC,cAAc,CAAC,GACzF,0BACF;CACA,MAAM,iBAAiB,SAAS;CAChC,MAAM,wBAAwB,WAAW,MAAM,gBAAgB,MAAM;CACrE,IAAI,gBAAgB;EAClB,IAAI,eAAe,SACjB,WAAW,MAAM,eAAe,MAAM;OAEtC,eAAe,iBAAiB,SAAS,iBAAiB,EAAE,MAAM,KAAK,CAAC;CAE5E;CAEA,IAAI;EAQF,OAAO;GAAE,gBAAgB;GAAM,aAAY,MAPpB,MAAM,KAAK;IAChC,QAAQ;IACR,SAAS,EAAE,gBAAgB,mBAAmB;IAC9C,MAAM,KAAK,UAAU,IAAI;IACzB,QAAQ,WAAW;GACrB,CAAC,EAAA,CAEmD,KAAK,CAAC,IAAI,CAAC,0BAA0B;EAAE;CAC7F,UAAU;EACR,aAAa,SAAS;EACtB,gBAAgB,oBAAoB,SAAS,eAAe;CAC9D;AACF"}
|
|
1
|
+
{"version":3,"file":"gateway.js","names":[],"sources":["../../../src/sdk/payment/gateway.ts"],"sourcesContent":["/**\n * The direct-to-gateway card POST (specs/payment-action-namespace.md, US-6) — the PCI boundary of the\n * credit-card feature. A new card's number and CVV, or a saved card's CVV, go from the browser straight\n * to the VTEX payment gateway and never transit our GraphQL layer or the normalized Apollo cache\n * (RFC D13; ADR-0010's sanctioned façade flavor).\n *\n * This module imports no Apollo and no GraphQL symbol on purpose: the code that holds card data has no\n * route to the cache, and that property is checkable by reading the imports above. Card data arrives as\n * a runtime argument from the card-entry component's local state (T7) — it is never a declared action\n * payload, a fragment field, or a schema-bindable prop, so the AI Store Builder and the visual editor\n * have no name to map a value onto.\n *\n * Nothing on this path is ever logged, serialized, or stored anywhere but the request body: the caller\n * in `use-payment-actions.ts` logs a static message plus the error's `name` only.\n *\n * The request shape is behavior evidence from the legacy checkout\n * (`fastcheckout@cd09bec3`, `packages/apps/client/src/screens/Payment/components/CardForm/services/gateway.service.ts`)\n * — never a port. Where the legacy hardcoded a value under a `TODO` (installment count, interest rate,\n * reference value) this builder reads it off the transaction descriptor instead.\n */\n\nimport { loggableErrorName } from '../../utils/loggable-error-name.js';\nimport { isTrustedGatewayOrigin } from './gateway-origin.js';\nimport type { PaymentUserError } from './operations.js';\n\n/** Client-synthesized code for a failed gateway leg — distinct from the BFF's `NETWORK_ERROR`. */\nexport const PAYMENT_GATEWAY_ERROR_CODE = 'GATEWAY_ERROR';\n\n/**\n * The single userError a rejected request, a non-2xx response and a request the builder refused all resolve to.\n * Its `message` is diagnostic; `PlaceOrder` words the code.\n */\nexport const PAYMENT_GATEWAY_USER_ERROR: PaymentUserError = {\n code: PAYMENT_GATEWAY_ERROR_CODE,\n message: 'Unable to complete the payment. Please try again.',\n};\n\n/**\n * Non-sensitive descriptor of the open transaction. Every field maps to a value the caller already\n * holds — `paymentStartTransaction`'s payload (T3) or the `Payment` read model (T1) — except\n * `gatewayUrl` and `merchantName`, which that payload does not carry today (Gaps 1 and 2 in the spec).\n * Amounts are in the currency's **minor units**; `PaymentStartTransactionPayload.value.amount` is a\n * decimal, so the caller converts once, visibly, rather than having this module assume an exponent.\n */\nexport type PaymentGatewayTransaction = Readonly<{\n /**\n * Gateway origin, e.g. `https://acme.vtexvault.com`. Server-resolved by T18 and read off\n * `paymentStartTransaction`'s payload; never derived here. It is nullable there and required here,\n * so the builder re-checks it (`isTrustedGatewayOrigin`) rather than trusting the type.\n */\n gatewayUrl: string;\n /** `PaymentStartTransactionPayload.transactionId`. */\n transactionId: string;\n /** VTEX merchant name for this transaction. Resolved server-side by T3 but not exposed on its payload. */\n merchantName: string;\n /** `PaymentStartTransactionPayload.orderGroup` — the gateway's `orderId` query parameter. */\n orderGroup: string;\n /** `PaymentStartTransactionPayload.userProfileId`; null for a guest order, so the query parameter is left out. */\n userProfileId: string | null;\n /** `Payment.selectedMethodId` — the VTEX payment-system id. */\n paymentSystem: string;\n /** `PaymentMethod.groupName`, e.g. `creditCardPaymentGroup`. */\n groupName: string;\n /** `PaymentStartTransactionPayload.value.currencyCode`. */\n currencyCode: string;\n /** Transaction total, in minor units. */\n valueInCents: number;\n /** Pre-interest base (`PaymentInstallmentOption.baseValue`), in minor units. */\n referenceValueInCents: number;\n /** `Payment.selectedInstallment.count`; 1 when the shopper never picked a plan. */\n installments: number;\n /** Per-installment amount (`PaymentInstallment.value`), in minor units. */\n installmentsValueInCents: number;\n /** `PaymentInstallment.interestRate`; 0 for the interest-free 1x default. */\n interestRate: number;\n /** Absolute return URL for the 3D Secure challenge. Omitted when the store authored no return page. */\n callbackUrl?: string;\n /**\n * Billing differs from the delivery address. Defaults to `false`, because `setBillingAddress` persists\n * billing on the order form's `invoiceData` (T3) and the gateway reads it from there rather than from\n * this body. The call site that would override it is T13's `usePlaceOrder`, the one place the\n * descriptor is assembled; T11 owns the live verification of the assumption and publishes the flag to\n * T5's `draft` for T13 to read. Confirming it never reopens this module.\n */\n billingAddressDifferent?: boolean;\n}>;\n\n/** A newly entered card. Runtime argument only — never declared, never logged, never cached. */\nexport type PaymentGatewayNewCard = Readonly<{\n kind: 'new';\n /** Full card number. The builder strips whatever separators the method's `numberMask` inserted. */\n number: string;\n /** Security code (CVV). */\n securityCode: string;\n /** Expiry. Normalized to `MM/YY`, so a `MM/YYYY` mask is accepted. */\n expirationDate: string;\n holderName: string;\n /** Shopper document (a CPF). The builder strips its punctuation. */\n document?: string;\n /** Shopper-assigned label, when the save-card control collected one. */\n cardLabel?: string;\n /** The card-save flag itself — the other half of `payment.saveCard`'s consent write (RFC D5). */\n saveCard?: boolean;\n}>;\n\n/** A previously saved card: its account reference plus the CVV the shopper re-enters. */\nexport type PaymentGatewaySavedCard = Readonly<{\n kind: 'saved';\n /** `SavedCard.accountId`. */\n accountId: string;\n /** `SavedCard.bin` — not sensitive; the read model exposes it for installment lookup. */\n bin: string;\n /** Security code (CVV). */\n securityCode: string;\n}>;\n\nexport type PaymentGatewayCard = PaymentGatewayNewCard | PaymentGatewaySavedCard;\n\nexport type SendPaymentsRequest = Readonly<{\n transaction: PaymentGatewayTransaction;\n card: PaymentGatewayCard;\n}>;\n\n/** The assembled request. Exported so the body shape is unit-testable without issuing a POST. */\nexport type GatewayPaymentsRequest = Readonly<{ url: string; body: readonly unknown[] }>;\n\n/** VTEX bins the leading eight digits, not six — the legacy's own `sanitizedBin` slice. */\nconst BIN_LENGTH = 8;\n\n/**\n * Strips the separators a mask inserted. `PaymentMethodValidator.numberMask` is server-driven (T1) and the\n * card-entry component (T7) renders the input through it, so its local state holds `4111 1111 1111 1111`\n * rather than the digits. The legacy normalized here, in the builder, before its own BIN slice\n * (`card.cardNumber = card.cardNumber.replace(/ /g, '')`) — the boundary is the one place the obligation\n * cannot be forgotten, and a spaced BIN is a lookup the gateway fails with no diagnosable error.\n */\nfunction toDigits(value: string): string {\n return value.replace(/\\D/g, '');\n}\n\n/** `MM/YY` whatever the mask produced (`MM/YYYY`, `MMYY`) — the legacy's own two-slice normalization. */\nfunction toDueDate(expirationDate: string): string {\n const digits = toDigits(expirationDate);\n return `${digits.slice(0, 2)}/${digits.slice(-2)}`;\n}\n\n/** The card half of the body — the BIN and the `fields` map, both derived from one normalization pass. */\nfunction toCardBody(card: PaymentGatewayCard): { bin: string; fields: Record<string, unknown> } {\n if (card.kind === 'saved') {\n return { bin: card.bin, fields: { bin: card.bin, validationCode: card.securityCode } };\n }\n\n const number = toDigits(card.number);\n const bin = number.slice(0, BIN_LENGTH);\n\n return {\n bin,\n fields: {\n bin,\n cardNumber: number,\n // The legacy's own `\\W` strip, so a CPF typed `123.456.789-00` reaches the gateway unpunctuated.\n document: card.document === undefined ? null : card.document.replace(/\\W/g, ''),\n dueDate: toDueDate(card.expirationDate),\n holderName: card.holderName,\n validationCode: card.securityCode,\n cardLabel: card.cardLabel,\n savePaymentData: card.saveCard ?? false,\n },\n };\n}\n\n/**\n * Builds the gateway URL and body. Billing is deliberately absent from the body: `setBillingAddress`\n * already persisted it on the order form's `invoiceData` (T3), so the gateway reads it from there — hence\n * the `billingAddressDifferent: false` default the two billing flags derive from. `originalPaymentIndex`\n * is the legacy's own constant for a single-payment order.\n *\n * The origin check is the last line of defence, not the first: `usePlaceOrder` already refuses an\n * untrusted origin *before* asking a holder for the card (T13, Decision 4). A builder has no userError\n * channel, so it throws — `sendPayments` catches, collapses it to `GATEWAY_ERROR`, and logs the error's\n * name only, so nothing about the card reaches a log. The throw happens before the body is assembled.\n */\nexport function buildGatewayPaymentsRequest({ transaction, card }: SendPaymentsRequest): GatewayPaymentsRequest {\n if (!isTrustedGatewayOrigin(transaction.gatewayUrl)) {\n throw new Error('Refusing to post card data: the transaction carries no trusted https gateway origin.');\n }\n\n const params = new URLSearchParams({ orderId: transaction.orderGroup });\n if (transaction.userProfileId !== null) params.append('userProfileId', transaction.userProfileId);\n if (transaction.callbackUrl !== undefined) {\n params.append('callbackUrl', transaction.callbackUrl);\n params.append('redirect', 'false');\n }\n\n const transactionId = encodeURIComponent(transaction.transactionId);\n const url = `${transaction.gatewayUrl}/api/payments/transactions/${transactionId}/payments?${params}`;\n\n const { bin, fields } = toCardBody(card);\n const billingAddressDifferent = transaction.billingAddressDifferent ?? false;\n\n const payment = {\n accountId: card.kind === 'saved' ? card.accountId : null,\n bin,\n chooseToUseNewCard: card.kind === 'new',\n currencyCode: transaction.currencyCode,\n fields,\n groupName: transaction.groupName,\n hasDefaultBillingAddress: !billingAddressDifferent,\n id: transaction.merchantName,\n installmentValue: transaction.installmentsValueInCents,\n installments: transaction.installments,\n installmentsInterestRate: transaction.interestRate,\n installmentsValue: transaction.installmentsValueInCents,\n interestRate: transaction.interestRate,\n isBillingAddressDifferent: billingAddressDifferent,\n originalPaymentIndex: 1,\n paymentSystem: transaction.paymentSystem,\n referenceValue: transaction.referenceValueInCents,\n tokenId: null,\n transaction: { id: transaction.transactionId, merchantName: transaction.merchantName },\n value: transaction.valueInCents,\n };\n\n return { url, body: [payment] };\n}\n\n/**\n * T29 (FR-6, OQ-7): this leg's own abort budget. Before this task the direct-to-gateway POST carried no\n * `AbortSignal` and no timeout at all, so a gateway that never answered left `placing` true and the CTA\n * dead for the life of the mount (Route C, `place-order.md`'s Known limitations). The BFF's own default\n * is 10s (`apps/storefront-api-core/.../api-timeout.ts:9`), but a card authorization can legitimately\n * run longer than a GraphQL round trip, and this exact number is not confirmed with payments (spec\n * OQ-7) — a placeholder pending that answer, not a tuned production value.\n *\n * Exported so `gateway.test.ts`'s own abort-budget test can assert the abort fires at exactly this\n * duration, rather than against `vi.runAllTimersAsync()`, which fires any pending timer regardless of\n * duration and would stay green even if this were edited to `300_000`. That mechanism test alone is\n * still self-referential about this constant's own *value* — it advances the fake clock by whatever\n * `GATEWAY_REQUEST_TIMEOUT_MS` currently holds, so on its own it cannot catch an edit to the constant\n * either (fix round 2, verification finding V2). `gateway.test.ts` also asserts this constant against\n * the literal `30_000` directly, in its own dedicated test — that is what actually fails if this\n * constant's value ever changes.\n */\nexport const GATEWAY_REQUEST_TIMEOUT_MS = 30_000;\n\n/**\n * What a card POST resolves (T112). `reachedGateway` is `false` only when this module can show no request\n * was issued — the request builder refused before any `fetch`. Anything it cannot classify (a rejection, a\n * timeout, an abort, a malformed result) is `true`: a browser cannot tell whether a request that never\n * answered was processed, and a `false` frees the shopper to post the card again. Beside it, `userErrors`\n * keeps the `{ ..., userErrors }` payload shape (ADR-00005); it carries no card value.\n */\nexport type PaymentGatewayResult = Readonly<{\n /** false only when no request was issued to the gateway. Anything unclassified is true. */\n reachedGateway: boolean;\n userErrors: readonly PaymentUserError[];\n}>;\n\n/**\n * Posts the card straight to the gateway and resolves a `PaymentGatewayResult`. A non-2xx response\n * resolves `{ reachedGateway: true, userErrors: [GATEWAY_ERROR] }`; a request the builder refuses\n * (an untrusted origin, a malformed card) resolves `{ reachedGateway: false, userErrors: [GATEWAY_ERROR] }`\n * without issuing a `fetch` or arming a timer, logging only the error's name. A rejected request —\n * including this leg's own timeout firing, or `options.signal` aborting — propagates, and the handler in\n * `use-payment-actions.ts` collapses it to a `reachedGateway: true` result: nothing here can prove it\n * never left. Neither outcome reads the response body: it may echo the request, and it tells the shopper\n * nothing more actionable than \"try again\".\n *\n * `options.signal`, when given, aborts this request in *addition to* — never instead of — this module's\n * own timeout: a caller opts into an earlier or externally triggered abort, but cannot opt out of the\n * budget that keeps this leg from hanging forever (FR-6, US-3). Composed with a plain `AbortController`\n * rather than `AbortSignal.any`/`AbortSignal.timeout` so the timeout is driven by an ordinary\n * `setTimeout`, the one implementation a test can advance deterministically with fake timers.\n *\n * No caller in this package passes `options.signal` today — it exists to satisfy the spec's own\n * Technical Contract (`specs/make-an-order-attempt-survive-a.md`) ahead of a future integration. Fix\n * pass, Low finding: the listener this composes onto `externalSignal` is now removed in the `finally`\n * below on every exit path, not only when it fires — previously, a caller that supplied a long-lived\n * signal (as opposed to a fresh one per call) would accumulate one listener, and this function's own\n * dead `AbortController`, per call that resolved normally.\n */\nexport async function postPaymentsToGateway(\n request: SendPaymentsRequest,\n options?: { signal?: AbortSignal },\n): Promise<PaymentGatewayResult> {\n let built: GatewayPaymentsRequest;\n try {\n built = buildGatewayPaymentsRequest(request);\n } catch (error) {\n // Name only: the builder's message and the request it rejected can carry the card.\n console.error(\n '[payment.sendPayments] the gateway request was refused before it was sent',\n loggableErrorName(error),\n );\n return { reachedGateway: false, userErrors: [PAYMENT_GATEWAY_USER_ERROR] };\n }\n const { url, body } = built;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(\n () => controller.abort(new DOMException('The gateway request timed out.', 'TimeoutError')),\n GATEWAY_REQUEST_TIMEOUT_MS,\n );\n const externalSignal = options?.signal;\n const onExternalAbort = () => controller.abort(externalSignal?.reason);\n if (externalSignal) {\n if (externalSignal.aborted) {\n controller.abort(externalSignal.reason);\n } else {\n externalSignal.addEventListener('abort', onExternalAbort, { once: true });\n }\n }\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n return { reachedGateway: true, userErrors: response.ok ? [] : [PAYMENT_GATEWAY_USER_ERROR] };\n } finally {\n clearTimeout(timeoutId);\n externalSignal?.removeEventListener('abort', onExternalAbort);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA0BA,MAAa,6BAA6B;;;;;AAM1C,MAAa,6BAA+C;CAC1D,MAAM;CACN,SAAS;AACX;;AA4FA,MAAM,aAAa;;;;;;;;AASnB,SAAS,SAAS,OAAuB;CACvC,OAAO,MAAM,QAAQ,OAAO,EAAE;AAChC;;AAGA,SAAS,UAAU,gBAAgC;CACjD,MAAM,SAAS,SAAS,cAAc;CACtC,OAAO,GAAG,OAAO,MAAM,GAAG,CAAC,EAAE,GAAG,OAAO,MAAM,EAAE;AACjD;;AAGA,SAAS,WAAW,MAA4E;CAC9F,IAAI,KAAK,SAAS,SAChB,OAAO;EAAE,KAAK,KAAK;EAAK,QAAQ;GAAE,KAAK,KAAK;GAAK,gBAAgB,KAAK;EAAa;CAAE;CAGvF,MAAM,SAAS,SAAS,KAAK,MAAM;CACnC,MAAM,MAAM,OAAO,MAAM,GAAG,UAAU;CAEtC,OAAO;EACL;EACA,QAAQ;GACN;GACA,YAAY;GAEZ,UAAU,KAAK,aAAa,KAAA,IAAY,OAAO,KAAK,SAAS,QAAQ,OAAO,EAAE;GAC9E,SAAS,UAAU,KAAK,cAAc;GACtC,YAAY,KAAK;GACjB,gBAAgB,KAAK;GACrB,WAAW,KAAK;GAChB,iBAAiB,KAAK,YAAY;EACpC;CACF;AACF;;;;;;;;;;;;AAaA,SAAgB,4BAA4B,EAAE,aAAa,QAAqD;CAC9G,IAAI,CAAC,uBAAuB,YAAY,UAAU,GAChD,MAAM,IAAI,MAAM,sFAAsF;CAGxG,MAAM,SAAS,IAAI,gBAAgB,EAAE,SAAS,YAAY,WAAW,CAAC;CACtE,IAAI,YAAY,kBAAkB,MAAM,OAAO,OAAO,iBAAiB,YAAY,aAAa;CAChG,IAAI,YAAY,gBAAgB,KAAA,GAAW;EACzC,OAAO,OAAO,eAAe,YAAY,WAAW;EACpD,OAAO,OAAO,YAAY,OAAO;CACnC;CAEA,MAAM,gBAAgB,mBAAmB,YAAY,aAAa;CAClE,MAAM,MAAM,GAAG,YAAY,WAAW,6BAA6B,cAAc,YAAY;CAE7F,MAAM,EAAE,KAAK,WAAW,WAAW,IAAI;CACvC,MAAM,0BAA0B,YAAY,2BAA2B;CAyBvE,OAAO;EAAE;EAAK,MAAM,CAAC;GAtBnB,WAAW,KAAK,SAAS,UAAU,KAAK,YAAY;GACpD;GACA,oBAAoB,KAAK,SAAS;GAClC,cAAc,YAAY;GAC1B;GACA,WAAW,YAAY;GACvB,0BAA0B,CAAC;GAC3B,IAAI,YAAY;GAChB,kBAAkB,YAAY;GAC9B,cAAc,YAAY;GAC1B,0BAA0B,YAAY;GACtC,mBAAmB,YAAY;GAC/B,cAAc,YAAY;GAC1B,2BAA2B;GAC3B,sBAAsB;GACtB,eAAe,YAAY;GAC3B,gBAAgB,YAAY;GAC5B,SAAS;GACT,aAAa;IAAE,IAAI,YAAY;IAAe,cAAc,YAAY;GAAa;GACrF,OAAO,YAAY;EAGM,CAAC;CAAE;AAChC;;;;;;;;;;;;;;;;;;AAmBA,MAAa,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;AAsC1C,eAAsB,sBACpB,SACA,SAC+B;CAC/B,IAAI;CACJ,IAAI;EACF,QAAQ,4BAA4B,OAAO;CAC7C,SAAS,OAAO;EAEd,QAAQ,MACN,6EACA,kBAAkB,KAAK,CACzB;EACA,OAAO;GAAE,gBAAgB;GAAO,YAAY,CAAC,0BAA0B;EAAE;CAC3E;CACA,MAAM,EAAE,KAAK,SAAS;CAEtB,MAAM,aAAa,IAAI,gBAAgB;CACvC,MAAM,YAAY,iBACV,WAAW,MAAM,IAAI,aAAa,kCAAkC,cAAc,CAAC,GACzF,0BACF;CACA,MAAM,iBAAiB,SAAS;CAChC,MAAM,wBAAwB,WAAW,MAAM,gBAAgB,MAAM;CACrE,IAAI,gBAAgB;EAClB,IAAI,eAAe,SACjB,WAAW,MAAM,eAAe,MAAM;OAEtC,eAAe,iBAAiB,SAAS,iBAAiB,EAAE,MAAM,KAAK,CAAC;CAE5E;CAEA,IAAI;EAQF,OAAO;GAAE,gBAAgB;GAAM,aAAY,MAPpB,MAAM,KAAK;IAChC,QAAQ;IACR,SAAS,EAAE,gBAAgB,mBAAmB;IAC9C,MAAM,KAAK,UAAU,IAAI;IACzB,QAAQ,WAAW;GACrB,CAAC,EAAA,CAEmD,KAAK,CAAC,IAAI,CAAC,0BAA0B;EAAE;CAC7F,UAAU;EACR,aAAa,SAAS;EACtB,gBAAgB,oBAAoB,SAAS,eAAe;CAC9D;AACF"}
|
|
@@ -40,11 +40,12 @@ const PaymentFragment = gql`
|
|
|
40
40
|
amount
|
|
41
41
|
currencyCode
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
totalAmount {
|
|
44
44
|
amount
|
|
45
45
|
currencyCode
|
|
46
46
|
}
|
|
47
47
|
interestRate
|
|
48
|
+
interestFree
|
|
48
49
|
}
|
|
49
50
|
installmentOptions {
|
|
50
51
|
__typename
|
|
@@ -61,17 +62,19 @@ const PaymentFragment = gql`
|
|
|
61
62
|
amount
|
|
62
63
|
currencyCode
|
|
63
64
|
}
|
|
64
|
-
|
|
65
|
+
totalAmount {
|
|
65
66
|
amount
|
|
66
67
|
currencyCode
|
|
67
68
|
}
|
|
68
69
|
interestRate
|
|
70
|
+
interestFree
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
73
|
methods {
|
|
72
74
|
__typename
|
|
73
75
|
id
|
|
74
76
|
name
|
|
77
|
+
type
|
|
75
78
|
groupName
|
|
76
79
|
validator {
|
|
77
80
|
__typename
|
|
@@ -137,9 +140,11 @@ const PAYMENT_METHOD_SELECT = gql`
|
|
|
137
140
|
${PaymentBillingAddressFragment}
|
|
138
141
|
`;
|
|
139
142
|
/**
|
|
140
|
-
* Selects `cart { ...CartFragment }` alongside `payment` so the
|
|
141
|
-
*
|
|
142
|
-
* selects the cart.
|
|
143
|
+
* Selects `cart { ...CartFragment }` alongside `payment` so the cart total the server returns lands in
|
|
144
|
+
* the normalized cache in the same round trip (REQ-03) — the same reason `shippingOptionSelect`
|
|
145
|
+
* selects the cart. That total is the server's: the greater of the order form's value and the selected
|
|
146
|
+
* payment's value (`mapOrderFormToGqlCart`), so it carries the picked tier's interest. Dropping `total`
|
|
147
|
+
* from the selection leaves the total on screen at its previous value.
|
|
143
148
|
*/
|
|
144
149
|
const PAYMENT_INSTALLMENT_SELECT = gql`
|
|
145
150
|
mutation PaymentInstallmentSelect($input: PaymentInstallmentSelectInput!) {
|
|
@@ -202,6 +207,7 @@ const PAYMENT_START_TRANSACTION = gql`
|
|
|
202
207
|
merchantName
|
|
203
208
|
gatewayUrl
|
|
204
209
|
orderGroup
|
|
210
|
+
orderNodeId
|
|
205
211
|
userProfileId
|
|
206
212
|
value {
|
|
207
213
|
amount
|
|
@@ -221,6 +227,8 @@ const PAYMENT_GATEWAY_CALLBACK = gql`
|
|
|
221
227
|
paymentGatewayCallback(input: $input) {
|
|
222
228
|
__typename
|
|
223
229
|
orderId
|
|
230
|
+
orderNodeId
|
|
231
|
+
redirectUrl
|
|
224
232
|
userErrors {
|
|
225
233
|
code
|
|
226
234
|
message
|
|
@@ -228,7 +236,21 @@ const PAYMENT_GATEWAY_CALLBACK = gql`
|
|
|
228
236
|
}
|
|
229
237
|
}
|
|
230
238
|
`;
|
|
239
|
+
/** Only what `emitPurchaseForOrder` needs: the order's reference, its status, and what it costs. */
|
|
240
|
+
const SETTLED_ORDER_FACTS = gql`
|
|
241
|
+
query SettledOrderFacts($id: ID!) {
|
|
242
|
+
order(id: $id) {
|
|
243
|
+
id
|
|
244
|
+
reference
|
|
245
|
+
status
|
|
246
|
+
total {
|
|
247
|
+
amount
|
|
248
|
+
currencyCode
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
`;
|
|
231
253
|
//#endregion
|
|
232
|
-
export { PAYMENT_BILLING_ADDRESS_UPDATE, PAYMENT_GATEWAY_CALLBACK, PAYMENT_INSTALLMENT_SELECT, PAYMENT_METHOD_SELECT, PAYMENT_NETWORK_ERROR_CODE, PAYMENT_START_TRANSACTION, PaymentBillingAddressFragment, PaymentFragment };
|
|
254
|
+
export { PAYMENT_BILLING_ADDRESS_UPDATE, PAYMENT_GATEWAY_CALLBACK, PAYMENT_INSTALLMENT_SELECT, PAYMENT_METHOD_SELECT, PAYMENT_NETWORK_ERROR_CODE, PAYMENT_START_TRANSACTION, PaymentBillingAddressFragment, PaymentFragment, SETTLED_ORDER_FACTS };
|
|
233
255
|
|
|
234
256
|
//# sourceMappingURL=operations.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operations.js","names":[],"sources":["../../../src/sdk/payment/operations.ts"],"sourcesContent":["import type { TypedDocumentNode } from '@apollo/client';\nimport { type CartEntity, CartFragment } from '../cart/operations.js';\nimport { gql } from '../graphql-tag.js';\n\n/**\n * Payment write-model GraphQL operations (specs/payment-action-namespace.md, T4) — the five mutations\n * `api-module:PaymentWriteModel` shipped (T3, `checkout/schema.graphql` § 11). Nothing here is\n * invented: every mutation, input field, and payload field is pinned to that schema.\n *\n * Authored with the hand `gql` tag — the codegen `graphql()` factory is reserved for the reactive read\n * path — and excluded from the codegen `documents` glob (see `codegen.ts`): the `gql` tag alone is\n * still plucked by `graphql-codegen`, which would duplicate these hand-written types in\n * `sdk/generated/`. Mirrors `shipping/option-select-operations.ts` and `identity/operations.ts`.\n *\n * No field at any nesting depth carries a card number or CVV — the read model guarantees it (RFC D13),\n * and the one call that does carry them never touches GraphQL at all (see `gateway.ts`).\n *\n * Failures follow ADR-00007's two-path contract: domain outcomes arrive as payload `userErrors`;\n * transport and top-level GraphQL failures are infrastructure, collapsed by the handlers in\n * `use-payment-actions.ts` to the one client-synthesized code, `PAYMENT_NETWORK_ERROR_CODE`.\n */\n\n// ---------------------------------------------------------------------------\n// Domain types — mirror the shipped checkout schema\n// ---------------------------------------------------------------------------\n\n/** A payment domain error. Every mutation's error type is `{ code, message }`; codes stay strings. */\nexport type PaymentUserError = Readonly<{ code: string; message: string }>;\n\n/**\n * The one client-synthesized error code, carried by the userError every mutation-backed method\n * *resolves* (never throws) when its mutation fails at the transport or top-level-GraphQL layer.\n * `gateway.ts` owns the separate code for a failed gateway leg (Decision 7).\n */\nexport const PAYMENT_NETWORK_ERROR_CODE = 'NETWORK_ERROR';\n\nexport type PaymentMoney = Readonly<{ __typename?: 'Money'; amount: number; currencyCode: string }>;\n\ntype PaymentInstallmentEntity = Readonly<{\n __typename?: 'PaymentInstallment';\n count: number;\n value: PaymentMoney;\n total: PaymentMoney;\n interestRate: number;\n}>;\n\ntype PaymentMethodValidatorEntity = Readonly<{\n __typename?: 'PaymentMethodValidator';\n numberMask: string | null;\n numberRegex: string | null;\n cvvMask: string | null;\n cvvRegex: string | null;\n cvvRequired: boolean;\n expirationRequired: boolean;\n holderNameRequired: boolean;\n billingAddressRequired: boolean;\n documentDisplayable: boolean;\n documentRequired: boolean;\n}>;\n\ntype PaymentCountryDocumentEntity = Readonly<{\n __typename?: 'PaymentCountryDocument';\n type: string;\n displayable: boolean;\n required: boolean;\n}>;\n\ntype PaymentCountryEntity = Readonly<{\n __typename?: 'PaymentCountry';\n countryCode: string;\n documents: readonly PaymentCountryDocumentEntity[];\n}>;\n\ntype PaymentMethodEntity = Readonly<{\n __typename?: 'PaymentMethod';\n id: string;\n name: string;\n groupName: string;\n validator: PaymentMethodValidatorEntity;\n}>;\n\ntype PaymentInstallmentOptionEntity = Readonly<{\n __typename?: 'PaymentInstallmentOption';\n methodId: string;\n bin: string | null;\n baseValue: PaymentMoney;\n installments: readonly PaymentInstallmentEntity[];\n}>;\n\ntype SavedCardEntity = Readonly<{\n __typename?: 'SavedCard';\n id: string;\n accountId: string;\n methodId: string;\n network: string;\n lastFourDigits: string;\n bin: string;\n label: string | null;\n expired: boolean;\n cvvRequired: boolean;\n}>;\n\n/** The full `Payment` entity every selection mutation echoes back. Never carries a PAN or CVV. */\nexport type PaymentEntity = Readonly<{\n __typename?: 'Payment';\n id: string;\n methods: readonly PaymentMethodEntity[];\n selectedMethodId: string | null;\n selectedAccountId: string | null;\n selectedBin: string | null;\n storeCountryCode: string | null;\n countries: readonly PaymentCountryEntity[];\n selectedInstallment: PaymentInstallmentEntity | null;\n installmentOptions: readonly PaymentInstallmentOptionEntity[];\n savedCards: readonly SavedCardEntity[];\n}>;\n\n/** The billing-address echo on the selection payloads — the schema's shared `Address` type. */\nexport type PaymentBillingAddressEntity = Readonly<{\n __typename?: 'Address';\n id: string | null;\n recipientName: string | null;\n postalCode: string | null;\n country: string;\n street: string | null;\n number: string | null;\n complement: string | null;\n neighborhood: string | null;\n city: string | null;\n state: string | null;\n}>;\n\n// ---------------------------------------------------------------------------\n// Mutation inputs — mirror the schema's input types verbatim\n// ---------------------------------------------------------------------------\n\n/** A `bin` with an `accountId` is validated against the saved card's own BIN (MISMATCHED_BIN_FOR_SAVED_CARD). */\nexport type PaymentMethodSelectInput = Readonly<{ methodId: string; bin?: string; accountId?: string }>;\n\nexport type PaymentInstallmentSelectInput = Readonly<{ count: number }>;\n\n/** With `sameAsDelivery: true` every other field is ignored and the server performs no upstream write. */\nexport type PaymentBillingAddressUpdateInput = Readonly<{\n sameAsDelivery: boolean;\n addressType?: string;\n postalCode?: string;\n country?: string;\n street?: string;\n number?: string;\n complement?: string;\n neighborhood?: string;\n city?: string;\n state?: string;\n reference?: string;\n}>;\n\nexport type PaymentGatewayCallbackInput = Readonly<{ orderGroup: string }>;\n\n// ---------------------------------------------------------------------------\n// Shared fragment\n// ---------------------------------------------------------------------------\n\n/**\n * The canonical `Payment` selection set, spread by every selection mutation so one response\n * re-normalizes the whole entity and every reactive subscriber re-renders with no refetch — the\n * payload-completeness invariant writes must honor (ADR-0010). Selecting the entire entity mirrors\n * `CartFragment` and `SHIPPING_ADDRESS_UPDATE`'s full-`Shipping` selection: the method list (T5), the\n * validator config (T7), `installmentOptions` (T9), and the summary's selection fields (T6) are all\n * subscribed independently, and a partial selection would leave one of them incomplete.\n */\nexport const PaymentFragment = gql`\n fragment PaymentFragment on Payment {\n __typename\n id\n selectedMethodId\n selectedAccountId\n selectedBin\n storeCountryCode\n countries {\n __typename\n countryCode\n documents {\n __typename\n type\n displayable\n required\n }\n }\n selectedInstallment {\n __typename\n count\n value {\n amount\n currencyCode\n }\n total {\n amount\n currencyCode\n }\n interestRate\n }\n installmentOptions {\n __typename\n methodId\n bin\n baseValue {\n amount\n currencyCode\n }\n installments {\n __typename\n count\n value {\n amount\n currencyCode\n }\n total {\n amount\n currencyCode\n }\n interestRate\n }\n }\n methods {\n __typename\n id\n name\n groupName\n validator {\n __typename\n numberMask\n numberRegex\n cvvMask\n cvvRegex\n cvvRequired\n expirationRequired\n holderNameRequired\n billingAddressRequired\n documentDisplayable\n documentRequired\n }\n }\n savedCards {\n __typename\n id\n accountId\n methodId\n network\n lastFourDigits\n bin\n label\n expired\n cvvRequired\n }\n }\n`;\n\n/** The billing-address echo, spread by the three selection mutations that return one. */\nexport const PaymentBillingAddressFragment = gql`\n fragment PaymentBillingAddressFragment on Address {\n __typename\n id\n recipientName\n postalCode\n country\n street\n number\n complement\n neighborhood\n city\n state\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Mutation operations\n// ---------------------------------------------------------------------------\n\ntype PaymentMethodSelectMutation = Readonly<{\n paymentMethodSelect: Readonly<{\n __typename?: 'PaymentMethodSelectPayload';\n payment: PaymentEntity | null;\n billingAddress: PaymentBillingAddressEntity | null;\n userErrors: readonly PaymentUserError[];\n }>;\n}>;\n\nexport const PAYMENT_METHOD_SELECT: TypedDocumentNode<\n PaymentMethodSelectMutation,\n Readonly<{ input: PaymentMethodSelectInput }>\n> = gql`\n mutation PaymentMethodSelect($input: PaymentMethodSelectInput!) {\n paymentMethodSelect(input: $input) {\n __typename\n payment {\n ...PaymentFragment\n }\n billingAddress {\n ...PaymentBillingAddressFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n ${PaymentFragment}\n ${PaymentBillingAddressFragment}\n`;\n\ntype PaymentInstallmentSelectMutation = Readonly<{\n paymentInstallmentSelect: Readonly<{\n __typename?: 'PaymentInstallmentSelectPayload';\n payment: PaymentEntity | null;\n cart: CartEntity | null;\n billingAddress: PaymentBillingAddressEntity | null;\n userErrors: readonly PaymentUserError[];\n }>;\n}>;\n\n/**\n * Selects `cart { ...CartFragment }` alongside `payment` so the interest-inclusive order total lands\n * in the normalized cache in the same round trip (REQ-03) — the same reason `shippingOptionSelect`\n * selects the cart.\n */\nexport const PAYMENT_INSTALLMENT_SELECT: TypedDocumentNode<\n PaymentInstallmentSelectMutation,\n Readonly<{ input: PaymentInstallmentSelectInput }>\n> = gql`\n mutation PaymentInstallmentSelect($input: PaymentInstallmentSelectInput!) {\n paymentInstallmentSelect(input: $input) {\n __typename\n payment {\n ...PaymentFragment\n }\n cart {\n ...CartFragment\n }\n billingAddress {\n ...PaymentBillingAddressFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n ${PaymentFragment}\n ${PaymentBillingAddressFragment}\n ${CartFragment}\n`;\n\ntype PaymentBillingAddressUpdateMutation = Readonly<{\n paymentBillingAddressUpdate: Readonly<{\n __typename?: 'PaymentBillingAddressUpdatePayload';\n billingAddress: PaymentBillingAddressEntity | null;\n payment: PaymentEntity | null;\n userErrors: readonly PaymentUserError[];\n }>;\n}>;\n\nexport const PAYMENT_BILLING_ADDRESS_UPDATE: TypedDocumentNode<\n PaymentBillingAddressUpdateMutation,\n Readonly<{ input: PaymentBillingAddressUpdateInput }>\n> = gql`\n mutation PaymentBillingAddressUpdate($input: PaymentBillingAddressUpdateInput!) {\n paymentBillingAddressUpdate(input: $input) {\n __typename\n billingAddress {\n ...PaymentBillingAddressFragment\n }\n payment {\n ...PaymentFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n ${PaymentFragment}\n ${PaymentBillingAddressFragment}\n`;\n\ntype PaymentStartTransactionMutation = Readonly<{\n paymentStartTransaction: Readonly<{\n __typename?: 'PaymentStartTransactionPayload';\n transactionId: string | null;\n merchantName: string | null;\n gatewayUrl: string | null;\n orderGroup: string | null;\n userProfileId: string | null;\n value: PaymentMoney | null;\n valueInCents: number | null;\n referenceValueInCents: number | null;\n userErrors: readonly PaymentUserError[];\n }>;\n}>;\n\n/**\n * Takes no input: every field VTEX's transaction endpoint needs is derived server-side from the\n * already-persisted selection (T3 Decision 9). The transaction cookies it mints ride on the HTTP\n * response, not on the payload.\n *\n * `merchantName`, `gatewayUrl`, `valueInCents`, and `referenceValueInCents` are T18's additions\n * (specs/payment-transaction-payload-gaps.md), selected here by T13: `PaymentGatewayTransaction`\n * requires all four, so the descriptor cannot be assembled without them. `value` stays selected\n * beside the minor-unit pair — it is the display decimal, and the `purchase` analytics event reports\n * it rather than a client-side division.\n */\nexport const PAYMENT_START_TRANSACTION: TypedDocumentNode<PaymentStartTransactionMutation, Record<string, never>> = gql`\n mutation PaymentStartTransaction {\n paymentStartTransaction {\n __typename\n transactionId\n merchantName\n gatewayUrl\n orderGroup\n userProfileId\n value {\n amount\n currencyCode\n }\n valueInCents\n referenceValueInCents\n userErrors {\n code\n message\n }\n }\n }\n`;\n\ntype PaymentGatewayCallbackMutation = Readonly<{\n paymentGatewayCallback: Readonly<{\n __typename?: 'PaymentGatewayCallbackPayload';\n orderId: string | null;\n userErrors: readonly PaymentUserError[];\n }>;\n}>;\n\nexport const PAYMENT_GATEWAY_CALLBACK: TypedDocumentNode<\n PaymentGatewayCallbackMutation,\n Readonly<{ input: PaymentGatewayCallbackInput }>\n> = gql`\n mutation PaymentGatewayCallback($input: PaymentGatewayCallbackInput!) {\n paymentGatewayCallback(input: $input) {\n __typename\n orderId\n userErrors {\n code\n message\n }\n }\n }\n`;\n"],"mappings":";;;;;;;;AAkCA,MAAa,6BAA6B;;;;;;;;;AAwI1C,MAAa,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwFlC,MAAa,gCAAgC,GAAG;;;;;;;;;;;;;;;AA6BhD,MAAa,wBAGT,GAAG;;;;;;;;;;;;;;;;IAgBH,gBAAgB;IAChB,8BAA8B;;;;;;;AAkBlC,MAAa,6BAGT,GAAG;;;;;;;;;;;;;;;;;;;IAmBH,gBAAgB;IAChB,8BAA8B;IAC9B,aAAa;;AAYjB,MAAa,iCAGT,GAAG;;;;;;;;;;;;;;;;IAgBH,gBAAgB;IAChB,8BAA8B;;;;;;;;;;;;;AA6BlC,MAAa,4BAAuG,GAAG;;;;;;;;;;;;;;;;;;;;;;AA+BvH,MAAa,2BAGT,GAAG"}
|
|
1
|
+
{"version":3,"file":"operations.js","names":[],"sources":["../../../src/sdk/payment/operations.ts"],"sourcesContent":["import type { TypedDocumentNode } from '@apollo/client';\nimport { type CartEntity, CartFragment } from '../cart/operations.js';\nimport type { PaymentType } from '../generated/graphql.js';\nimport { gql } from '../graphql-tag.js';\n\n/**\n * Payment write-model GraphQL operations (specs/payment-action-namespace.md, T4) — the five mutations\n * `api-module:PaymentWriteModel` shipped (T3, `checkout/schema.graphql` § 11). Nothing here is\n * invented: every mutation, input field, and payload field is pinned to that schema.\n *\n * Authored with the hand `gql` tag — the codegen `graphql()` factory is reserved for the reactive read\n * path — and excluded from the codegen `documents` glob (see `codegen.ts`): the `gql` tag alone is\n * still plucked by `graphql-codegen`, which would duplicate these hand-written types in\n * `sdk/generated/`. Mirrors `shipping/option-select-operations.ts` and `identity/operations.ts`.\n *\n * No field at any nesting depth carries a card number or CVV — the read model guarantees it (RFC D13),\n * and the one call that does carry them never touches GraphQL at all (see `gateway.ts`).\n *\n * Failures follow ADR-00007's two-path contract: domain outcomes arrive as payload `userErrors`;\n * transport and top-level GraphQL failures are infrastructure, collapsed by the handlers in\n * `use-payment-actions.ts` to the one client-synthesized code, `PAYMENT_NETWORK_ERROR_CODE`.\n */\n\n// ---------------------------------------------------------------------------\n// Domain types — mirror the shipped checkout schema\n// ---------------------------------------------------------------------------\n\n/** A payment domain error. Every mutation's error type is `{ code, message }`; codes stay strings. */\nexport type PaymentUserError = Readonly<{ code: string; message: string }>;\n\n/**\n * The one client-synthesized error code, carried by the userError every mutation-backed method\n * *resolves* (never throws) when its mutation fails at the transport or top-level-GraphQL layer.\n * `gateway.ts` owns the separate code for a failed gateway leg (Decision 7).\n */\nexport const PAYMENT_NETWORK_ERROR_CODE = 'NETWORK_ERROR';\n\nexport type PaymentMoney = Readonly<{ __typename?: 'Money'; amount: number; currencyCode: string }>;\n\ntype PaymentInstallmentEntity = Readonly<{\n __typename?: 'PaymentInstallment';\n count: number;\n value: PaymentMoney;\n /** The whole-plan amount, interest included. */\n totalAmount: PaymentMoney;\n /** Hundredths of a percent. `null` when the order form did not say what the rate is. */\n interestRate: number | null;\n /** `null` exactly when `interestRate` is `null`. */\n interestFree: boolean | null;\n}>;\n\ntype PaymentMethodValidatorEntity = Readonly<{\n __typename?: 'PaymentMethodValidator';\n numberMask: string | null;\n numberRegex: string | null;\n cvvMask: string | null;\n cvvRegex: string | null;\n cvvRequired: boolean;\n expirationRequired: boolean;\n holderNameRequired: boolean;\n billingAddressRequired: boolean;\n documentDisplayable: boolean;\n documentRequired: boolean;\n}>;\n\ntype PaymentCountryDocumentEntity = Readonly<{\n __typename?: 'PaymentCountryDocument';\n type: string;\n displayable: boolean;\n required: boolean;\n}>;\n\ntype PaymentCountryEntity = Readonly<{\n __typename?: 'PaymentCountry';\n countryCode: string;\n documents: readonly PaymentCountryDocumentEntity[];\n}>;\n\ntype PaymentMethodEntity = Readonly<{\n __typename?: 'PaymentMethod';\n id: string;\n name: string;\n /** The vendor-neutral kind. Classify a method with this; `groupName` is the gateway transport value. */\n type: PaymentType;\n groupName: string;\n validator: PaymentMethodValidatorEntity;\n}>;\n\ntype PaymentInstallmentOptionEntity = Readonly<{\n __typename?: 'PaymentInstallmentOption';\n methodId: string;\n bin: string | null;\n baseValue: PaymentMoney;\n installments: readonly PaymentInstallmentEntity[];\n}>;\n\ntype SavedCardEntity = Readonly<{\n __typename?: 'SavedCard';\n id: string;\n accountId: string;\n methodId: string;\n network: string;\n lastFourDigits: string;\n bin: string;\n label: string | null;\n expired: boolean;\n cvvRequired: boolean;\n}>;\n\n/** The full `Payment` entity every selection mutation echoes back. Never carries a PAN or CVV. */\nexport type PaymentEntity = Readonly<{\n __typename?: 'Payment';\n id: string;\n methods: readonly PaymentMethodEntity[];\n selectedMethodId: string | null;\n selectedAccountId: string | null;\n selectedBin: string | null;\n storeCountryCode: string | null;\n countries: readonly PaymentCountryEntity[];\n selectedInstallment: PaymentInstallmentEntity | null;\n installmentOptions: readonly PaymentInstallmentOptionEntity[];\n savedCards: readonly SavedCardEntity[];\n}>;\n\n/** The billing-address echo on the selection payloads — the schema's shared `Address` type. */\nexport type PaymentBillingAddressEntity = Readonly<{\n __typename?: 'Address';\n id: string | null;\n recipientName: string | null;\n postalCode: string | null;\n country: string;\n street: string | null;\n number: string | null;\n complement: string | null;\n neighborhood: string | null;\n city: string | null;\n state: string | null;\n}>;\n\n// ---------------------------------------------------------------------------\n// Mutation inputs — mirror the schema's input types verbatim\n// ---------------------------------------------------------------------------\n\n/** A `bin` with an `accountId` is validated against the saved card's own BIN (MISMATCHED_BIN_FOR_SAVED_CARD). */\nexport type PaymentMethodSelectInput = Readonly<{ methodId: string; bin?: string; accountId?: string }>;\n\nexport type PaymentInstallmentSelectInput = Readonly<{ count: number }>;\n\n/** With `sameAsDelivery: true` every other field is ignored and the server performs no upstream write. */\nexport type PaymentBillingAddressUpdateInput = Readonly<{\n sameAsDelivery: boolean;\n addressType?: string;\n postalCode?: string;\n country?: string;\n street?: string;\n number?: string;\n complement?: string;\n neighborhood?: string;\n city?: string;\n state?: string;\n reference?: string;\n}>;\n\nexport type PaymentGatewayCallbackInput = Readonly<{ orderGroup: string; acceptChallenges?: boolean }>;\n\n// ---------------------------------------------------------------------------\n// Shared fragment\n// ---------------------------------------------------------------------------\n\n/**\n * The canonical `Payment` selection set, spread by every selection mutation so one response\n * re-normalizes the whole entity and every reactive subscriber re-renders with no refetch — the\n * payload-completeness invariant writes must honor (ADR-0010). Selecting the entire entity mirrors\n * `CartFragment` and `SHIPPING_ADDRESS_UPDATE`'s full-`Shipping` selection: the method list (T5), the\n * validator config (T7), `installmentOptions` (T9), and the summary's selection fields (T6) are all\n * subscribed independently, and a partial selection would leave one of them incomplete.\n */\nexport const PaymentFragment = gql`\n fragment PaymentFragment on Payment {\n __typename\n id\n selectedMethodId\n selectedAccountId\n selectedBin\n storeCountryCode\n countries {\n __typename\n countryCode\n documents {\n __typename\n type\n displayable\n required\n }\n }\n selectedInstallment {\n __typename\n count\n value {\n amount\n currencyCode\n }\n totalAmount {\n amount\n currencyCode\n }\n interestRate\n interestFree\n }\n installmentOptions {\n __typename\n methodId\n bin\n baseValue {\n amount\n currencyCode\n }\n installments {\n __typename\n count\n value {\n amount\n currencyCode\n }\n totalAmount {\n amount\n currencyCode\n }\n interestRate\n interestFree\n }\n }\n methods {\n __typename\n id\n name\n type\n groupName\n validator {\n __typename\n numberMask\n numberRegex\n cvvMask\n cvvRegex\n cvvRequired\n expirationRequired\n holderNameRequired\n billingAddressRequired\n documentDisplayable\n documentRequired\n }\n }\n savedCards {\n __typename\n id\n accountId\n methodId\n network\n lastFourDigits\n bin\n label\n expired\n cvvRequired\n }\n }\n`;\n\n/** The billing-address echo, spread by the three selection mutations that return one. */\nexport const PaymentBillingAddressFragment = gql`\n fragment PaymentBillingAddressFragment on Address {\n __typename\n id\n recipientName\n postalCode\n country\n street\n number\n complement\n neighborhood\n city\n state\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Mutation operations\n// ---------------------------------------------------------------------------\n\ntype PaymentMethodSelectMutation = Readonly<{\n paymentMethodSelect: Readonly<{\n __typename?: 'PaymentMethodSelectPayload';\n payment: PaymentEntity | null;\n billingAddress: PaymentBillingAddressEntity | null;\n userErrors: readonly PaymentUserError[];\n }>;\n}>;\n\nexport const PAYMENT_METHOD_SELECT: TypedDocumentNode<\n PaymentMethodSelectMutation,\n Readonly<{ input: PaymentMethodSelectInput }>\n> = gql`\n mutation PaymentMethodSelect($input: PaymentMethodSelectInput!) {\n paymentMethodSelect(input: $input) {\n __typename\n payment {\n ...PaymentFragment\n }\n billingAddress {\n ...PaymentBillingAddressFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n ${PaymentFragment}\n ${PaymentBillingAddressFragment}\n`;\n\ntype PaymentInstallmentSelectMutation = Readonly<{\n paymentInstallmentSelect: Readonly<{\n __typename?: 'PaymentInstallmentSelectPayload';\n payment: PaymentEntity | null;\n cart: CartEntity | null;\n billingAddress: PaymentBillingAddressEntity | null;\n userErrors: readonly PaymentUserError[];\n }>;\n}>;\n\n/**\n * Selects `cart { ...CartFragment }` alongside `payment` so the cart total the server returns lands in\n * the normalized cache in the same round trip (REQ-03) — the same reason `shippingOptionSelect`\n * selects the cart. That total is the server's: the greater of the order form's value and the selected\n * payment's value (`mapOrderFormToGqlCart`), so it carries the picked tier's interest. Dropping `total`\n * from the selection leaves the total on screen at its previous value.\n */\nexport const PAYMENT_INSTALLMENT_SELECT: TypedDocumentNode<\n PaymentInstallmentSelectMutation,\n Readonly<{ input: PaymentInstallmentSelectInput }>\n> = gql`\n mutation PaymentInstallmentSelect($input: PaymentInstallmentSelectInput!) {\n paymentInstallmentSelect(input: $input) {\n __typename\n payment {\n ...PaymentFragment\n }\n cart {\n ...CartFragment\n }\n billingAddress {\n ...PaymentBillingAddressFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n ${PaymentFragment}\n ${PaymentBillingAddressFragment}\n ${CartFragment}\n`;\n\ntype PaymentBillingAddressUpdateMutation = Readonly<{\n paymentBillingAddressUpdate: Readonly<{\n __typename?: 'PaymentBillingAddressUpdatePayload';\n billingAddress: PaymentBillingAddressEntity | null;\n payment: PaymentEntity | null;\n userErrors: readonly PaymentUserError[];\n }>;\n}>;\n\nexport const PAYMENT_BILLING_ADDRESS_UPDATE: TypedDocumentNode<\n PaymentBillingAddressUpdateMutation,\n Readonly<{ input: PaymentBillingAddressUpdateInput }>\n> = gql`\n mutation PaymentBillingAddressUpdate($input: PaymentBillingAddressUpdateInput!) {\n paymentBillingAddressUpdate(input: $input) {\n __typename\n billingAddress {\n ...PaymentBillingAddressFragment\n }\n payment {\n ...PaymentFragment\n }\n userErrors {\n code\n message\n }\n }\n }\n ${PaymentFragment}\n ${PaymentBillingAddressFragment}\n`;\n\ntype PaymentStartTransactionMutation = Readonly<{\n paymentStartTransaction: Readonly<{\n __typename?: 'PaymentStartTransactionPayload';\n transactionId: string | null;\n merchantName: string | null;\n gatewayUrl: string | null;\n orderGroup: string | null;\n orderNodeId: string | null;\n userProfileId: string | null;\n value: PaymentMoney | null;\n valueInCents: number | null;\n referenceValueInCents: number | null;\n userErrors: readonly PaymentUserError[];\n }>;\n}>;\n\n/**\n * Takes no input: every field VTEX's transaction endpoint needs is derived server-side from the\n * already-persisted selection (T3 Decision 9). The transaction cookies it mints ride on the HTTP\n * response, not on the payload.\n *\n * `merchantName`, `gatewayUrl`, `valueInCents`, and `referenceValueInCents` are T18's additions\n * (specs/payment-transaction-payload-gaps.md), selected here by T13: `PaymentGatewayTransaction`\n * requires all four, so the descriptor cannot be assembled without them. `value` stays selected\n * beside the minor-unit pair — it is the display decimal, and the `purchase` analytics event reports\n * it rather than a client-side division.\n */\nexport const PAYMENT_START_TRANSACTION: TypedDocumentNode<PaymentStartTransactionMutation, Record<string, never>> = gql`\n mutation PaymentStartTransaction {\n paymentStartTransaction {\n __typename\n transactionId\n merchantName\n gatewayUrl\n orderGroup\n orderNodeId\n userProfileId\n value {\n amount\n currencyCode\n }\n valueInCents\n referenceValueInCents\n userErrors {\n code\n message\n }\n }\n }\n`;\n\ntype PaymentGatewayCallbackMutation = Readonly<{\n paymentGatewayCallback: Readonly<{\n __typename?: 'PaymentGatewayCallbackPayload';\n orderId: string | null;\n orderNodeId: string | null;\n redirectUrl: string | null;\n userErrors: readonly PaymentUserError[];\n }>;\n}>;\n\nexport const PAYMENT_GATEWAY_CALLBACK: TypedDocumentNode<\n PaymentGatewayCallbackMutation,\n Readonly<{ input: PaymentGatewayCallbackInput }>\n> = gql`\n mutation PaymentGatewayCallback($input: PaymentGatewayCallbackInput!) {\n paymentGatewayCallback(input: $input) {\n __typename\n orderId\n orderNodeId\n redirectUrl\n userErrors {\n code\n message\n }\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// The settled-order read the resume paths report a purchase from (T208)\n// ---------------------------------------------------------------------------\n\ntype SettledOrderQuery = Readonly<{\n order: Readonly<{\n __typename?: 'Order';\n id: string;\n reference: string;\n status: 'APPROVED' | 'PENDING' | 'DENIED' | 'UNKNOWN';\n total: Readonly<{ __typename?: 'Money'; amount: string; currencyCode: string }> | null;\n }> | null;\n}>;\n\n/** Only what `emitPurchaseForOrder` needs: the order's reference, its status, and what it costs. */\nexport const SETTLED_ORDER_FACTS: TypedDocumentNode<SettledOrderQuery, Readonly<{ id: string }>> = gql`\n query SettledOrderFacts($id: ID!) {\n order(id: $id) {\n id\n reference\n status\n total {\n amount\n currencyCode\n }\n }\n }\n`;\n"],"mappings":";;;;;;;;AAmCA,MAAa,6BAA6B;;;;;;;;;AA8I1C,MAAa,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2FlC,MAAa,gCAAgC,GAAG;;;;;;;;;;;;;;;AA6BhD,MAAa,wBAGT,GAAG;;;;;;;;;;;;;;;;IAgBH,gBAAgB;IAChB,8BAA8B;;;;;;;;;AAoBlC,MAAa,6BAGT,GAAG;;;;;;;;;;;;;;;;;;;IAmBH,gBAAgB;IAChB,8BAA8B;IAC9B,aAAa;;AAYjB,MAAa,iCAGT,GAAG;;;;;;;;;;;;;;;;IAgBH,gBAAgB;IAChB,8BAA8B;;;;;;;;;;;;;AA8BlC,MAAa,4BAAuG,GAAG;;;;;;;;;;;;;;;;;;;;;;;AAkCvH,MAAa,2BAGT,GAAG;;;;;;;;;;;;;;;AA8BP,MAAa,sBAAsF,GAAG"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "../analytics/use-analytics-actions.js";
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { loggableErrorName } from "../../utils/loggable-error-name.js";
|
|
3
|
+
import { SETTLED_ORDER_FACTS } from "./operations.js";
|
|
4
|
+
import { getApolloContext } from "@apollo/client/react";
|
|
5
|
+
import { useCallback, useContext } from "react";
|
|
6
|
+
//#region src/sdk/payment/resumed-purchase.ts
|
|
7
|
+
/**
|
|
8
|
+
* The `purchase` analytics event for an order that was finished on a second call: the in-page resume
|
|
9
|
+
* that `usePlaceOrder` runs, and the return page the bank sends the shopper to (T208, RFC D23).
|
|
10
|
+
*
|
|
11
|
+
* The attempt record may carry no value or currency (T29 FR-7), and a resume holds no transaction, so
|
|
12
|
+
* both are read from the settled order. Every `purchase` in this package goes through `claimPurchase`,
|
|
13
|
+
* so an order is reported once whichever path finished it.
|
|
14
|
+
*/
|
|
15
|
+
const PURCHASE_MARKER_PREFIX = "fs:purchase-sent:";
|
|
16
|
+
/** Only used when storage cannot be read or written: one page lifetime still reports an order once. */
|
|
17
|
+
const claimedInMemory = /* @__PURE__ */ new Set();
|
|
18
|
+
/**
|
|
19
|
+
* A synchronous check-and-set on `localStorage`, keyed on the order group and holding `1`, never a value, a
|
|
20
|
+
* currency or anything about the shopper. The key is written before the caller emits, so an emission that
|
|
21
|
+
* throws is still never repeated. Two browsers may each report one order once (ruled 2026-10-14); the marker
|
|
22
|
+
* never expires and no server holds it.
|
|
23
|
+
*/
|
|
24
|
+
function claimPurchase(orderGroup) {
|
|
25
|
+
const key = `${PURCHASE_MARKER_PREFIX}${orderGroup}`;
|
|
26
|
+
try {
|
|
27
|
+
if (localStorage.getItem(key) !== null) return false;
|
|
28
|
+
localStorage.setItem(key, "1");
|
|
29
|
+
return true;
|
|
30
|
+
} catch {
|
|
31
|
+
if (claimedInMemory.has(key)) return false;
|
|
32
|
+
claimedInMemory.add(key);
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The budget for the settled-order read: a hung read must not hold the shopper on a blank return page. A
|
|
38
|
+
* local race with a cleared timer, mirroring the API's own ten-second default. On expiry nothing is
|
|
39
|
+
* reported and the flow goes on.
|
|
40
|
+
*/
|
|
41
|
+
const SETTLED_ORDER_READ_TIMEOUT_MS = 1e4;
|
|
42
|
+
const ABANDONED = Symbol("abandoned");
|
|
43
|
+
async function readWithinBudget(readOrder, orderNodeId) {
|
|
44
|
+
let timer;
|
|
45
|
+
const deadline = new Promise((resolve) => {
|
|
46
|
+
timer = setTimeout(() => resolve(ABANDONED), SETTLED_ORDER_READ_TIMEOUT_MS);
|
|
47
|
+
});
|
|
48
|
+
try {
|
|
49
|
+
return await Promise.race([readOrder(orderNodeId), deadline]);
|
|
50
|
+
} finally {
|
|
51
|
+
clearTimeout(timer);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Reads a settled order for the resume paths. `null` without an Apollo client, so a story or a test rendered
|
|
56
|
+
* with no provider has no read and reports no purchase; the client is taken from Apollo's context rather than
|
|
57
|
+
* `useApolloClient()`, which throws without one.
|
|
58
|
+
*/
|
|
59
|
+
function useSettledOrderRead() {
|
|
60
|
+
const { client } = useContext(getApolloContext());
|
|
61
|
+
const read = useCallback(async (orderNodeId) => {
|
|
62
|
+
if (!client) return null;
|
|
63
|
+
const { data } = await client.query({
|
|
64
|
+
query: SETTLED_ORDER_FACTS,
|
|
65
|
+
variables: { id: orderNodeId },
|
|
66
|
+
fetchPolicy: "network-only"
|
|
67
|
+
});
|
|
68
|
+
const order = data?.order;
|
|
69
|
+
if (!order) return null;
|
|
70
|
+
const { reference, status, total } = order;
|
|
71
|
+
return {
|
|
72
|
+
reference,
|
|
73
|
+
status,
|
|
74
|
+
total: total ? {
|
|
75
|
+
amount: total.amount,
|
|
76
|
+
currencyCode: total.currencyCode
|
|
77
|
+
} : null
|
|
78
|
+
};
|
|
79
|
+
}, [client]);
|
|
80
|
+
return client ? read : null;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Reports the purchase of an order a resume just finished. Reads the order (under the budget), then emits
|
|
84
|
+
* unless it is `DENIED` or carries no total: an order that failed is not a sale, and one without a total has
|
|
85
|
+
* no value to report. `PENDING` (anti-fraud review) and `UNKNOWN` report, as the direct path reports a
|
|
86
|
+
* placed order without reading its status. The marker is claimed only after those checks, so an order that
|
|
87
|
+
* was not reported is not burned.
|
|
88
|
+
*
|
|
89
|
+
* Never rejects and never throws: the order is finished whether or not the event could be sent, so a read
|
|
90
|
+
* that fails, a tracker that throws or storage that refuses must not change what the shopper sees.
|
|
91
|
+
*/
|
|
92
|
+
async function emitPurchaseForOrder({ track, readOrder, orderNodeId }) {
|
|
93
|
+
if (!track || !readOrder) return;
|
|
94
|
+
let facts;
|
|
95
|
+
try {
|
|
96
|
+
facts = await readWithinBudget(readOrder, orderNodeId);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error("[payment.purchase] the settled order could not be read", loggableErrorName(error));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (facts === ABANDONED || facts === null || facts.status === "DENIED" || facts.total === null) return;
|
|
102
|
+
if (!claimPurchase(facts.reference)) return;
|
|
103
|
+
try {
|
|
104
|
+
track({
|
|
105
|
+
name: "purchase",
|
|
106
|
+
transactionId: facts.reference,
|
|
107
|
+
value: {
|
|
108
|
+
amount: Number(facts.total.amount),
|
|
109
|
+
currencyCode: facts.total.currencyCode
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error("[payment.purchase] the purchase event could not be sent", loggableErrorName(error));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
export { SETTLED_ORDER_READ_TIMEOUT_MS, claimPurchase, emitPurchaseForOrder, useSettledOrderRead };
|
|
118
|
+
|
|
119
|
+
//# sourceMappingURL=resumed-purchase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resumed-purchase.js","names":[],"sources":["../../../src/sdk/payment/resumed-purchase.ts"],"sourcesContent":["'use client';\n\nimport { getApolloContext } from '@apollo/client/react';\nimport { useCallback, useContext } from 'react';\nimport { loggableErrorName } from '../../utils/loggable-error-name.js';\nimport type { AnalyticsActions } from '../analytics/use-analytics-actions.js';\nimport { SETTLED_ORDER_FACTS } from './operations.js';\n\n/**\n * The `purchase` analytics event for an order that was finished on a second call: the in-page resume\n * that `usePlaceOrder` runs, and the return page the bank sends the shopper to (T208, RFC D23).\n *\n * The attempt record may carry no value or currency (T29 FR-7), and a resume holds no transaction, so\n * both are read from the settled order. Every `purchase` in this package goes through `claimPurchase`,\n * so an order is reported once whichever path finished it.\n */\n\nconst PURCHASE_MARKER_PREFIX = 'fs:purchase-sent:';\n\n/** Only used when storage cannot be read or written: one page lifetime still reports an order once. */\nconst claimedInMemory = new Set<string>();\n\n/**\n * A synchronous check-and-set on `localStorage`, keyed on the order group and holding `1`, never a value, a\n * currency or anything about the shopper. The key is written before the caller emits, so an emission that\n * throws is still never repeated. Two browsers may each report one order once (ruled 2026-10-14); the marker\n * never expires and no server holds it.\n */\nexport function claimPurchase(orderGroup: string): boolean {\n const key = `${PURCHASE_MARKER_PREFIX}${orderGroup}`;\n try {\n if (localStorage.getItem(key) !== null) return false;\n localStorage.setItem(key, '1');\n return true;\n } catch {\n if (claimedInMemory.has(key)) return false;\n claimedInMemory.add(key);\n return true;\n }\n}\n\nexport type SettledOrderFacts = Readonly<{\n reference: string;\n status: 'APPROVED' | 'PENDING' | 'DENIED' | 'UNKNOWN';\n total: Readonly<{ amount: string; currencyCode: string }> | null;\n}>;\n\nexport type SettledOrderRead = (orderNodeId: string) => Promise<SettledOrderFacts | null>;\n\n/**\n * The budget for the settled-order read: a hung read must not hold the shopper on a blank return page. A\n * local race with a cleared timer, mirroring the API's own ten-second default. On expiry nothing is\n * reported and the flow goes on.\n */\nexport const SETTLED_ORDER_READ_TIMEOUT_MS = 10_000;\n\nconst ABANDONED = Symbol('abandoned');\n\nasync function readWithinBudget(\n readOrder: SettledOrderRead,\n orderNodeId: string,\n): Promise<SettledOrderFacts | null | typeof ABANDONED> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const deadline = new Promise<typeof ABANDONED>((resolve) => {\n timer = setTimeout(() => resolve(ABANDONED), SETTLED_ORDER_READ_TIMEOUT_MS);\n });\n try {\n return await Promise.race([readOrder(orderNodeId), deadline]);\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * Reads a settled order for the resume paths. `null` without an Apollo client, so a story or a test rendered\n * with no provider has no read and reports no purchase; the client is taken from Apollo's context rather than\n * `useApolloClient()`, which throws without one.\n */\nexport function useSettledOrderRead(): SettledOrderRead | null {\n const { client } = useContext(getApolloContext());\n\n const read = useCallback<SettledOrderRead>(\n async (orderNodeId) => {\n if (!client) return null;\n const { data } = await client.query({\n query: SETTLED_ORDER_FACTS,\n variables: { id: orderNodeId },\n fetchPolicy: 'network-only',\n });\n const order = data?.order;\n if (!order) return null;\n const { reference, status, total } = order;\n return {\n reference,\n status,\n total: total ? { amount: total.amount, currencyCode: total.currencyCode } : null,\n };\n },\n [client],\n );\n\n return client ? read : null;\n}\n\n/**\n * Reports the purchase of an order a resume just finished. Reads the order (under the budget), then emits\n * unless it is `DENIED` or carries no total: an order that failed is not a sale, and one without a total has\n * no value to report. `PENDING` (anti-fraud review) and `UNKNOWN` report, as the direct path reports a\n * placed order without reading its status. The marker is claimed only after those checks, so an order that\n * was not reported is not burned.\n *\n * Never rejects and never throws: the order is finished whether or not the event could be sent, so a read\n * that fails, a tracker that throws or storage that refuses must not change what the shopper sees.\n */\nexport async function emitPurchaseForOrder({\n track,\n readOrder,\n orderNodeId,\n}: {\n track: AnalyticsActions['track'] | null | undefined;\n readOrder: SettledOrderRead | null | undefined;\n orderNodeId: string;\n}): Promise<void> {\n if (!track || !readOrder) return;\n\n let facts: SettledOrderFacts | null | typeof ABANDONED;\n try {\n facts = await readWithinBudget(readOrder, orderNodeId);\n } catch (error) {\n console.error('[payment.purchase] the settled order could not be read', loggableErrorName(error));\n return;\n }\n if (facts === ABANDONED || facts === null || facts.status === 'DENIED' || facts.total === null) return;\n\n if (!claimPurchase(facts.reference)) return;\n try {\n track({\n name: 'purchase',\n transactionId: facts.reference,\n value: { amount: Number(facts.total.amount), currencyCode: facts.total.currencyCode },\n });\n } catch (error) {\n console.error('[payment.purchase] the purchase event could not be sent', loggableErrorName(error));\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAiBA,MAAM,yBAAyB;;AAG/B,MAAM,kCAAkB,IAAI,IAAY;;;;;;;AAQxC,SAAgB,cAAc,YAA6B;CACzD,MAAM,MAAM,GAAG,yBAAyB;CACxC,IAAI;EACF,IAAI,aAAa,QAAQ,GAAG,MAAM,MAAM,OAAO;EAC/C,aAAa,QAAQ,KAAK,GAAG;EAC7B,OAAO;CACT,QAAQ;EACN,IAAI,gBAAgB,IAAI,GAAG,GAAG,OAAO;EACrC,gBAAgB,IAAI,GAAG;EACvB,OAAO;CACT;AACF;;;;;;AAeA,MAAa,gCAAgC;AAE7C,MAAM,YAAY,OAAO,WAAW;AAEpC,eAAe,iBACb,WACA,aACsD;CACtD,IAAI;CACJ,MAAM,WAAW,IAAI,SAA2B,YAAY;EAC1D,QAAQ,iBAAiB,QAAQ,SAAS,GAAG,6BAA6B;CAC5E,CAAC;CACD,IAAI;EACF,OAAO,MAAM,QAAQ,KAAK,CAAC,UAAU,WAAW,GAAG,QAAQ,CAAC;CAC9D,UAAU;EACR,aAAa,KAAK;CACpB;AACF;;;;;;AAOA,SAAgB,sBAA+C;CAC7D,MAAM,EAAE,WAAW,WAAW,iBAAiB,CAAC;CAEhD,MAAM,OAAO,YACX,OAAO,gBAAgB;EACrB,IAAI,CAAC,QAAQ,OAAO;EACpB,MAAM,EAAE,SAAS,MAAM,OAAO,MAAM;GAClC,OAAO;GACP,WAAW,EAAE,IAAI,YAAY;GAC7B,aAAa;EACf,CAAC;EACD,MAAM,QAAQ,MAAM;EACpB,IAAI,CAAC,OAAO,OAAO;EACnB,MAAM,EAAE,WAAW,QAAQ,UAAU;EACrC,OAAO;GACL;GACA;GACA,OAAO,QAAQ;IAAE,QAAQ,MAAM;IAAQ,cAAc,MAAM;GAAa,IAAI;EAC9E;CACF,GACA,CAAC,MAAM,CACT;CAEA,OAAO,SAAS,OAAO;AACzB;;;;;;;;;;;AAYA,eAAsB,qBAAqB,EACzC,OACA,WACA,eAKgB;CAChB,IAAI,CAAC,SAAS,CAAC,WAAW;CAE1B,IAAI;CACJ,IAAI;EACF,QAAQ,MAAM,iBAAiB,WAAW,WAAW;CACvD,SAAS,OAAO;EACd,QAAQ,MAAM,0DAA0D,kBAAkB,KAAK,CAAC;EAChG;CACF;CACA,IAAI,UAAU,aAAa,UAAU,QAAQ,MAAM,WAAW,YAAY,MAAM,UAAU,MAAM;CAEhG,IAAI,CAAC,cAAc,MAAM,SAAS,GAAG;CACrC,IAAI;EACF,MAAM;GACJ,MAAM;GACN,eAAe,MAAM;GACrB,OAAO;IAAE,QAAQ,OAAO,MAAM,MAAM,MAAM;IAAG,cAAc,MAAM,MAAM;GAAa;EACtF,CAAC;CACH,SAAS,OAAO;EACd,QAAQ,MAAM,2DAA2D,kBAAkB,KAAK,CAAC;CACnG;AACF"}
|
|
@@ -39,33 +39,26 @@
|
|
|
39
39
|
*/
|
|
40
40
|
const SECURITY_CODE_MASK_STYLE = Object.freeze({ WebkitTextSecurity: "disc" });
|
|
41
41
|
/**
|
|
42
|
-
* Whether the security code is
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* the code.
|
|
42
|
+
* Whether the security code is obscured: while it is non-empty and its input does not have focus
|
|
43
|
+
* (specs/stop-sending-a-stale-non-displayable-cardholder.md, T34, reversing the T173 timing that
|
|
44
|
+
* obscured a code the instant its last digit arrived). A shopper who is still in the field can read
|
|
45
|
+
* back what they typed and catch a typo; the moment focus leaves, nothing is left legible. The
|
|
46
|
+
* mechanism is untouched; only its timing moves.
|
|
48
47
|
*
|
|
49
|
-
* `
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* own three-or-four-digit floor. Resolving it here would mean knowing about payment-method validators
|
|
53
|
-
* and saved-card rows, and would still leave the comparison itself free to drift between the two.
|
|
48
|
+
* `focused` is passed in already folded with whatever else disables the input: a browser fires no
|
|
49
|
+
* `blur` when a focused element becomes disabled, so each caller counts a disabled input as
|
|
50
|
+
* unfocused. This function knows nothing about `disabled`.
|
|
54
51
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* the
|
|
59
|
-
*
|
|
60
|
-
* Derived from the value on every render, with no state and no timer. That is what makes the render
|
|
61
|
-
* which first holds a complete code already the masked one — so a paste delivering every digit in one
|
|
62
|
-
* event opens no legible frame — lets a cleared field unmask with nothing to reset, and leaves a code
|
|
63
|
-
* the shopper backspaces into legible again while they correct it.
|
|
52
|
+
* Derived from the value and the focus flag on every render, never from an effect or a timer. That
|
|
53
|
+
* is what makes the render which first holds an unfocused code already the masked one — so a paste or
|
|
54
|
+
* a browser autofill delivering every digit in one event, and the blur itself, open no legible frame.
|
|
55
|
+
* The length of the code plays no part: the mask no longer needs a per-network or per-field length,
|
|
56
|
+
* and a partial code is hidden on blur like a complete one.
|
|
64
57
|
*/
|
|
65
|
-
function
|
|
66
|
-
return value.length > 0 &&
|
|
58
|
+
function isSecurityCodeMasked(value, focused) {
|
|
59
|
+
return value.length > 0 && !focused;
|
|
67
60
|
}
|
|
68
61
|
//#endregion
|
|
69
|
-
export { SECURITY_CODE_MASK_STYLE,
|
|
62
|
+
export { SECURITY_CODE_MASK_STYLE, isSecurityCodeMasked };
|
|
70
63
|
|
|
71
64
|
//# sourceMappingURL=security-code-mask.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security-code-mask.js","names":[],"sources":["../../../src/sdk/payment/security-code-mask.ts"],"sourcesContent":["import type { CSSProperties } from 'react';\n\n/**\n * The security-code masking mechanism (specs/choose-a-security-code-masking-mechanism.md, T101 of\n * the Credit Card epic; docs/features/credit-card/rfc.md § D13). One shared constant for the two\n * inputs that hold a security code — `CardEntry`'s `cvv` field and `SavedCardCvvField`'s CVV\n * re-entry field — so the two call sites cannot drift on the mechanism.\n *\n * Why this and not `type=\"password\"`: T70 shipped `type=\"password\"` on both fields and it made real\n * Chrome offer a \"Save password?\" prompt on the new-card form — pairing the expiration date as a\n * fake *Username* and the security code as *Password*, and offering to store it in Google Password\n * Manager, synced to the signed-in account. PCI-DSS forbids retaining the security code past\n * authorization, and a cloud-synced credential vault is a strictly worse posture than the field's\n * pre-T70 baseline. `type=\"password\"` was reverted; both fields ship `type=\"text\"`.\n *\n * `-webkit-text-security: disc` obscures the digits on screen without touching `type`, so the\n * heuristic that triggered the prompt (keyed off `type=\"password\"` fields in the form) never fires —\n * settled by the 2026-08-24 unblock ruling through measurement in real Chrome, not documentation.\n * Applied as an **element inline style**, never a stylesheet the package ships (forbidden by\n * ADR-0007's Consequences, `:104`) and never a class a store authors (defeatable by any compound\n * swatch) — an inline declaration is the one CSS route that is both within the architecture rule and\n * outside the store's reach, and it outranks every class-based rule at normal importance.\n *\n * This closes the password-manager capture surface structurally — it does not mean the code never\n * leaves the browser by any path. `CardEntry`'s CVV keeps `autoComplete=\"cc-csc\"` (FR-5, unchanged by\n * this task): the exact token Chrome's own Autofill uses to classify the field as\n * `CREDIT_CARD_VERIFICATION_CODE`, and Chrome ships CVC storage (`AutofillEnableCvcStorageAndFilling`)\n * that retains the CVC beside a saved server card and syncs it to the signed-in Google account. This\n * mechanism does not close that path and is not trying to — it is the separate *card* vault the field\n * honestly declares itself to via `autoComplete`, not the credential vault this change removes (spec\n * § Verification, G1(d)).\n *\n * `WebkitTextSecurity` is asserted `as CSSProperties` because csstype 3.2.3 declares no such key —\n * mirrors the existing assertion in `variant-selector.tsx`. The property is standardized nowhere;\n * supported in Chromium and WebKit (and Firefox 114+, per T70's spec). An engine without it renders\n * exactly what ships today — not a regression.\n *\n * Defined once at module scope so the object is referentially stable and React's style diffing never\n * has work to do on re-render.\n */\nexport const SECURITY_CODE_MASK_STYLE: CSSProperties = Object.freeze({ WebkitTextSecurity: 'disc' }) as CSSProperties;\n\n/**\n * Whether the security code is
|
|
1
|
+
{"version":3,"file":"security-code-mask.js","names":[],"sources":["../../../src/sdk/payment/security-code-mask.ts"],"sourcesContent":["import type { CSSProperties } from 'react';\n\n/**\n * The security-code masking mechanism (specs/choose-a-security-code-masking-mechanism.md, T101 of\n * the Credit Card epic; docs/features/credit-card/rfc.md § D13). One shared constant for the two\n * inputs that hold a security code — `CardEntry`'s `cvv` field and `SavedCardCvvField`'s CVV\n * re-entry field — so the two call sites cannot drift on the mechanism.\n *\n * Why this and not `type=\"password\"`: T70 shipped `type=\"password\"` on both fields and it made real\n * Chrome offer a \"Save password?\" prompt on the new-card form — pairing the expiration date as a\n * fake *Username* and the security code as *Password*, and offering to store it in Google Password\n * Manager, synced to the signed-in account. PCI-DSS forbids retaining the security code past\n * authorization, and a cloud-synced credential vault is a strictly worse posture than the field's\n * pre-T70 baseline. `type=\"password\"` was reverted; both fields ship `type=\"text\"`.\n *\n * `-webkit-text-security: disc` obscures the digits on screen without touching `type`, so the\n * heuristic that triggered the prompt (keyed off `type=\"password\"` fields in the form) never fires —\n * settled by the 2026-08-24 unblock ruling through measurement in real Chrome, not documentation.\n * Applied as an **element inline style**, never a stylesheet the package ships (forbidden by\n * ADR-0007's Consequences, `:104`) and never a class a store authors (defeatable by any compound\n * swatch) — an inline declaration is the one CSS route that is both within the architecture rule and\n * outside the store's reach, and it outranks every class-based rule at normal importance.\n *\n * This closes the password-manager capture surface structurally — it does not mean the code never\n * leaves the browser by any path. `CardEntry`'s CVV keeps `autoComplete=\"cc-csc\"` (FR-5, unchanged by\n * this task): the exact token Chrome's own Autofill uses to classify the field as\n * `CREDIT_CARD_VERIFICATION_CODE`, and Chrome ships CVC storage (`AutofillEnableCvcStorageAndFilling`)\n * that retains the CVC beside a saved server card and syncs it to the signed-in Google account. This\n * mechanism does not close that path and is not trying to — it is the separate *card* vault the field\n * honestly declares itself to via `autoComplete`, not the credential vault this change removes (spec\n * § Verification, G1(d)).\n *\n * `WebkitTextSecurity` is asserted `as CSSProperties` because csstype 3.2.3 declares no such key —\n * mirrors the existing assertion in `variant-selector.tsx`. The property is standardized nowhere;\n * supported in Chromium and WebKit (and Firefox 114+, per T70's spec). An engine without it renders\n * exactly what ships today — not a regression.\n *\n * Defined once at module scope so the object is referentially stable and React's style diffing never\n * has work to do on re-render.\n */\nexport const SECURITY_CODE_MASK_STYLE: CSSProperties = Object.freeze({ WebkitTextSecurity: 'disc' }) as CSSProperties;\n\n/**\n * Whether the security code is obscured: while it is non-empty and its input does not have focus\n * (specs/stop-sending-a-stale-non-displayable-cardholder.md, T34, reversing the T173 timing that\n * obscured a code the instant its last digit arrived). A shopper who is still in the field can read\n * back what they typed and catch a typo; the moment focus leaves, nothing is left legible. The\n * mechanism is untouched; only its timing moves.\n *\n * `focused` is passed in already folded with whatever else disables the input: a browser fires no\n * `blur` when a focused element becomes disabled, so each caller counts a disabled input as\n * unfocused. This function knows nothing about `disabled`.\n *\n * Derived from the value and the focus flag on every render, never from an effect or a timer. That\n * is what makes the render which first holds an unfocused code already the masked one — so a paste or\n * a browser autofill delivering every digit in one event, and the blur itself, open no legible frame.\n * The length of the code plays no part: the mask no longer needs a per-network or per-field length,\n * and a partial code is hidden on blur like a complete one.\n */\nexport function isSecurityCodeMasked(value: string, focused: boolean): boolean {\n return value.length > 0 && !focused;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,MAAa,2BAA0C,OAAO,OAAO,EAAE,oBAAoB,OAAO,CAAC;;;;;;;;;;;;;;;;;;AAmBnG,SAAgB,qBAAqB,OAAe,SAA2B;CAC7E,OAAO,MAAM,SAAS,KAAK,CAAC;AAC9B"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { createContext, useContext,
|
|
2
|
+
import { createContext, useContext, useLayoutEffect, useMemo, useRef } from "react";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
//#region src/sdk/payment/submission-registry.tsx
|
|
5
5
|
const SUBMISSION_CONTEXT_UNSET = Symbol("PaymentSubmissionContextUnset");
|
|
@@ -45,10 +45,14 @@ function usePaymentSubmission() {
|
|
|
45
45
|
* `path`, and unregisters on unmount or on any argument
|
|
46
46
|
* changing, so the map always holds the latest closures over the holder's card fields — a stale one
|
|
47
47
|
* would pay with the previous keystroke's CVV.
|
|
48
|
+
*
|
|
49
|
+
* Registration runs in the layout phase: before paint and before any passive effect of the commit.
|
|
50
|
+
* A render that no discrete event caused (an async mutation response, a timer) can be clicked on
|
|
51
|
+
* before its passive effects flush, and the click would then resolve the previous commit's closure.
|
|
48
52
|
*/
|
|
49
53
|
function useRegisterPaymentSubmission(path, submit, preflight, explainMissingMethod) {
|
|
50
54
|
const { register } = usePaymentSubmission();
|
|
51
|
-
|
|
55
|
+
useLayoutEffect(() => register(path, submit, preflight, explainMissingMethod), [
|
|
52
56
|
register,
|
|
53
57
|
path,
|
|
54
58
|
submit,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"submission-registry.js","names":[],"sources":["../../../src/sdk/payment/submission-registry.tsx"],"sourcesContent":["'use client';\n\nimport { createContext, type ReactNode, useContext, useEffect, useMemo, useRef } from 'react';\nimport type { PaymentGatewayResult, PaymentGatewayTransaction } from './gateway.js';\nimport type { PaymentUserError } from './operations.js';\n\n/**\n * The card-data submission channel (specs/use-place-order.md, Decision 1; specs/epics/credit-card.md\n * § \"The card-data submission channel\"). **The holder submits; the orchestrator never holds.**\n *\n * T7 (new card) and T12 (saved card) each register a submit-time continuation here. Each closes over\n * its own card fields, builds the gateway card object inside that closure, calls\n * `$actions.payment.sendPayments` itself, and returns only the gateway leg's result: its `userErrors` and\n * whether a request reached the gateway (T112). T13's\n * `usePlaceOrder` resolves one by `path` and invokes it between start-transaction and\n * gateway-callback, so no card-shaped field appears anywhere in this channel's type signature.\n *\n * It is a *second*, narrow context beside T5's `payment` context rather than a field on it, because\n * the two have different shapes and different readers: `draft` is render state every leaf reads, this\n * is an imperative registry exactly two components write and one hook reads. Registration is a\n * ref-held `Map`, not state — T7 re-registers a fresh closure on every keystroke, and making that a\n * render would re-render the whole payment section per character.\n *\n * Values that are **not** sensitive travel on T5's `draft`, never here: the save-card flag and label\n * (T10), the cardholder document (T8), and the billing-address-differs flag (T11). The holder folds\n * them into the card object inside its own closure.\n *\n * This module lives in the SDK rather than beside T5's roots so the layering stays `matter → sdk`:\n * `usePlaceOrder` is an SDK hook and cannot import from `matter/`. \"Provided by T5's root\" means the\n * root renders `PaymentSubmissionProvider`, which it does — both of them, for the same ADR-0009\n * reason the `payment` context has two providers.\n */\n\n/** Which entry path a submission serves. The canonical key type; T5's `PaymentPath` aliases it. */\nexport type PaymentSubmissionPath = 'new-card' | 'saved-card';\n\n/**\n * What a continuation resolves (T112): the gateway leg's `userErrors` (empty on success) and\n * `reachedGateway`, which is `false` only when the holder can show it refused before issuing any\n * request. `usePlaceOrder` frees the attempt for exactly that case and locks it for every other one,\n * including a missing or non-boolean flag. Defined by the producer, `gateway.ts`; re-exported here so the\n * hook imports it from this module and keeps `./gateway.js` to the transaction type alone.\n */\nexport type PaymentSubmissionResult = PaymentGatewayResult;\n\n/**\n * A holder's submit-time continuation — the whole channel. It takes the non-sensitive transaction\n * descriptor and resolves a `PaymentSubmissionResult`. It must not log, serialize, or return the card.\n */\nexport type PaymentSubmission = (transaction: PaymentGatewayTransaction) => Promise<PaymentSubmissionResult>;\n\n/**\n * A holder's pre-flight verdict on its own draft, resolved before any transaction opens (T27,\n * specs/place-order-preflight-card-validation.md). Returns the refusals that would make this\n * submission pointless, or an empty array to proceed. Synchronous by contract (Decision 6): a holder\n * validates what it already holds and performs no I/O.\n *\n * Card-free by the same rule the submission itself follows — it returns `PaymentUserError`s and never\n * the fields it inspected. It may have side effects inside its own component (rendering field errors,\n * moving focus); that is the half a value-shaped gate cannot carry.\n */\nexport type PaymentPreflightCheck = () => readonly PaymentUserError[];\n\n/**\n * A holder's explanation of why no payment method is selected (T47, specs/distinguish-a-pan-that-\n * matches-no-configured.md, Decision 2/4/5). Consulted only by `usePlaceOrder`'s method step, and only\n * when nothing is selected, so it can never reorder the checks. Returns the refusal to show in place of\n * the generic missing-method one, or `null` when the holder has nothing more specific to say.\n *\n * Synchronous and side-effect-free, unlike the pre-flight: it returns a value and touches nothing in its\n * own component. Fixed copy and a fixed code only — it must never echo anything the shopper typed.\n */\nexport type PaymentMissingMethodExplainer = () => PaymentUserError | null;\n\n/** One path's registration. `preflight` and `explainMissingMethod` are `null` when its holder registered none (FR-6). */\ntype PaymentSubmissionEntry = Readonly<{\n submit: PaymentSubmission;\n preflight: PaymentPreflightCheck | null;\n explainMissingMethod: PaymentMissingMethodExplainer | null;\n}>;\n\nexport type PaymentSubmissionChannel = Readonly<{\n /**\n * The entry path the shopper is on — T5's `draft.path`, mirrored here so the place-order sequence\n * resolves it without reading the matter context. One-way and derived: the root passes it through\n * on every render.\n */\n path: PaymentSubmissionPath | null;\n /**\n * Registers (or replaces) the continuation, and optionally its pre-flight check and its missing-method\n * explainer, for a path. Returns its unregister function.\n */\n register: (\n path: PaymentSubmissionPath,\n submit: PaymentSubmission,\n preflight?: PaymentPreflightCheck,\n explainMissingMethod?: PaymentMissingMethodExplainer,\n ) => () => void;\n /** Unchanged: the continuation registered for a path, or `null` when its holder is not composed or unmounted. */\n resolve: (path: PaymentSubmissionPath) => PaymentSubmission | null;\n /** The pre-flight check registered for a path, or `null` when its holder registered none. */\n resolvePreflight: (path: PaymentSubmissionPath) => PaymentPreflightCheck | null;\n /** The missing-method explainer registered for a path, or `null` when its holder registered none (T47). */\n resolveMissingMethodExplainer: (path: PaymentSubmissionPath) => PaymentMissingMethodExplainer | null;\n}>;\n\nconst SUBMISSION_CONTEXT_UNSET = Symbol('PaymentSubmissionContextUnset');\n\nconst PaymentSubmissionContext = createContext<PaymentSubmissionChannel | typeof SUBMISSION_CONTEXT_UNSET>(\n SUBMISSION_CONTEXT_UNSET,\n);\n\nexport type PaymentSubmissionProviderProps = Readonly<{\n /** T5's `draft.path`. */\n path: PaymentSubmissionPath | null;\n children: ReactNode;\n}>;\n\n/** Rendered by `PaymentSectionRoot` and `PaymentSectionController` around the section composition. */\nexport function PaymentSubmissionProvider({ path, children }: PaymentSubmissionProviderProps) {\n const submissions = useRef(new Map<PaymentSubmissionPath, PaymentSubmissionEntry>());\n\n const channel = useMemo<PaymentSubmissionChannel>(\n () => ({\n path,\n register: (registeredPath, submit, preflight, explainMissingMethod) => {\n submissions.current.set(registeredPath, {\n submit,\n preflight: preflight ?? null,\n explainMissingMethod: explainMissingMethod ?? null,\n });\n return () => {\n // Delete only if this registration is still the one on file: a Map holds one entry per\n // path, so an unmounting holder whose registration was already superseded by a still-mounted\n // sibling under the same path must not wipe that sibling's live continuation (or its check).\n if (submissions.current.get(registeredPath)?.submit === submit) {\n submissions.current.delete(registeredPath);\n }\n };\n },\n resolve: (registeredPath) => submissions.current.get(registeredPath)?.submit ?? null,\n resolvePreflight: (registeredPath) => submissions.current.get(registeredPath)?.preflight ?? null,\n resolveMissingMethodExplainer: (registeredPath) =>\n submissions.current.get(registeredPath)?.explainMissingMethod ?? null,\n }),\n [path],\n );\n\n return <PaymentSubmissionContext value={channel}>{children}</PaymentSubmissionContext>;\n}\n\n/**\n * Read the submission channel from the nearest `PaymentSectionRoot` or `PaymentSectionController`.\n * Throws outside either subtree, mirroring `usePayment()`: a misplaced holder or CTA is a composition\n * bug the author should see immediately. Distinct from the two *runtime* states — no path chosen, no\n * continuation registered — which `usePlaceOrder` resolves as a userError.\n */\nexport function usePaymentSubmission(): PaymentSubmissionChannel {\n const channel = useContext(PaymentSubmissionContext);\n if (channel === SUBMISSION_CONTEXT_UNSET) {\n throw new Error(\n 'usePaymentSubmission() must be used inside a <PaymentSectionRoot> or <PaymentSectionController> subtree.',\n );\n }\n return channel;\n}\n\n/**\n * The holder-facing signature (T7, T12; `preflight` added by T27, `explainMissingMethod` by T47).\n * Registers `submit` — and, optionally, its pre-flight check and its missing-method explainer — for\n * `path`, and unregisters on unmount or on any argument\n * changing, so the map always holds the latest closures over the holder's card fields — a stale one\n * would pay with the previous keystroke's CVV.\n */\nexport function useRegisterPaymentSubmission(\n path: PaymentSubmissionPath,\n submit: PaymentSubmission,\n preflight?: PaymentPreflightCheck,\n explainMissingMethod?: PaymentMissingMethodExplainer,\n): void {\n const { register } = usePaymentSubmission();\n\n useEffect(\n () => register(path, submit, preflight, explainMissingMethod),\n [register, path, submit, preflight, explainMissingMethod],\n );\n}\n"],"mappings":";;;;AA0GA,MAAM,2BAA2B,OAAO,+BAA+B;AAEvE,MAAM,2BAA2B,cAC/B,wBACF;;AASA,SAAgB,0BAA0B,EAAE,MAAM,YAA4C;CAC5F,MAAM,cAAc,uBAAO,IAAI,IAAmD,CAAC;CAEnF,MAAM,UAAU,eACP;EACL;EACA,WAAW,gBAAgB,QAAQ,WAAW,yBAAyB;GACrE,YAAY,QAAQ,IAAI,gBAAgB;IACtC;IACA,WAAW,aAAa;IACxB,sBAAsB,wBAAwB;GAChD,CAAC;GACD,aAAa;IAIX,IAAI,YAAY,QAAQ,IAAI,cAAc,CAAC,EAAE,WAAW,QACtD,YAAY,QAAQ,OAAO,cAAc;GAE7C;EACF;EACA,UAAU,mBAAmB,YAAY,QAAQ,IAAI,cAAc,CAAC,EAAE,UAAU;EAChF,mBAAmB,mBAAmB,YAAY,QAAQ,IAAI,cAAc,CAAC,EAAE,aAAa;EAC5F,gCAAgC,mBAC9B,YAAY,QAAQ,IAAI,cAAc,CAAC,EAAE,wBAAwB;CACrE,IACA,CAAC,IAAI,CACP;CAEA,OAAO,oBAAC,0BAAD;EAA0B,OAAO;EAAU;CAAmC,CAAA;AACvF;;;;;;;AAQA,SAAgB,uBAAiD;CAC/D,MAAM,UAAU,WAAW,wBAAwB;CACnD,IAAI,YAAY,0BACd,MAAM,IAAI,MACR,0GACF;CAEF,OAAO;AACT;;;;;;;;AASA,SAAgB,6BACd,MACA,QACA,WACA,sBACM;CACN,MAAM,EAAE,aAAa,qBAAqB;CAE1C,gBACQ,SAAS,MAAM,QAAQ,WAAW,oBAAoB,GAC5D;EAAC;EAAU;EAAM;EAAQ;EAAW;CAAoB,CAC1D;AACF"}
|
|
1
|
+
{"version":3,"file":"submission-registry.js","names":[],"sources":["../../../src/sdk/payment/submission-registry.tsx"],"sourcesContent":["'use client';\n\nimport { createContext, type ReactNode, useContext, useLayoutEffect, useMemo, useRef } from 'react';\nimport type { PaymentGatewayResult, PaymentGatewayTransaction } from './gateway.js';\nimport type { PaymentUserError } from './operations.js';\n\n/**\n * The card-data submission channel (specs/use-place-order.md, Decision 1; specs/epics/credit-card.md\n * § \"The card-data submission channel\"). **The holder submits; the orchestrator never holds.**\n *\n * T7 (new card) and T12 (saved card) each register a submit-time continuation here. Each closes over\n * its own card fields, builds the gateway card object inside that closure, calls\n * `$actions.payment.sendPayments` itself, and returns only the gateway leg's result: its `userErrors` and\n * whether a request reached the gateway (T112). T13's\n * `usePlaceOrder` resolves one by `path` and invokes it between start-transaction and\n * gateway-callback, so no card-shaped field appears anywhere in this channel's type signature.\n *\n * It is a *second*, narrow context beside T5's `payment` context rather than a field on it, because\n * the two have different shapes and different readers: `draft` is render state every leaf reads, this\n * is an imperative registry exactly two components write and one hook reads. Registration is a\n * ref-held `Map`, not state — T7 re-registers a fresh closure on every keystroke, and making that a\n * render would re-render the whole payment section per character.\n *\n * Values that are **not** sensitive travel on T5's `draft`, never here: the save-card flag and label\n * (T10), the cardholder document (T8), and the billing-address-differs flag (T11). The holder folds\n * them into the card object inside its own closure.\n *\n * This module lives in the SDK rather than beside T5's roots so the layering stays `matter → sdk`:\n * `usePlaceOrder` is an SDK hook and cannot import from `matter/`. \"Provided by T5's root\" means the\n * root renders `PaymentSubmissionProvider`, which it does — both of them, for the same ADR-0009\n * reason the `payment` context has two providers.\n */\n\n/** Which entry path a submission serves. The canonical key type; T5's `PaymentPath` aliases it. */\nexport type PaymentSubmissionPath = 'new-card' | 'saved-card';\n\n/**\n * What a continuation resolves (T112): the gateway leg's `userErrors` (empty on success) and\n * `reachedGateway`, which is `false` only when the holder can show it refused before issuing any\n * request. `usePlaceOrder` frees the attempt for exactly that case and locks it for every other one,\n * including a missing or non-boolean flag. Defined by the producer, `gateway.ts`; re-exported here so the\n * hook imports it from this module and keeps `./gateway.js` to the transaction type alone.\n */\nexport type PaymentSubmissionResult = PaymentGatewayResult &\n Readonly<{\n /**\n * `true` only from a holder whose order is placed by Start Transaction alone, so the gateway leg and its\n * callback never run (T39). Read as `=== true`, and only with an empty `userErrors`: anything else keeps\n * the callback leg.\n */\n completesWithoutGateway?: boolean;\n }>;\n\n/**\n * A holder's submit-time continuation — the whole channel. It takes the non-sensitive transaction\n * descriptor and resolves a `PaymentSubmissionResult`. It must not log, serialize, or return the card.\n */\nexport type PaymentSubmission = (transaction: PaymentGatewayTransaction) => Promise<PaymentSubmissionResult>;\n\n/**\n * A holder's pre-flight verdict on its own draft, resolved before any transaction opens (T27,\n * specs/place-order-preflight-card-validation.md). Returns the refusals that would make this\n * submission pointless, or an empty array to proceed. Synchronous by contract (Decision 6): a holder\n * validates what it already holds and performs no I/O.\n *\n * Card-free by the same rule the submission itself follows — it returns `PaymentUserError`s and never\n * the fields it inspected. It may have side effects inside its own component (rendering field errors,\n * moving focus); that is the half a value-shaped gate cannot carry.\n */\nexport type PaymentPreflightCheck = () => readonly PaymentUserError[];\n\n/**\n * A holder's explanation of why no payment method is selected (T47, specs/distinguish-a-pan-that-\n * matches-no-configured.md, Decision 2/4/5). Consulted only by `usePlaceOrder`'s method step, and only\n * when nothing is selected, so it can never reorder the checks. Returns the refusal to show in place of\n * the generic missing-method one, or `null` when the holder has nothing more specific to say.\n *\n * Synchronous and side-effect-free, unlike the pre-flight: it returns a value and touches nothing in its\n * own component. Fixed copy and a fixed code only — it must never echo anything the shopper typed.\n */\nexport type PaymentMissingMethodExplainer = () => PaymentUserError | null;\n\n/** One path's registration. `preflight` and `explainMissingMethod` are `null` when its holder registered none (FR-6). */\ntype PaymentSubmissionEntry = Readonly<{\n submit: PaymentSubmission;\n preflight: PaymentPreflightCheck | null;\n explainMissingMethod: PaymentMissingMethodExplainer | null;\n}>;\n\nexport type PaymentSubmissionChannel = Readonly<{\n /**\n * The entry path the shopper is on — T5's `draft.path`, mirrored here so the place-order sequence\n * resolves it without reading the matter context. One-way and derived: the root passes it through\n * on every render.\n */\n path: PaymentSubmissionPath | null;\n /**\n * Registers (or replaces) the continuation, and optionally its pre-flight check and its missing-method\n * explainer, for a path. Returns its unregister function.\n */\n register: (\n path: PaymentSubmissionPath,\n submit: PaymentSubmission,\n preflight?: PaymentPreflightCheck,\n explainMissingMethod?: PaymentMissingMethodExplainer,\n ) => () => void;\n /** Unchanged: the continuation registered for a path, or `null` when its holder is not composed or unmounted. */\n resolve: (path: PaymentSubmissionPath) => PaymentSubmission | null;\n /** The pre-flight check registered for a path, or `null` when its holder registered none. */\n resolvePreflight: (path: PaymentSubmissionPath) => PaymentPreflightCheck | null;\n /** The missing-method explainer registered for a path, or `null` when its holder registered none (T47). */\n resolveMissingMethodExplainer: (path: PaymentSubmissionPath) => PaymentMissingMethodExplainer | null;\n}>;\n\nconst SUBMISSION_CONTEXT_UNSET = Symbol('PaymentSubmissionContextUnset');\n\nconst PaymentSubmissionContext = createContext<PaymentSubmissionChannel | typeof SUBMISSION_CONTEXT_UNSET>(\n SUBMISSION_CONTEXT_UNSET,\n);\n\nexport type PaymentSubmissionProviderProps = Readonly<{\n /** T5's `draft.path`. */\n path: PaymentSubmissionPath | null;\n children: ReactNode;\n}>;\n\n/** Rendered by `PaymentSectionRoot` and `PaymentSectionController` around the section composition. */\nexport function PaymentSubmissionProvider({ path, children }: PaymentSubmissionProviderProps) {\n const submissions = useRef(new Map<PaymentSubmissionPath, PaymentSubmissionEntry>());\n\n const channel = useMemo<PaymentSubmissionChannel>(\n () => ({\n path,\n register: (registeredPath, submit, preflight, explainMissingMethod) => {\n submissions.current.set(registeredPath, {\n submit,\n preflight: preflight ?? null,\n explainMissingMethod: explainMissingMethod ?? null,\n });\n return () => {\n // Delete only if this registration is still the one on file: a Map holds one entry per\n // path, so an unmounting holder whose registration was already superseded by a still-mounted\n // sibling under the same path must not wipe that sibling's live continuation (or its check).\n if (submissions.current.get(registeredPath)?.submit === submit) {\n submissions.current.delete(registeredPath);\n }\n };\n },\n resolve: (registeredPath) => submissions.current.get(registeredPath)?.submit ?? null,\n resolvePreflight: (registeredPath) => submissions.current.get(registeredPath)?.preflight ?? null,\n resolveMissingMethodExplainer: (registeredPath) =>\n submissions.current.get(registeredPath)?.explainMissingMethod ?? null,\n }),\n [path],\n );\n\n return <PaymentSubmissionContext value={channel}>{children}</PaymentSubmissionContext>;\n}\n\n/**\n * Read the submission channel from the nearest `PaymentSectionRoot` or `PaymentSectionController`.\n * Throws outside either subtree, mirroring `usePayment()`: a misplaced holder or CTA is a composition\n * bug the author should see immediately. Distinct from the two *runtime* states — no path chosen, no\n * continuation registered — which `usePlaceOrder` resolves as a userError.\n */\nexport function usePaymentSubmission(): PaymentSubmissionChannel {\n const channel = useContext(PaymentSubmissionContext);\n if (channel === SUBMISSION_CONTEXT_UNSET) {\n throw new Error(\n 'usePaymentSubmission() must be used inside a <PaymentSectionRoot> or <PaymentSectionController> subtree.',\n );\n }\n return channel;\n}\n\n/**\n * The holder-facing signature (T7, T12; `preflight` added by T27, `explainMissingMethod` by T47).\n * Registers `submit` — and, optionally, its pre-flight check and its missing-method explainer — for\n * `path`, and unregisters on unmount or on any argument\n * changing, so the map always holds the latest closures over the holder's card fields — a stale one\n * would pay with the previous keystroke's CVV.\n *\n * Registration runs in the layout phase: before paint and before any passive effect of the commit.\n * A render that no discrete event caused (an async mutation response, a timer) can be clicked on\n * before its passive effects flush, and the click would then resolve the previous commit's closure.\n */\nexport function useRegisterPaymentSubmission(\n path: PaymentSubmissionPath,\n submit: PaymentSubmission,\n preflight?: PaymentPreflightCheck,\n explainMissingMethod?: PaymentMissingMethodExplainer,\n): void {\n const { register } = usePaymentSubmission();\n\n useLayoutEffect(\n () => register(path, submit, preflight, explainMissingMethod),\n [register, path, submit, preflight, explainMissingMethod],\n );\n}\n"],"mappings":";;;;AAkHA,MAAM,2BAA2B,OAAO,+BAA+B;AAEvE,MAAM,2BAA2B,cAC/B,wBACF;;AASA,SAAgB,0BAA0B,EAAE,MAAM,YAA4C;CAC5F,MAAM,cAAc,uBAAO,IAAI,IAAmD,CAAC;CAEnF,MAAM,UAAU,eACP;EACL;EACA,WAAW,gBAAgB,QAAQ,WAAW,yBAAyB;GACrE,YAAY,QAAQ,IAAI,gBAAgB;IACtC;IACA,WAAW,aAAa;IACxB,sBAAsB,wBAAwB;GAChD,CAAC;GACD,aAAa;IAIX,IAAI,YAAY,QAAQ,IAAI,cAAc,CAAC,EAAE,WAAW,QACtD,YAAY,QAAQ,OAAO,cAAc;GAE7C;EACF;EACA,UAAU,mBAAmB,YAAY,QAAQ,IAAI,cAAc,CAAC,EAAE,UAAU;EAChF,mBAAmB,mBAAmB,YAAY,QAAQ,IAAI,cAAc,CAAC,EAAE,aAAa;EAC5F,gCAAgC,mBAC9B,YAAY,QAAQ,IAAI,cAAc,CAAC,EAAE,wBAAwB;CACrE,IACA,CAAC,IAAI,CACP;CAEA,OAAO,oBAAC,0BAAD;EAA0B,OAAO;EAAU;CAAmC,CAAA;AACvF;;;;;;;AAQA,SAAgB,uBAAiD;CAC/D,MAAM,UAAU,WAAW,wBAAwB;CACnD,IAAI,YAAY,0BACd,MAAM,IAAI,MACR,0GACF;CAEF,OAAO;AACT;;;;;;;;;;;;AAaA,SAAgB,6BACd,MACA,QACA,WACA,sBACM;CACN,MAAM,EAAE,aAAa,qBAAqB;CAE1C,sBACQ,SAAS,MAAM,QAAQ,WAAW,oBAAoB,GAC5D;EAAC;EAAU;EAAM;EAAQ;EAAW;CAAoB,CAC1D;AACF"}
|