@aglyn/plugins-commerce 1.0.0-beta.143
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/LICENSE +201 -0
- package/README.md +10 -0
- package/package.json +57 -0
- package/src/index.d.ts +38 -0
- package/src/index.js +38 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/account.d.ts +31 -0
- package/src/lib/components/account.js +645 -0
- package/src/lib/components/account.js.map +1 -0
- package/src/lib/components/cart.d.ts +38 -0
- package/src/lib/components/cart.js +851 -0
- package/src/lib/components/cart.js.map +1 -0
- package/src/lib/components/commerce-console-page.d.ts +32 -0
- package/src/lib/components/commerce-console-page.js +296 -0
- package/src/lib/components/commerce-console-page.js.map +1 -0
- package/src/lib/components/commerce-console-sections.d.ts +34 -0
- package/src/lib/components/commerce-console-sections.js +62 -0
- package/src/lib/components/commerce-console-sections.js.map +1 -0
- package/src/lib/components/console/catalog-organization-card.component.d.ts +14 -0
- package/src/lib/components/console/catalog-organization-card.component.js +1087 -0
- package/src/lib/components/console/catalog-organization-card.component.js.map +1 -0
- package/src/lib/components/console/commerce-analytics-card.component.d.ts +27 -0
- package/src/lib/components/console/commerce-analytics-card.component.js +310 -0
- package/src/lib/components/console/commerce-analytics-card.component.js.map +1 -0
- package/src/lib/components/console/commerce-glance-card.component.d.ts +20 -0
- package/src/lib/components/console/commerce-glance-card.component.js +340 -0
- package/src/lib/components/console/commerce-glance-card.component.js.map +1 -0
- package/src/lib/components/console/commerce-stat-tile.component.d.ts +37 -0
- package/src/lib/components/console/commerce-stat-tile.component.js +72 -0
- package/src/lib/components/console/commerce-stat-tile.component.js.map +1 -0
- package/src/lib/components/console/discounts-card.component.d.ts +14 -0
- package/src/lib/components/console/discounts-card.component.js +443 -0
- package/src/lib/components/console/discounts-card.component.js.map +1 -0
- package/src/lib/components/console/entitlement-gate.component.d.ts +76 -0
- package/src/lib/components/console/entitlement-gate.component.js +115 -0
- package/src/lib/components/console/entitlement-gate.component.js.map +1 -0
- package/src/lib/components/console/gift-cards-card.component.d.ts +29 -0
- package/src/lib/components/console/gift-cards-card.component.js +379 -0
- package/src/lib/components/console/gift-cards-card.component.js.map +1 -0
- package/src/lib/components/console/host-coupons-card.component.d.ts +14 -0
- package/src/lib/components/console/host-coupons-card.component.js +252 -0
- package/src/lib/components/console/host-coupons-card.component.js.map +1 -0
- package/src/lib/components/console/host-orders-card.component.d.ts +13 -0
- package/src/lib/components/console/host-orders-card.component.js +861 -0
- package/src/lib/components/console/host-orders-card.component.js.map +1 -0
- package/src/lib/components/console/locations-card.component.d.ts +14 -0
- package/src/lib/components/console/locations-card.component.js +256 -0
- package/src/lib/components/console/locations-card.component.js.map +1 -0
- package/src/lib/components/console/member-posts-card.component.d.ts +8 -0
- package/src/lib/components/console/member-posts-card.component.js +283 -0
- package/src/lib/components/console/member-posts-card.component.js.map +1 -0
- package/src/lib/components/console/newest-site-users-card.component.d.ts +36 -0
- package/src/lib/components/console/newest-site-users-card.component.js +126 -0
- package/src/lib/components/console/newest-site-users-card.component.js.map +1 -0
- package/src/lib/components/console/order-detail-dialog.component.d.ts +28 -0
- package/src/lib/components/console/order-detail-dialog.component.js +976 -0
- package/src/lib/components/console/order-detail-dialog.component.js.map +1 -0
- package/src/lib/components/console/paid-media.d.ts +127 -0
- package/src/lib/components/console/paid-media.js +397 -0
- package/src/lib/components/console/paid-media.js.map +1 -0
- package/src/lib/components/console/payments-settings-card.component.d.ts +13 -0
- package/src/lib/components/console/payments-settings-card.component.js +295 -0
- package/src/lib/components/console/payments-settings-card.component.js.map +1 -0
- package/src/lib/components/console/pos-page.component.d.ts +13 -0
- package/src/lib/components/console/pos-page.component.js +916 -0
- package/src/lib/components/console/pos-page.component.js.map +1 -0
- package/src/lib/components/console/product-editor-dialog.component.d.ts +32 -0
- package/src/lib/components/console/product-editor-dialog.component.js +1374 -0
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -0
- package/src/lib/components/console/products-hub-card.component.d.ts +16 -0
- package/src/lib/components/console/products-hub-card.component.js +1280 -0
- package/src/lib/components/console/products-hub-card.component.js.map +1 -0
- package/src/lib/components/console/products-hub-zone.component.d.ts +50 -0
- package/src/lib/components/console/products-hub-zone.component.js +200 -0
- package/src/lib/components/console/products-hub-zone.component.js.map +1 -0
- package/src/lib/components/console/recovery-queue-card.component.d.ts +39 -0
- package/src/lib/components/console/recovery-queue-card.component.js +262 -0
- package/src/lib/components/console/recovery-queue-card.component.js.map +1 -0
- package/src/lib/components/console/registers-card.component.d.ts +21 -0
- package/src/lib/components/console/registers-card.component.js +296 -0
- package/src/lib/components/console/registers-card.component.js.map +1 -0
- package/src/lib/components/console/reservations-card.component.d.ts +14 -0
- package/src/lib/components/console/reservations-card.component.js +650 -0
- package/src/lib/components/console/reservations-card.component.js.map +1 -0
- package/src/lib/components/console/reviews-moderation-card.component.d.ts +8 -0
- package/src/lib/components/console/reviews-moderation-card.component.js +235 -0
- package/src/lib/components/console/reviews-moderation-card.component.js.map +1 -0
- package/src/lib/components/console/shipping-settings-card.component.d.ts +38 -0
- package/src/lib/components/console/shipping-settings-card.component.js +560 -0
- package/src/lib/components/console/shipping-settings-card.component.js.map +1 -0
- package/src/lib/components/console/stock-movements-card.component.d.ts +31 -0
- package/src/lib/components/console/stock-movements-card.component.js +333 -0
- package/src/lib/components/console/stock-movements-card.component.js.map +1 -0
- package/src/lib/components/console/store-settings-card.component.d.ts +14 -0
- package/src/lib/components/console/store-settings-card.component.js +300 -0
- package/src/lib/components/console/store-settings-card.component.js.map +1 -0
- package/src/lib/components/console/storefront-tax-summary-card.component.d.ts +73 -0
- package/src/lib/components/console/storefront-tax-summary-card.component.js +408 -0
- package/src/lib/components/console/storefront-tax-summary-card.component.js.map +1 -0
- package/src/lib/components/console/suppliers-card.component.d.ts +8 -0
- package/src/lib/components/console/suppliers-card.component.js +328 -0
- package/src/lib/components/console/suppliers-card.component.js.map +1 -0
- package/src/lib/components/console/tax-settings-card.component.d.ts +14 -0
- package/src/lib/components/console/tax-settings-card.component.js +473 -0
- package/src/lib/components/console/tax-settings-card.component.js.map +1 -0
- package/src/lib/components/gate.d.ts +39 -0
- package/src/lib/components/gate.js +202 -0
- package/src/lib/components/gate.js.map +1 -0
- package/src/lib/components/gated-video.d.ts +40 -0
- package/src/lib/components/gated-video.js +366 -0
- package/src/lib/components/gated-video.js.map +1 -0
- package/src/lib/components/member-feed.d.ts +32 -0
- package/src/lib/components/member-feed.js +198 -0
- package/src/lib/components/member-feed.js.map +1 -0
- package/src/lib/components/member-recovery.d.ts +35 -0
- package/src/lib/components/member-recovery.js +313 -0
- package/src/lib/components/member-recovery.js.map +1 -0
- package/src/lib/components/member-signin.d.ts +35 -0
- package/src/lib/components/member-signin.js +257 -0
- package/src/lib/components/member-signin.js.map +1 -0
- package/src/lib/components/member-signup.d.ts +34 -0
- package/src/lib/components/member-signup.js +287 -0
- package/src/lib/components/member-signup.js.map +1 -0
- package/src/lib/components/newsletter-signup.d.ts +39 -0
- package/src/lib/components/newsletter-signup.js +229 -0
- package/src/lib/components/newsletter-signup.js.map +1 -0
- package/src/lib/components/product-detail.d.ts +38 -0
- package/src/lib/components/product-detail.js +1022 -0
- package/src/lib/components/product-detail.js.map +1 -0
- package/src/lib/components/product-grid.d.ts +67 -0
- package/src/lib/components/product-grid.js +909 -0
- package/src/lib/components/product-grid.js.map +1 -0
- package/src/lib/components/product-reviews.d.ts +32 -0
- package/src/lib/components/product-reviews.js +349 -0
- package/src/lib/components/product-reviews.js.map +1 -0
- package/src/lib/components/related-products.d.ts +33 -0
- package/src/lib/components/related-products.js +232 -0
- package/src/lib/components/related-products.js.map +1 -0
- package/src/lib/components/reservation-widget.d.ts +33 -0
- package/src/lib/components/reservation-widget.js +418 -0
- package/src/lib/components/reservation-widget.js.map +1 -0
- package/src/lib/components/storefront-payment-element-fallback.d.ts +18 -0
- package/src/lib/components/storefront-payment-element-fallback.js +51 -0
- package/src/lib/components/storefront-payment-element-fallback.js.map +1 -0
- package/src/lib/components/storefront-payment-element.d.ts +78 -0
- package/src/lib/components/storefront-payment-element.js +156 -0
- package/src/lib/components/storefront-payment-element.js.map +1 -0
- package/src/lib/components/wishlist.d.ts +46 -0
- package/src/lib/components/wishlist.js +304 -0
- package/src/lib/components/wishlist.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +18 -0
- package/src/lib/constants/bundle-common.js +18 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/constants/product-filters.d.ts +35 -0
- package/src/lib/constants/product-filters.js +109 -0
- package/src/lib/constants/product-filters.js.map +1 -0
- package/src/lib/model/commerce-cart.d.ts +52 -0
- package/src/lib/model/commerce-cart.js +102 -0
- package/src/lib/model/commerce-cart.js.map +1 -0
- package/src/lib/model/commerce-connect-transfer.d.ts +243 -0
- package/src/lib/model/commerce-connect-transfer.js +231 -0
- package/src/lib/model/commerce-connect-transfer.js.map +1 -0
- package/src/lib/model/commerce-discounts.d.ts +164 -0
- package/src/lib/model/commerce-discounts.js +231 -0
- package/src/lib/model/commerce-discounts.js.map +1 -0
- package/src/lib/model/commerce-dispute.d.ts +124 -0
- package/src/lib/model/commerce-dispute.js +225 -0
- package/src/lib/model/commerce-dispute.js.map +1 -0
- package/src/lib/model/commerce-gift-cards.d.ts +107 -0
- package/src/lib/model/commerce-gift-cards.js +121 -0
- package/src/lib/model/commerce-gift-cards.js.map +1 -0
- package/src/lib/model/commerce-io.d.ts +36 -0
- package/src/lib/model/commerce-io.js +245 -0
- package/src/lib/model/commerce-io.js.map +1 -0
- package/src/lib/model/commerce-orders.d.ts +1040 -0
- package/src/lib/model/commerce-orders.js +1022 -0
- package/src/lib/model/commerce-orders.js.map +1 -0
- package/src/lib/model/commerce-promotions.d.ts +188 -0
- package/src/lib/model/commerce-promotions.js +199 -0
- package/src/lib/model/commerce-promotions.js.map +1 -0
- package/src/lib/model/commerce-reservations.d.ts +175 -0
- package/src/lib/model/commerce-reservations.js +157 -0
- package/src/lib/model/commerce-reservations.js.map +1 -0
- package/src/lib/model/commerce-shipping.d.ts +316 -0
- package/src/lib/model/commerce-shipping.js +484 -0
- package/src/lib/model/commerce-shipping.js.map +1 -0
- package/src/lib/model/commerce-stock-holds.d.ts +199 -0
- package/src/lib/model/commerce-stock-holds.js +190 -0
- package/src/lib/model/commerce-stock-holds.js.map +1 -0
- package/src/lib/model/commerce-subscription-status.d.ts +67 -0
- package/src/lib/model/commerce-subscription-status.js +76 -0
- package/src/lib/model/commerce-subscription-status.js.map +1 -0
- package/src/lib/model/commerce-tax-decision.d.ts +222 -0
- package/src/lib/model/commerce-tax-decision.js +165 -0
- package/src/lib/model/commerce-tax-decision.js.map +1 -0
- package/src/lib/model/commerce-tax.d.ts +176 -0
- package/src/lib/model/commerce-tax.js +92 -0
- package/src/lib/model/commerce-tax.js.map +1 -0
- package/src/lib/model/commerce.d.ts +551 -0
- package/src/lib/model/commerce.js +739 -0
- package/src/lib/model/commerce.js.map +1 -0
- package/src/lib/model/index.d.ts +40 -0
- package/src/lib/model/index.js +40 -0
- package/src/lib/model/index.js.map +1 -0
- package/src/lib/model/order-figures.d.ts +48 -0
- package/src/lib/model/order-figures.js +74 -0
- package/src/lib/model/order-figures.js.map +1 -0
- package/src/lib/model/plugin-permissions.d.ts +23 -0
- package/src/lib/model/plugin-permissions.js +34 -0
- package/src/lib/model/plugin-permissions.js.map +1 -0
- package/src/lib/model/purchase-analytics.d.ts +186 -0
- package/src/lib/model/purchase-analytics.js +216 -0
- package/src/lib/model/purchase-analytics.js.map +1 -0
- package/src/lib/plugin-config.d.ts +62 -0
- package/src/lib/plugin-config.js +78 -0
- package/src/lib/plugin-config.js.map +1 -0
- package/src/lib/plugin.d.ts +28 -0
- package/src/lib/plugin.js +147 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/server/billing-webhook.d.ts +26 -0
- package/src/lib/server/billing-webhook.js +4011 -0
- package/src/lib/server/billing-webhook.js.map +1 -0
- package/src/lib/server/cancel-order.d.ts +87 -0
- package/src/lib/server/cancel-order.js +368 -0
- package/src/lib/server/cancel-order.js.map +1 -0
- package/src/lib/server/cart-checkout.d.ts +25 -0
- package/src/lib/server/cart-checkout.js +976 -0
- package/src/lib/server/cart-checkout.js.map +1 -0
- package/src/lib/server/cart-cookie.d.ts +32 -0
- package/src/lib/server/cart-cookie.js +77 -0
- package/src/lib/server/cart-cookie.js.map +1 -0
- package/src/lib/server/cart.d.ts +34 -0
- package/src/lib/server/cart.js +181 -0
- package/src/lib/server/cart.js.map +1 -0
- package/src/lib/server/catalog.d.ts +127 -0
- package/src/lib/server/catalog.js +277 -0
- package/src/lib/server/catalog.js.map +1 -0
- package/src/lib/server/checkout.d.ts +36 -0
- package/src/lib/server/checkout.js +915 -0
- package/src/lib/server/checkout.js.map +1 -0
- package/src/lib/server/connect.d.ts +26 -0
- package/src/lib/server/connect.js +238 -0
- package/src/lib/server/connect.js.map +1 -0
- package/src/lib/server/contact-refund.d.ts +117 -0
- package/src/lib/server/contact-refund.js +196 -0
- package/src/lib/server/contact-refund.js.map +1 -0
- package/src/lib/server/download.d.ts +39 -0
- package/src/lib/server/download.js +206 -0
- package/src/lib/server/download.js.map +1 -0
- package/src/lib/server/draft-order.d.ts +26 -0
- package/src/lib/server/draft-order.js +706 -0
- package/src/lib/server/draft-order.js.map +1 -0
- package/src/lib/server/feed.d.ts +24 -0
- package/src/lib/server/feed.js +59 -0
- package/src/lib/server/feed.js.map +1 -0
- package/src/lib/server/fulfill-order.d.ts +97 -0
- package/src/lib/server/fulfill-order.js +235 -0
- package/src/lib/server/fulfill-order.js.map +1 -0
- package/src/lib/server/gate.d.ts +36 -0
- package/src/lib/server/gate.js +112 -0
- package/src/lib/server/gate.js.map +1 -0
- package/src/lib/server/gift-cards.d.ts +42 -0
- package/src/lib/server/gift-cards.js +219 -0
- package/src/lib/server/gift-cards.js.map +1 -0
- package/src/lib/server/low-stock.d.ts +49 -0
- package/src/lib/server/low-stock.js +60 -0
- package/src/lib/server/low-stock.js.map +1 -0
- package/src/lib/server/manual-tax-rate.d.ts +82 -0
- package/src/lib/server/manual-tax-rate.js +113 -0
- package/src/lib/server/manual-tax-rate.js.map +1 -0
- package/src/lib/server/member-feed.d.ts +24 -0
- package/src/lib/server/member-feed.js +72 -0
- package/src/lib/server/member-feed.js.map +1 -0
- package/src/lib/server/member-name-search.d.ts +37 -0
- package/src/lib/server/member-name-search.js +42 -0
- package/src/lib/server/member-name-search.js.map +1 -0
- package/src/lib/server/member-post.d.ts +23 -0
- package/src/lib/server/member-post.js +166 -0
- package/src/lib/server/member-post.js.map +1 -0
- package/src/lib/server/membership-account.d.ts +37 -0
- package/src/lib/server/membership-account.js +148 -0
- package/src/lib/server/membership-account.js.map +1 -0
- package/src/lib/server/membership-admin-password.d.ts +33 -0
- package/src/lib/server/membership-admin-password.js +202 -0
- package/src/lib/server/membership-admin-password.js.map +1 -0
- package/src/lib/server/membership-content.d.ts +23 -0
- package/src/lib/server/membership-content.js +105 -0
- package/src/lib/server/membership-content.js.map +1 -0
- package/src/lib/server/membership-login.d.ts +19 -0
- package/src/lib/server/membership-login.js +137 -0
- package/src/lib/server/membership-login.js.map +1 -0
- package/src/lib/server/membership-logout.d.ts +19 -0
- package/src/lib/server/membership-logout.js +35 -0
- package/src/lib/server/membership-logout.js.map +1 -0
- package/src/lib/server/membership-recover.d.ts +60 -0
- package/src/lib/server/membership-recover.js +244 -0
- package/src/lib/server/membership-recover.js.map +1 -0
- package/src/lib/server/membership-register.d.ts +22 -0
- package/src/lib/server/membership-register.js +241 -0
- package/src/lib/server/membership-register.js.map +1 -0
- package/src/lib/server/membership-reset.d.ts +26 -0
- package/src/lib/server/membership-reset.js +88 -0
- package/src/lib/server/membership-reset.js.map +1 -0
- package/src/lib/server/membership-wishlist.d.ts +23 -0
- package/src/lib/server/membership-wishlist.js +89 -0
- package/src/lib/server/membership-wishlist.js.map +1 -0
- package/src/lib/server/membership.d.ts +96 -0
- package/src/lib/server/membership.js +222 -0
- package/src/lib/server/membership.js.map +1 -0
- package/src/lib/server/native-checkout.d.ts +170 -0
- package/src/lib/server/native-checkout.js +193 -0
- package/src/lib/server/native-checkout.js.map +1 -0
- package/src/lib/server/newsletter.d.ts +23 -0
- package/src/lib/server/newsletter.js +323 -0
- package/src/lib/server/newsletter.js.map +1 -0
- package/src/lib/server/notify-restock.d.ts +22 -0
- package/src/lib/server/notify-restock.js +84 -0
- package/src/lib/server/notify-restock.js.map +1 -0
- package/src/lib/server/order-analytics.d.ts +134 -0
- package/src/lib/server/order-analytics.js +263 -0
- package/src/lib/server/order-analytics.js.map +1 -0
- package/src/lib/server/order-figures.d.ts +33 -0
- package/src/lib/server/order-figures.js +220 -0
- package/src/lib/server/order-figures.js.map +1 -0
- package/src/lib/server/paid-media-delivery.d.ts +173 -0
- package/src/lib/server/paid-media-delivery.js +212 -0
- package/src/lib/server/paid-media-delivery.js.map +1 -0
- package/src/lib/server/pos-order.d.ts +54 -0
- package/src/lib/server/pos-order.js +1137 -0
- package/src/lib/server/pos-order.js.map +1 -0
- package/src/lib/server/process-abandoned.d.ts +54 -0
- package/src/lib/server/process-abandoned.js +222 -0
- package/src/lib/server/process-abandoned.js.map +1 -0
- package/src/lib/server/process-restock.d.ts +42 -0
- package/src/lib/server/process-restock.js +219 -0
- package/src/lib/server/process-restock.js.map +1 -0
- package/src/lib/server/product-card.d.ts +29 -0
- package/src/lib/server/product-card.js +54 -0
- package/src/lib/server/product-card.js.map +1 -0
- package/src/lib/server/product.d.ts +70 -0
- package/src/lib/server/product.js +105 -0
- package/src/lib/server/product.js.map +1 -0
- package/src/lib/server/promotion-hold.d.ts +144 -0
- package/src/lib/server/promotion-hold.js +174 -0
- package/src/lib/server/promotion-hold.js.map +1 -0
- package/src/lib/server/reconcile-stock.d.ts +106 -0
- package/src/lib/server/reconcile-stock.js +349 -0
- package/src/lib/server/reconcile-stock.js.map +1 -0
- package/src/lib/server/refund.d.ts +45 -0
- package/src/lib/server/refund.js +607 -0
- package/src/lib/server/refund.js.map +1 -0
- package/src/lib/server/related.d.ts +24 -0
- package/src/lib/server/related.js +104 -0
- package/src/lib/server/related.js.map +1 -0
- package/src/lib/server/reservation-availability.d.ts +23 -0
- package/src/lib/server/reservation-availability.js +111 -0
- package/src/lib/server/reservation-availability.js.map +1 -0
- package/src/lib/server/reserve-stock.d.ts +156 -0
- package/src/lib/server/reserve-stock.js +146 -0
- package/src/lib/server/reserve-stock.js.map +1 -0
- package/src/lib/server/reserve.d.ts +25 -0
- package/src/lib/server/reserve.js +426 -0
- package/src/lib/server/reserve.js.map +1 -0
- package/src/lib/server/restock-flag.d.ts +202 -0
- package/src/lib/server/restock-flag.js +449 -0
- package/src/lib/server/restock-flag.js.map +1 -0
- package/src/lib/server/reviews.d.ts +51 -0
- package/src/lib/server/reviews.js +132 -0
- package/src/lib/server/reviews.js.map +1 -0
- package/src/lib/server/site-page-enricher.d.ts +42 -0
- package/src/lib/server/site-page-enricher.js +149 -0
- package/src/lib/server/site-page-enricher.js.map +1 -0
- package/src/lib/server/site-page-resolver.d.ts +26 -0
- package/src/lib/server/site-page-resolver.js +223 -0
- package/src/lib/server/site-page-resolver.js.map +1 -0
- package/src/lib/server/stock-hold.d.ts +127 -0
- package/src/lib/server/stock-hold.js +258 -0
- package/src/lib/server/stock-hold.js.map +1 -0
- package/src/lib/server/storefront-tax-record.d.ts +22 -0
- package/src/lib/server/storefront-tax-record.js +218 -0
- package/src/lib/server/storefront-tax-record.js.map +1 -0
- package/src/lib/server/storefront-tax.d.ts +197 -0
- package/src/lib/server/storefront-tax.js +245 -0
- package/src/lib/server/storefront-tax.js.map +1 -0
- package/src/lib/server/stream.d.ts +25 -0
- package/src/lib/server/stream.js +191 -0
- package/src/lib/server/stream.js.map +1 -0
- package/src/lib/server/subscription-portal.d.ts +24 -0
- package/src/lib/server/subscription-portal.js +91 -0
- package/src/lib/server/subscription-portal.js.map +1 -0
- package/src/lib/server/supplier-outbox.d.ts +190 -0
- package/src/lib/server/supplier-outbox.js +342 -0
- package/src/lib/server/supplier-outbox.js.map +1 -0
- package/src/lib/server/supplier-update.d.ts +23 -0
- package/src/lib/server/supplier-update.js +289 -0
- package/src/lib/server/supplier-update.js.map +1 -0
- package/src/lib/server/tax-profile.d.ts +29 -0
- package/src/lib/server/tax-profile.js +40 -0
- package/src/lib/server/tax-profile.js.map +1 -0
- package/src/lib/server.d.ts +32 -0
- package/src/lib/server.js +311 -0
- package/src/lib/server.js.map +1 -0
- package/src/lib/site.d.ts +27 -0
- package/src/lib/site.js +134 -0
- package/src/lib/site.js.map +1 -0
- package/src/lib/utils/escape-html.d.ts +41 -0
- package/src/lib/utils/escape-html.js +41 -0
- package/src/lib/utils/escape-html.js.map +1 -0
- package/src/lib/utils/generate-preset-id.d.ts +19 -0
- package/src/lib/utils/generate-preset-id.js +25 -0
- package/src/lib/utils/generate-preset-id.js.map +1 -0
- package/src/lib/utils/member-continue.d.ts +45 -0
- package/src/lib/utils/member-continue.js +49 -0
- package/src/lib/utils/member-continue.js.map +1 -0
- package/src/lib/utils/use-storefront-purchase-event.d.ts +60 -0
- package/src/lib/utils/use-storefront-purchase-event.js +142 -0
- package/src/lib/utils/use-storefront-purchase-event.js.map +1 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { createHash, createHmac, randomBytes, scryptSync, timingSafeEqual } from "crypto";
|
|
17
|
+
import { firebaseAdmin } from "@aglyn/tenant-data-admin";
|
|
18
|
+
/**
|
|
19
|
+
* Site membership primitives (AGL-109): visitor accounts per host with
|
|
20
|
+
* scrypt-hashed passwords and an HMAC-signed httpOnly session cookie.
|
|
21
|
+
* Per-site Firebase Auth is a future infra decision (AGL-45 class); this
|
|
22
|
+
* keeps member content out of static HTML the same way AGL-87 does.
|
|
23
|
+
*/ const SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
|
24
|
+
// A per-boot fallback keeps dev working; production must set the env so
|
|
25
|
+
// sessions survive deploys and instances agree.
|
|
26
|
+
//
|
|
27
|
+
// `||`, NOT `??`, and on a self-host install that is the whole difference
|
|
28
|
+
// (AGL-2014). `.env.selfhost.example` ships `MEMBER_SESSION_SECRET=` blank and
|
|
29
|
+
// compose `env_file` sets a blank line as the EMPTY STRING rather than leaving
|
|
30
|
+
// it unset — so `??` did not fire, `SESSION_SECRET` was `''`, and `createHmac`
|
|
31
|
+
// signs happily with an empty key. Every storefront member session cookie on a
|
|
32
|
+
// template-following deployment was therefore signed with a key any reader of
|
|
33
|
+
// this repository already has, i.e. forgeable. `||` restores the per-boot
|
|
34
|
+
// random the comment above describes and the env template already promises
|
|
35
|
+
// ("without it, sessions don't survive a restart and disagree across
|
|
36
|
+
// instances") — unforgeable, and visibly degraded rather than silently open.
|
|
37
|
+
//
|
|
38
|
+
// Nothing caught it because `tools/scripts/selfhost-ci-env.mjs` FILLS this
|
|
39
|
+
// variable before the image build, so the one job that builds these images
|
|
40
|
+
// tests a configuration no operator following the template has.
|
|
41
|
+
const SESSION_SECRET = process.env.MEMBER_SESSION_SECRET || randomBytes(32).toString('hex');
|
|
42
|
+
export function hashMemberPassword(password) {
|
|
43
|
+
const salt = randomBytes(16).toString('hex');
|
|
44
|
+
const hash = scryptSync(password, salt, 64).toString('hex');
|
|
45
|
+
return `${salt}:${hash}`;
|
|
46
|
+
}
|
|
47
|
+
export function verifyMemberPassword(password, stored) {
|
|
48
|
+
const [salt, hash] = String(stored != null ? stored : '').split(':');
|
|
49
|
+
if (!salt || !hash) return false;
|
|
50
|
+
const candidate = scryptSync(password, salt, 64);
|
|
51
|
+
const expected = Buffer.from(hash, 'hex');
|
|
52
|
+
return candidate.length === expected.length && timingSafeEqual(new Uint8Array(candidate), new Uint8Array(expected));
|
|
53
|
+
}
|
|
54
|
+
function sign(payload) {
|
|
55
|
+
return createHmac('sha256', SESSION_SECRET).update(payload).digest('hex');
|
|
56
|
+
}
|
|
57
|
+
export function mintMemberSession(hostId, memberId) {
|
|
58
|
+
const payload = `${hostId}.${memberId}.${Date.now() + SESSION_TTL_MS}`;
|
|
59
|
+
return `${payload}.${sign(payload)}`;
|
|
60
|
+
}
|
|
61
|
+
/** Returns the session contents when the cookie verifies for this host. */ export function parseMemberSession(hostId, token) {
|
|
62
|
+
const parts = String(token != null ? token : '').split('.');
|
|
63
|
+
if (parts.length !== 4) return null;
|
|
64
|
+
const [tokenHost, memberId, expires, signature] = parts;
|
|
65
|
+
if (tokenHost !== hostId) return null;
|
|
66
|
+
if (Number(expires) < Date.now()) return null;
|
|
67
|
+
const payload = `${tokenHost}.${memberId}.${expires}`;
|
|
68
|
+
const expected = sign(payload);
|
|
69
|
+
const a = Buffer.from(signature);
|
|
70
|
+
const b = Buffer.from(expected);
|
|
71
|
+
if (a.length !== b.length) return null;
|
|
72
|
+
if (!timingSafeEqual(new Uint8Array(a), new Uint8Array(b))) return null;
|
|
73
|
+
return {
|
|
74
|
+
memberId,
|
|
75
|
+
issuedAtMs: Number(expires) - SESSION_TTL_MS
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/** Returns the memberId when the cookie verifies for this host. */ export function verifyMemberSession(hostId, token) {
|
|
79
|
+
var _ref;
|
|
80
|
+
var _parseMemberSession;
|
|
81
|
+
return (_ref = (_parseMemberSession = parseMemberSession(hostId, token)) == null ? void 0 : _parseMemberSession.memberId) != null ? _ref : null;
|
|
82
|
+
}
|
|
83
|
+
/** Password-reset links live for one hour (AGL-552). */ const RESET_TOKEN_TTL_MS = 60 * 60 * 1000;
|
|
84
|
+
/**
|
|
85
|
+
* Fingerprint of the member's CURRENT scrypt hash, folded into the reset
|
|
86
|
+
* token signature (AGL-552): completing a reset rewrites `passwordScrypt`,
|
|
87
|
+
* so every token minted before it stops verifying — single-use without any
|
|
88
|
+
* server-side token storage.
|
|
89
|
+
*/ function passwordFingerprint(passwordScrypt) {
|
|
90
|
+
return createHash('sha256').update(String(passwordScrypt != null ? passwordScrypt : '')).digest('hex');
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Single-use, time-boxed password-reset token (AGL-552):
|
|
94
|
+
* `hostId.memberId.expiry.HMAC(payload + password-fingerprint)`. Same
|
|
95
|
+
* secret as sessions; the fingerprint binding is what makes it one-shot.
|
|
96
|
+
*/ export function mintPasswordResetToken(hostId, memberId, passwordScrypt) {
|
|
97
|
+
const payload = `${hostId}.${memberId}.${Date.now() + RESET_TOKEN_TTL_MS}`;
|
|
98
|
+
return `${payload}.${sign(`${payload}.${passwordFingerprint(passwordScrypt)}`)}`;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* First step of reset verification: extract the memberId from a
|
|
102
|
+
* well-formed, unexpired token for THIS host. The signature is NOT checked
|
|
103
|
+
* here (it binds to the member's current password hash, which the caller
|
|
104
|
+
* has to load first) — never trust the result without a follow-up
|
|
105
|
+
* {@link verifyPasswordResetToken}.
|
|
106
|
+
*/ export function passwordResetTokenMemberId(hostId, token) {
|
|
107
|
+
const parts = String(token != null ? token : '').split('.');
|
|
108
|
+
if (parts.length !== 4) return null;
|
|
109
|
+
const [tokenHost, memberId, expires] = parts;
|
|
110
|
+
if (tokenHost !== hostId) return null;
|
|
111
|
+
if (!memberId) return null;
|
|
112
|
+
if (Number(expires) < Date.now()) return null;
|
|
113
|
+
return memberId;
|
|
114
|
+
}
|
|
115
|
+
/** Full reset-token check: host, expiry, and password-hash binding. */ export function verifyPasswordResetToken(hostId, token, currentPasswordScrypt) {
|
|
116
|
+
const parts = String(token != null ? token : '').split('.');
|
|
117
|
+
if (parts.length !== 4) return false;
|
|
118
|
+
const [tokenHost, memberId, expires, signature] = parts;
|
|
119
|
+
if (tokenHost !== hostId || !memberId) return false;
|
|
120
|
+
if (Number(expires) < Date.now()) return false;
|
|
121
|
+
const payload = `${tokenHost}.${memberId}.${expires}`;
|
|
122
|
+
const expected = sign(`${payload}.${passwordFingerprint(currentPasswordScrypt)}`);
|
|
123
|
+
const a = Buffer.from(signature);
|
|
124
|
+
const b = Buffer.from(expected);
|
|
125
|
+
if (a.length !== b.length) return false;
|
|
126
|
+
return timingSafeEqual(new Uint8Array(a), new Uint8Array(b));
|
|
127
|
+
}
|
|
128
|
+
export function memberCookieName(hostId) {
|
|
129
|
+
return `aglyn_member_${hostId.replace(/[^A-Za-z0-9_-]/g, '')}`;
|
|
130
|
+
}
|
|
131
|
+
export function setMemberCookie(res, hostId, token) {
|
|
132
|
+
const name = memberCookieName(hostId);
|
|
133
|
+
// Secure in production (AGL-500): the member session is an HMAC-signed
|
|
134
|
+
// credential, so it must never travel over plaintext HTTP on a tenant
|
|
135
|
+
// custom domain. Omit only in local dev (http://localhost), matching the
|
|
136
|
+
// console __session cookie.
|
|
137
|
+
const secure = process.env.NODE_ENV === 'production' ? '; Secure' : '';
|
|
138
|
+
const base = `${name}=${token != null ? token : ''}; Path=/; HttpOnly; SameSite=Lax${secure}`;
|
|
139
|
+
res.setHeader('Set-Cookie', token ? `${base}; Max-Age=${30 * 24 * 60 * 60}` : `${base}; Max-Age=0`);
|
|
140
|
+
}
|
|
141
|
+
export function readMemberSession(req, hostId) {
|
|
142
|
+
var _req_cookies;
|
|
143
|
+
const raw = (_req_cookies = req.cookies) == null ? void 0 : _req_cookies[memberCookieName(hostId)];
|
|
144
|
+
return verifyMemberSession(hostId, raw);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Field name for the bulk session cut-off (AGL-914). Set to `Date.now()`
|
|
148
|
+
* whenever every existing session for a member must stop working — today
|
|
149
|
+
* only when an admin sets their password from the console, since a rewritten
|
|
150
|
+
* `passwordScrypt` does NOT invalidate sessions on its own (unlike a reset
|
|
151
|
+
* TOKEN, which binds to the hash by design).
|
|
152
|
+
*/ export const MEMBER_SESSIONS_VALID_FROM_FIELD = 'sessionsValidFromMs';
|
|
153
|
+
/** Suspension rejection body — matches the login flow's AGL-546 message. */ export const MEMBER_SUSPENDED_ERROR = 'This account has been suspended. Contact the site owner.';
|
|
154
|
+
/**
|
|
155
|
+
* Session + member-doc resolution for cookie-authenticated endpoints
|
|
156
|
+
* (AGL-550). A verified cookie alone stopped being enough once console
|
|
157
|
+
* suspension (AGL-546) landed: the HMAC session outlives the member's
|
|
158
|
+
* standing, so every endpoint must also load the doc and check
|
|
159
|
+
* `suspended`. That is one doc read per request — acceptable, and most
|
|
160
|
+
* endpoints needed the member doc anyway. A deleted member doc reads as
|
|
161
|
+
* `anonymous` (same as no cookie).
|
|
162
|
+
*/ export async function readActiveMemberSession(req, hostId) {
|
|
163
|
+
var _member_get;
|
|
164
|
+
var _req_cookies;
|
|
165
|
+
const session = parseMemberSession(hostId, (_req_cookies = req.cookies) == null ? void 0 : _req_cookies[memberCookieName(hostId)]);
|
|
166
|
+
if (!session) return {
|
|
167
|
+
status: 'anonymous'
|
|
168
|
+
};
|
|
169
|
+
const { memberId, issuedAtMs } = session;
|
|
170
|
+
const member = await firebaseAdmin.app().firestore().collection('hosts').doc(hostId).collection('siteMembers').doc(memberId).get();
|
|
171
|
+
if (!member.exists) return {
|
|
172
|
+
status: 'anonymous'
|
|
173
|
+
};
|
|
174
|
+
// Revoked in bulk (AGL-914): a session minted before the cut-off is dead
|
|
175
|
+
// even though its signature is still good. Reads as `anonymous` rather
|
|
176
|
+
// than `suspended` — the account is fine, this browser just holds a stale
|
|
177
|
+
// cookie, and signing in again is the correct next step.
|
|
178
|
+
const validFrom = Number((_member_get = member.get(MEMBER_SESSIONS_VALID_FROM_FIELD)) != null ? _member_get : 0);
|
|
179
|
+
if (validFrom && issuedAtMs < validFrom) return {
|
|
180
|
+
status: 'anonymous'
|
|
181
|
+
};
|
|
182
|
+
if (member.get('suspended') === true) {
|
|
183
|
+
return {
|
|
184
|
+
status: 'suspended',
|
|
185
|
+
memberId
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
status: 'active',
|
|
190
|
+
memberId,
|
|
191
|
+
member
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Guard for cookie-authenticated member endpoints (AGL-550): resolves
|
|
196
|
+
* the session AND the member doc, writing the rejection itself — 401
|
|
197
|
+
* (`signInError`) when there is no usable session, 403 with the session
|
|
198
|
+
* cookie cleared (mirroring membership-account's AGL-546 handling, so
|
|
199
|
+
* the site stops presenting a signed-in shell) when the member is
|
|
200
|
+
* suspended. Returns null after responding; handlers `if (!auth) return`.
|
|
201
|
+
*/ export async function requireActiveMember(req, res, hostId, signInError = 'Not signed in') {
|
|
202
|
+
const session = await readActiveMemberSession(req, hostId);
|
|
203
|
+
if (session.status === 'suspended') {
|
|
204
|
+
setMemberCookie(res, hostId, null);
|
|
205
|
+
res.status(403).json({
|
|
206
|
+
error: MEMBER_SUSPENDED_ERROR
|
|
207
|
+
});
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
if (session.status !== 'active') {
|
|
211
|
+
res.status(401).json({
|
|
212
|
+
error: signInError
|
|
213
|
+
});
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
memberId: session.memberId,
|
|
218
|
+
member: session.member
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
//# sourceMappingURL=membership.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/membership.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n createHash,\n createHmac,\n randomBytes,\n scryptSync,\n timingSafeEqual,\n} from 'crypto'\nimport type { PluginApiRequest, PluginApiResponse } from '@aglyn/aglyn/server'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\n\n/**\n * Site membership primitives (AGL-109): visitor accounts per host with\n * scrypt-hashed passwords and an HMAC-signed httpOnly session cookie.\n * Per-site Firebase Auth is a future infra decision (AGL-45 class); this\n * keeps member content out of static HTML the same way AGL-87 does.\n */\n\nconst SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1000\n\n// A per-boot fallback keeps dev working; production must set the env so\n// sessions survive deploys and instances agree.\n//\n// `||`, NOT `??`, and on a self-host install that is the whole difference\n// (AGL-2014). `.env.selfhost.example` ships `MEMBER_SESSION_SECRET=` blank and\n// compose `env_file` sets a blank line as the EMPTY STRING rather than leaving\n// it unset — so `??` did not fire, `SESSION_SECRET` was `''`, and `createHmac`\n// signs happily with an empty key. Every storefront member session cookie on a\n// template-following deployment was therefore signed with a key any reader of\n// this repository already has, i.e. forgeable. `||` restores the per-boot\n// random the comment above describes and the env template already promises\n// (\"without it, sessions don't survive a restart and disagree across\n// instances\") — unforgeable, and visibly degraded rather than silently open.\n//\n// Nothing caught it because `tools/scripts/selfhost-ci-env.mjs` FILLS this\n// variable before the image build, so the one job that builds these images\n// tests a configuration no operator following the template has.\nconst SESSION_SECRET =\n process.env.MEMBER_SESSION_SECRET || randomBytes(32).toString('hex')\n\nexport function hashMemberPassword(password: string): string {\n const salt = randomBytes(16).toString('hex')\n const hash = scryptSync(password, salt, 64).toString('hex')\n return `${salt}:${hash}`\n}\n\nexport function verifyMemberPassword(\n password: string,\n stored: string | undefined,\n): boolean {\n const [salt, hash] = String(stored ?? '').split(':')\n if (!salt || !hash) return false\n const candidate = scryptSync(password, salt, 64)\n const expected = Buffer.from(hash, 'hex')\n return (\n candidate.length === expected.length &&\n timingSafeEqual(new Uint8Array(candidate), new Uint8Array(expected))\n )\n}\n\nfunction sign(payload: string): string {\n return createHmac('sha256', SESSION_SECRET).update(payload).digest('hex')\n}\n\nexport function mintMemberSession(hostId: string, memberId: string): string {\n const payload = `${hostId}.${memberId}.${Date.now() + SESSION_TTL_MS}`\n return `${payload}.${sign(payload)}`\n}\n\n/**\n * Verified session contents. `issuedAtMs` is derived, not stored: the\n * payload carries `now + TTL`, so subtracting the TTL recovers the mint\n * time. That is what lets sessions be revoked in bulk (AGL-914) without\n * changing the token format or invalidating cookies already in the wild.\n */\nexport interface MemberSessionToken {\n memberId: string\n issuedAtMs: number\n}\n\n/** Returns the session contents when the cookie verifies for this host. */\nexport function parseMemberSession(\n hostId: string,\n token: string | undefined,\n): MemberSessionToken | null {\n const parts = String(token ?? '').split('.')\n if (parts.length !== 4) return null\n const [tokenHost, memberId, expires, signature] = parts\n if (tokenHost !== hostId) return null\n if (Number(expires) < Date.now()) return null\n const payload = `${tokenHost}.${memberId}.${expires}`\n const expected = sign(payload)\n const a = Buffer.from(signature)\n const b = Buffer.from(expected)\n if (a.length !== b.length) return null\n if (!timingSafeEqual(new Uint8Array(a), new Uint8Array(b))) return null\n return { memberId, issuedAtMs: Number(expires) - SESSION_TTL_MS }\n}\n\n/** Returns the memberId when the cookie verifies for this host. */\nexport function verifyMemberSession(\n hostId: string,\n token: string | undefined,\n): string | null {\n return parseMemberSession(hostId, token)?.memberId ?? null\n}\n\n/** Password-reset links live for one hour (AGL-552). */\nconst RESET_TOKEN_TTL_MS = 60 * 60 * 1000\n\n/**\n * Fingerprint of the member's CURRENT scrypt hash, folded into the reset\n * token signature (AGL-552): completing a reset rewrites `passwordScrypt`,\n * so every token minted before it stops verifying — single-use without any\n * server-side token storage.\n */\nfunction passwordFingerprint(passwordScrypt: string | undefined): string {\n return createHash('sha256')\n .update(String(passwordScrypt ?? ''))\n .digest('hex')\n}\n\n/**\n * Single-use, time-boxed password-reset token (AGL-552):\n * `hostId.memberId.expiry.HMAC(payload + password-fingerprint)`. Same\n * secret as sessions; the fingerprint binding is what makes it one-shot.\n */\nexport function mintPasswordResetToken(\n hostId: string,\n memberId: string,\n passwordScrypt: string | undefined,\n): string {\n const payload = `${hostId}.${memberId}.${Date.now() + RESET_TOKEN_TTL_MS}`\n return `${payload}.${sign(\n `${payload}.${passwordFingerprint(passwordScrypt)}`,\n )}`\n}\n\n/**\n * First step of reset verification: extract the memberId from a\n * well-formed, unexpired token for THIS host. The signature is NOT checked\n * here (it binds to the member's current password hash, which the caller\n * has to load first) — never trust the result without a follow-up\n * {@link verifyPasswordResetToken}.\n */\nexport function passwordResetTokenMemberId(\n hostId: string,\n token: string | undefined,\n): string | null {\n const parts = String(token ?? '').split('.')\n if (parts.length !== 4) return null\n const [tokenHost, memberId, expires] = parts\n if (tokenHost !== hostId) return null\n if (!memberId) return null\n if (Number(expires) < Date.now()) return null\n return memberId\n}\n\n/** Full reset-token check: host, expiry, and password-hash binding. */\nexport function verifyPasswordResetToken(\n hostId: string,\n token: string | undefined,\n currentPasswordScrypt: string | undefined,\n): boolean {\n const parts = String(token ?? '').split('.')\n if (parts.length !== 4) return false\n const [tokenHost, memberId, expires, signature] = parts\n if (tokenHost !== hostId || !memberId) return false\n if (Number(expires) < Date.now()) return false\n const payload = `${tokenHost}.${memberId}.${expires}`\n const expected = sign(\n `${payload}.${passwordFingerprint(currentPasswordScrypt)}`,\n )\n const a = Buffer.from(signature)\n const b = Buffer.from(expected)\n if (a.length !== b.length) return false\n return timingSafeEqual(new Uint8Array(a), new Uint8Array(b))\n}\n\nexport function memberCookieName(hostId: string): string {\n return `aglyn_member_${hostId.replace(/[^A-Za-z0-9_-]/g, '')}`\n}\n\nexport function setMemberCookie(\n res: PluginApiResponse,\n hostId: string,\n token: string | null,\n): void {\n const name = memberCookieName(hostId)\n // Secure in production (AGL-500): the member session is an HMAC-signed\n // credential, so it must never travel over plaintext HTTP on a tenant\n // custom domain. Omit only in local dev (http://localhost), matching the\n // console __session cookie.\n const secure = process.env.NODE_ENV === 'production' ? '; Secure' : ''\n const base = `${name}=${token ?? ''}; Path=/; HttpOnly; SameSite=Lax${secure}`\n res.setHeader(\n 'Set-Cookie',\n token ? `${base}; Max-Age=${30 * 24 * 60 * 60}` : `${base}; Max-Age=0`,\n )\n}\n\nexport function readMemberSession(\n req: PluginApiRequest,\n hostId: string,\n): string | null {\n const raw = req.cookies?.[memberCookieName(hostId)]\n return verifyMemberSession(hostId, raw)\n}\n\n/**\n * Field name for the bulk session cut-off (AGL-914). Set to `Date.now()`\n * whenever every existing session for a member must stop working — today\n * only when an admin sets their password from the console, since a rewritten\n * `passwordScrypt` does NOT invalidate sessions on its own (unlike a reset\n * TOKEN, which binds to the hash by design).\n */\nexport const MEMBER_SESSIONS_VALID_FROM_FIELD = 'sessionsValidFromMs'\n\n/** Suspension rejection body — matches the login flow's AGL-546 message. */\nexport const MEMBER_SUSPENDED_ERROR =\n 'This account has been suspended. Contact the site owner.'\n\n/** Resolved member-session state, see {@link readActiveMemberSession}. */\nexport type MemberSessionCheck =\n | { status: 'anonymous' }\n | { status: 'suspended'; memberId: string }\n | {\n status: 'active'\n memberId: string\n member: FirebaseFirestore.DocumentSnapshot\n }\n\n/**\n * Session + member-doc resolution for cookie-authenticated endpoints\n * (AGL-550). A verified cookie alone stopped being enough once console\n * suspension (AGL-546) landed: the HMAC session outlives the member's\n * standing, so every endpoint must also load the doc and check\n * `suspended`. That is one doc read per request — acceptable, and most\n * endpoints needed the member doc anyway. A deleted member doc reads as\n * `anonymous` (same as no cookie).\n */\nexport async function readActiveMemberSession(\n req: PluginApiRequest,\n hostId: string,\n): Promise<MemberSessionCheck> {\n const session = parseMemberSession(\n hostId,\n req.cookies?.[memberCookieName(hostId)],\n )\n if (!session) return { status: 'anonymous' }\n const { memberId, issuedAtMs } = session\n const member = await firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(hostId)\n .collection('siteMembers')\n .doc(memberId)\n .get()\n if (!member.exists) return { status: 'anonymous' }\n // Revoked in bulk (AGL-914): a session minted before the cut-off is dead\n // even though its signature is still good. Reads as `anonymous` rather\n // than `suspended` — the account is fine, this browser just holds a stale\n // cookie, and signing in again is the correct next step.\n const validFrom = Number(member.get(MEMBER_SESSIONS_VALID_FROM_FIELD) ?? 0)\n if (validFrom && issuedAtMs < validFrom) return { status: 'anonymous' }\n if (member.get('suspended') === true) {\n return { status: 'suspended', memberId }\n }\n return { status: 'active', memberId, member }\n}\n\n/**\n * Guard for cookie-authenticated member endpoints (AGL-550): resolves\n * the session AND the member doc, writing the rejection itself — 401\n * (`signInError`) when there is no usable session, 403 with the session\n * cookie cleared (mirroring membership-account's AGL-546 handling, so\n * the site stops presenting a signed-in shell) when the member is\n * suspended. Returns null after responding; handlers `if (!auth) return`.\n */\nexport async function requireActiveMember(\n req: PluginApiRequest,\n res: PluginApiResponse,\n hostId: string,\n signInError = 'Not signed in',\n): Promise<{\n memberId: string\n member: FirebaseFirestore.DocumentSnapshot\n} | null> {\n const session = await readActiveMemberSession(req, hostId)\n if (session.status === 'suspended') {\n setMemberCookie(res, hostId, null)\n res.status(403).json({ error: MEMBER_SUSPENDED_ERROR })\n return null\n }\n if (session.status !== 'active') {\n res.status(401).json({ error: signInError })\n return null\n }\n return { memberId: session.memberId, member: session.member }\n}\n"],"names":["createHash","createHmac","randomBytes","scryptSync","timingSafeEqual","firebaseAdmin","SESSION_TTL_MS","SESSION_SECRET","process","env","MEMBER_SESSION_SECRET","toString","hashMemberPassword","password","salt","hash","verifyMemberPassword","stored","String","split","candidate","expected","Buffer","from","length","Uint8Array","sign","payload","update","digest","mintMemberSession","hostId","memberId","Date","now","parseMemberSession","token","parts","tokenHost","expires","signature","Number","a","b","issuedAtMs","verifyMemberSession","RESET_TOKEN_TTL_MS","passwordFingerprint","passwordScrypt","mintPasswordResetToken","passwordResetTokenMemberId","verifyPasswordResetToken","currentPasswordScrypt","memberCookieName","replace","setMemberCookie","res","name","secure","NODE_ENV","base","setHeader","readMemberSession","req","raw","cookies","MEMBER_SESSIONS_VALID_FROM_FIELD","MEMBER_SUSPENDED_ERROR","readActiveMemberSession","member","session","status","app","firestore","collection","doc","get","exists","validFrom","requireActiveMember","signInError","json","error"],"mappings":"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"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The storefront Payment Element (AGL-1944), the half of native payments
|
|
19
|
+
* AGL-1132 said mattered more: a shopper on a merchant's own domain pays
|
|
20
|
+
* without being bounced to `checkout.stripe.com` and back.
|
|
21
|
+
*
|
|
22
|
+
* ## Why a Checkout Session and not a bare PaymentIntent
|
|
23
|
+
*
|
|
24
|
+
* AGL-1132 recommended "PaymentIntent + `<PaymentElement/>`" for this surface,
|
|
25
|
+
* and AGL-1944 asked for that to be a conscious re-decision rather than an
|
|
26
|
+
* inherited one. It is: the Payment Element ships, the bare PaymentIntent does
|
|
27
|
+
* not, and the difference is worth stating because it is the whole safety
|
|
28
|
+
* argument.
|
|
29
|
+
*
|
|
30
|
+
* A PaymentIntent-backed flow would have to own totals, tax, shipping and the
|
|
31
|
+
* platform fee itself — which means a SECOND place that computes what a shopper
|
|
32
|
+
* owes. Two paths that price a cart independently is how a storefront starts
|
|
33
|
+
* under-collecting tax without anyone noticing: nothing fails, the number is
|
|
34
|
+
* just quietly wrong on one of them. It also emits `payment_intent.succeeded`
|
|
35
|
+
* rather than `checkout.session.completed`, so every fulfilment branch in
|
|
36
|
+
* `billing-webhook.ts` — orders, inventory, coupons, gift cards, licence keys,
|
|
37
|
+
* subscriptions — would need a parallel implementation reading a different
|
|
38
|
+
* object shape.
|
|
39
|
+
*
|
|
40
|
+
* `ui_mode: 'custom'` is the Payment Element backed by a Checkout Session. The
|
|
41
|
+
* shopper sees `<PaymentElement/>` inside the merchant's own layout — no Stripe
|
|
42
|
+
* chrome, which is the thing AGL-1944 objected to about embedded Checkout — and
|
|
43
|
+
* the server keeps the session builder it already has. So:
|
|
44
|
+
*
|
|
45
|
+
* - tax, shipping, inventory, coupons, the Connect destination and the fee
|
|
46
|
+
* ladder are the SAME code, not a parallel implementation. Parity is by
|
|
47
|
+
* construction rather than by test;
|
|
48
|
+
* - `checkout.session.completed` still fires, so `billing-webhook.ts` needs
|
|
49
|
+
* no change at all and stays the only thing that fulfils an order.
|
|
50
|
+
*
|
|
51
|
+
* ## The API version, and why it is pinned per-request
|
|
52
|
+
*
|
|
53
|
+
* Measured against live Stripe in test mode: `ui_mode: 'custom'` is refused at
|
|
54
|
+
* the platform account's default API version with
|
|
55
|
+
*
|
|
56
|
+
* Invalid Stripe API version: 2020-08-27. In order to use `ui_mode:
|
|
57
|
+
* custom`, you must upgrade to Stripe API version 2025-03-31.basil.
|
|
58
|
+
*
|
|
59
|
+
* Upgrading the ACCOUNT default would re-version every Stripe call in this
|
|
60
|
+
* repo at once — a change whose blast radius nobody could review in one pass.
|
|
61
|
+
* A `Stripe-Version` header pins one request instead, and the pin is contained
|
|
62
|
+
* in a way worth being explicit about: webhook deliveries are versioned per
|
|
63
|
+
* ENDPOINT, not per request, so the events `billing-webhook.ts` parses keep
|
|
64
|
+
* arriving in exactly the shape it already reads. The only thing read off the
|
|
65
|
+
* pinned response is `client_secret` and `id`, neither of which the version
|
|
66
|
+
* affects.
|
|
67
|
+
*
|
|
68
|
+
* Same probe, same params (line items, a manual tax line,
|
|
69
|
+
* `shipping_address_collection`, a `fixed_amount` shipping option, Connect
|
|
70
|
+
* `transfer_data[destination]` and `application_fee_amount`), hosted vs custom:
|
|
71
|
+
* `amount_total` **6212 in both**. That is the tax-and-shipping parity claim,
|
|
72
|
+
* measured rather than assumed.
|
|
73
|
+
*
|
|
74
|
+
* ## What this module deliberately does NOT do
|
|
75
|
+
*
|
|
76
|
+
* It does not fulfil anything, and it hands the browser nothing it could use to
|
|
77
|
+
* claim an order was paid. The client gets a `clientSecret` — which is an
|
|
78
|
+
* instruction to Stripe, not an assertion about money — and the same
|
|
79
|
+
* `session_id` the redirect flow already put in the return URL. Fulfilment
|
|
80
|
+
* remains `checkout.session.completed` in `billing-webhook.ts`, exactly as with
|
|
81
|
+
* the redirect. An in-page flow makes trusting the browser tempting; the
|
|
82
|
+
* browser is the one participant that can lie about having paid.
|
|
83
|
+
*/
|
|
84
|
+
/**
|
|
85
|
+
* The API version `ui_mode: 'custom'` needs, pinned on the session-create
|
|
86
|
+
* request only. Stated once so the header and the doc comment cannot drift.
|
|
87
|
+
*/
|
|
88
|
+
export declare const NATIVE_CHECKOUT_STRIPE_VERSION = "2025-03-31.basil";
|
|
89
|
+
/** The flag both halves of native checkout share (AGL-1132, AGL-1944). */
|
|
90
|
+
export declare const NATIVE_CHECKOUT_FLAG: "release_native_checkout";
|
|
91
|
+
export interface NativeCheckoutMode {
|
|
92
|
+
/** True only when the flag is on for this org AND a publishable key exists. */
|
|
93
|
+
native: boolean;
|
|
94
|
+
/** Empty unless `native` — the client needs it to boot Stripe.js. */
|
|
95
|
+
publishableKey: string;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Two conditions, not one — the same pairing the console's route settled on.
|
|
99
|
+
*
|
|
100
|
+
* A `ui_mode` session returns a `client_secret` and NO `url`, so a browser that
|
|
101
|
+
* cannot mount Stripe.js has nothing to navigate to. If the flag alone decided
|
|
102
|
+
* this, flipping it in an environment without
|
|
103
|
+
* `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` would replace a working redirect with a
|
|
104
|
+
* dead Buy button on every storefront at once. Requiring the key here means the
|
|
105
|
+
* worst case of a premature flag flip is the redirect we already ship.
|
|
106
|
+
*
|
|
107
|
+
* That is not hypothetical for the storefront. Measured on 2026-08-18 through
|
|
108
|
+
* the Vercel REST API (`vercel env ls` cannot see team-shared vars):
|
|
109
|
+
* `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` exists as a project-level var on
|
|
110
|
+
* `aglyn-console` and is set NOWHERE on `aglyn-tenant`, which is the project
|
|
111
|
+
* that serves storefronts. So today this resolves to `native: false` in
|
|
112
|
+
* production however the flag is set, and the shopper keeps the redirect.
|
|
113
|
+
*
|
|
114
|
+
* The org — never the host — is the rollout bucket, per AGL-1656: a hostId and
|
|
115
|
+
* an orgId hash to different buckets, so bucketing on the host could land a
|
|
116
|
+
* merchant's storefront and their console on opposite sides of the same
|
|
117
|
+
* percentage.
|
|
118
|
+
*/
|
|
119
|
+
export declare function resolveNativeCheckoutMode(orgId: string | null | undefined): Promise<NativeCheckoutMode>;
|
|
120
|
+
/**
|
|
121
|
+
* Rewrite a built session param set for the Payment Element, in place.
|
|
122
|
+
*
|
|
123
|
+
* DELETES `success_url` and `cancel_url` rather than leaving them alongside
|
|
124
|
+
* `return_url`: Stripe rejects the pair outright on a `ui_mode` session, and a
|
|
125
|
+
* 400 here would surface to the shopper as "Checkout failed" on a store that
|
|
126
|
+
* was working an hour ago. Everything else in the param set — line items, the
|
|
127
|
+
* tax line, `automatic_tax`, shipping options, the Connect destination, the
|
|
128
|
+
* application fee, every `metadata[…]` key the webhook reads — is left exactly
|
|
129
|
+
* as the caller built it. That is the point: this function must be incapable of
|
|
130
|
+
* changing what the shopper is charged.
|
|
131
|
+
*
|
|
132
|
+
* `returnUrl` is the caller's OWN `success_url` string, `{CHECKOUT_SESSION_ID}`
|
|
133
|
+
* placeholder and all. Stripe substitutes it on a `ui_mode: 'custom'` return
|
|
134
|
+
* exactly as it does on a hosted redirect (measured), so the storefront's
|
|
135
|
+
* post-purchase reporting — which reads `session_id` off the query to name the
|
|
136
|
+
* order it just completed (AGL-1641) — keeps working unchanged.
|
|
137
|
+
*/
|
|
138
|
+
export declare function applyNativeCheckoutParams(params: URLSearchParams, returnUrl: string): void;
|
|
139
|
+
/**
|
|
140
|
+
* The `Stripe-Version` pin, as headers to spread into the session-create fetch.
|
|
141
|
+
* Empty for the hosted path so its request is byte-identical to today's.
|
|
142
|
+
*/
|
|
143
|
+
export declare function nativeCheckoutStripeHeaders(mode: NativeCheckoutMode): Record<string, string>;
|
|
144
|
+
export interface NativeCheckoutPayload {
|
|
145
|
+
/** Stripe's own instruction to the browser. Not a claim that money moved. */
|
|
146
|
+
clientSecret: string;
|
|
147
|
+
publishableKey: string;
|
|
148
|
+
/** The order document id, and the `session_id` the return URL carries. */
|
|
149
|
+
sessionId: string;
|
|
150
|
+
}
|
|
151
|
+
export interface HostedCheckoutPayload {
|
|
152
|
+
url: string;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Read the created session into the payload the storefront gets back.
|
|
156
|
+
*
|
|
157
|
+
* Returns `null` when the session is unusable, which BOTH callers already treat
|
|
158
|
+
* as "nothing was sold" — release the idempotency claim and answer 502. The
|
|
159
|
+
* null case is the whole reason this is a function rather than a ternary: the
|
|
160
|
+
* hosted path's liveness check was `!session.url`, and a `ui_mode` session
|
|
161
|
+
* never has one. Left unchanged, switching a store to native mode would have
|
|
162
|
+
* made every successful session look like a Stripe failure — a 502 to the
|
|
163
|
+
* shopper, a released claim, and a real Checkout Session left open on the
|
|
164
|
+
* account. So the check has to move with the mode, and it does.
|
|
165
|
+
*/
|
|
166
|
+
export declare function readCheckoutSessionPayload(session: {
|
|
167
|
+
url?: string;
|
|
168
|
+
id?: string;
|
|
169
|
+
client_secret?: string;
|
|
170
|
+
}, mode: NativeCheckoutMode): NativeCheckoutPayload | HostedCheckoutPayload | null;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { isServerReleaseFlagOnForOrg } from "@aglyn/tenant-data-admin";
|
|
17
|
+
/**
|
|
18
|
+
* The storefront Payment Element (AGL-1944), the half of native payments
|
|
19
|
+
* AGL-1132 said mattered more: a shopper on a merchant's own domain pays
|
|
20
|
+
* without being bounced to `checkout.stripe.com` and back.
|
|
21
|
+
*
|
|
22
|
+
* ## Why a Checkout Session and not a bare PaymentIntent
|
|
23
|
+
*
|
|
24
|
+
* AGL-1132 recommended "PaymentIntent + `<PaymentElement/>`" for this surface,
|
|
25
|
+
* and AGL-1944 asked for that to be a conscious re-decision rather than an
|
|
26
|
+
* inherited one. It is: the Payment Element ships, the bare PaymentIntent does
|
|
27
|
+
* not, and the difference is worth stating because it is the whole safety
|
|
28
|
+
* argument.
|
|
29
|
+
*
|
|
30
|
+
* A PaymentIntent-backed flow would have to own totals, tax, shipping and the
|
|
31
|
+
* platform fee itself — which means a SECOND place that computes what a shopper
|
|
32
|
+
* owes. Two paths that price a cart independently is how a storefront starts
|
|
33
|
+
* under-collecting tax without anyone noticing: nothing fails, the number is
|
|
34
|
+
* just quietly wrong on one of them. It also emits `payment_intent.succeeded`
|
|
35
|
+
* rather than `checkout.session.completed`, so every fulfilment branch in
|
|
36
|
+
* `billing-webhook.ts` — orders, inventory, coupons, gift cards, licence keys,
|
|
37
|
+
* subscriptions — would need a parallel implementation reading a different
|
|
38
|
+
* object shape.
|
|
39
|
+
*
|
|
40
|
+
* `ui_mode: 'custom'` is the Payment Element backed by a Checkout Session. The
|
|
41
|
+
* shopper sees `<PaymentElement/>` inside the merchant's own layout — no Stripe
|
|
42
|
+
* chrome, which is the thing AGL-1944 objected to about embedded Checkout — and
|
|
43
|
+
* the server keeps the session builder it already has. So:
|
|
44
|
+
*
|
|
45
|
+
* - tax, shipping, inventory, coupons, the Connect destination and the fee
|
|
46
|
+
* ladder are the SAME code, not a parallel implementation. Parity is by
|
|
47
|
+
* construction rather than by test;
|
|
48
|
+
* - `checkout.session.completed` still fires, so `billing-webhook.ts` needs
|
|
49
|
+
* no change at all and stays the only thing that fulfils an order.
|
|
50
|
+
*
|
|
51
|
+
* ## The API version, and why it is pinned per-request
|
|
52
|
+
*
|
|
53
|
+
* Measured against live Stripe in test mode: `ui_mode: 'custom'` is refused at
|
|
54
|
+
* the platform account's default API version with
|
|
55
|
+
*
|
|
56
|
+
* Invalid Stripe API version: 2020-08-27. In order to use `ui_mode:
|
|
57
|
+
* custom`, you must upgrade to Stripe API version 2025-03-31.basil.
|
|
58
|
+
*
|
|
59
|
+
* Upgrading the ACCOUNT default would re-version every Stripe call in this
|
|
60
|
+
* repo at once — a change whose blast radius nobody could review in one pass.
|
|
61
|
+
* A `Stripe-Version` header pins one request instead, and the pin is contained
|
|
62
|
+
* in a way worth being explicit about: webhook deliveries are versioned per
|
|
63
|
+
* ENDPOINT, not per request, so the events `billing-webhook.ts` parses keep
|
|
64
|
+
* arriving in exactly the shape it already reads. The only thing read off the
|
|
65
|
+
* pinned response is `client_secret` and `id`, neither of which the version
|
|
66
|
+
* affects.
|
|
67
|
+
*
|
|
68
|
+
* Same probe, same params (line items, a manual tax line,
|
|
69
|
+
* `shipping_address_collection`, a `fixed_amount` shipping option, Connect
|
|
70
|
+
* `transfer_data[destination]` and `application_fee_amount`), hosted vs custom:
|
|
71
|
+
* `amount_total` **6212 in both**. That is the tax-and-shipping parity claim,
|
|
72
|
+
* measured rather than assumed.
|
|
73
|
+
*
|
|
74
|
+
* ## What this module deliberately does NOT do
|
|
75
|
+
*
|
|
76
|
+
* It does not fulfil anything, and it hands the browser nothing it could use to
|
|
77
|
+
* claim an order was paid. The client gets a `clientSecret` — which is an
|
|
78
|
+
* instruction to Stripe, not an assertion about money — and the same
|
|
79
|
+
* `session_id` the redirect flow already put in the return URL. Fulfilment
|
|
80
|
+
* remains `checkout.session.completed` in `billing-webhook.ts`, exactly as with
|
|
81
|
+
* the redirect. An in-page flow makes trusting the browser tempting; the
|
|
82
|
+
* browser is the one participant that can lie about having paid.
|
|
83
|
+
*/ /**
|
|
84
|
+
* The API version `ui_mode: 'custom'` needs, pinned on the session-create
|
|
85
|
+
* request only. Stated once so the header and the doc comment cannot drift.
|
|
86
|
+
*/ export const NATIVE_CHECKOUT_STRIPE_VERSION = '2025-03-31.basil';
|
|
87
|
+
/** The flag both halves of native checkout share (AGL-1132, AGL-1944). */ export const NATIVE_CHECKOUT_FLAG = 'release_native_checkout';
|
|
88
|
+
/**
|
|
89
|
+
* Two conditions, not one — the same pairing the console's route settled on.
|
|
90
|
+
*
|
|
91
|
+
* A `ui_mode` session returns a `client_secret` and NO `url`, so a browser that
|
|
92
|
+
* cannot mount Stripe.js has nothing to navigate to. If the flag alone decided
|
|
93
|
+
* this, flipping it in an environment without
|
|
94
|
+
* `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` would replace a working redirect with a
|
|
95
|
+
* dead Buy button on every storefront at once. Requiring the key here means the
|
|
96
|
+
* worst case of a premature flag flip is the redirect we already ship.
|
|
97
|
+
*
|
|
98
|
+
* That is not hypothetical for the storefront. Measured on 2026-08-18 through
|
|
99
|
+
* the Vercel REST API (`vercel env ls` cannot see team-shared vars):
|
|
100
|
+
* `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` exists as a project-level var on
|
|
101
|
+
* `aglyn-console` and is set NOWHERE on `aglyn-tenant`, which is the project
|
|
102
|
+
* that serves storefronts. So today this resolves to `native: false` in
|
|
103
|
+
* production however the flag is set, and the shopper keeps the redirect.
|
|
104
|
+
*
|
|
105
|
+
* The org — never the host — is the rollout bucket, per AGL-1656: a hostId and
|
|
106
|
+
* an orgId hash to different buckets, so bucketing on the host could land a
|
|
107
|
+
* merchant's storefront and their console on opposite sides of the same
|
|
108
|
+
* percentage.
|
|
109
|
+
*/ export async function resolveNativeCheckoutMode(orgId) {
|
|
110
|
+
var _process_env_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
|
|
111
|
+
const publishableKey = String((_process_env_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY) != null ? _process_env_NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY : '').trim();
|
|
112
|
+
if (!publishableKey) return {
|
|
113
|
+
native: false,
|
|
114
|
+
publishableKey: ''
|
|
115
|
+
};
|
|
116
|
+
// A flag lookup that throws must not take a sale down with it, and the catch
|
|
117
|
+
// is a `try` rather than `.catch()` deliberately: Remote Config being
|
|
118
|
+
// unreachable rejects, but the resolver being absent entirely — an
|
|
119
|
+
// environment where the module never loaded — throws SYNCHRONOUSLY, before
|
|
120
|
+
// there is a promise to attach a handler to. Only the block form covers both.
|
|
121
|
+
// Either way the answer is the same and it is the safe one: the redirect.
|
|
122
|
+
let on;
|
|
123
|
+
try {
|
|
124
|
+
on = await isServerReleaseFlagOnForOrg(NATIVE_CHECKOUT_FLAG, orgId != null ? orgId : null);
|
|
125
|
+
} catch (unused) {
|
|
126
|
+
return {
|
|
127
|
+
native: false,
|
|
128
|
+
publishableKey: ''
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return on ? {
|
|
132
|
+
native: true,
|
|
133
|
+
publishableKey
|
|
134
|
+
} : {
|
|
135
|
+
native: false,
|
|
136
|
+
publishableKey: ''
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Rewrite a built session param set for the Payment Element, in place.
|
|
141
|
+
*
|
|
142
|
+
* DELETES `success_url` and `cancel_url` rather than leaving them alongside
|
|
143
|
+
* `return_url`: Stripe rejects the pair outright on a `ui_mode` session, and a
|
|
144
|
+
* 400 here would surface to the shopper as "Checkout failed" on a store that
|
|
145
|
+
* was working an hour ago. Everything else in the param set — line items, the
|
|
146
|
+
* tax line, `automatic_tax`, shipping options, the Connect destination, the
|
|
147
|
+
* application fee, every `metadata[…]` key the webhook reads — is left exactly
|
|
148
|
+
* as the caller built it. That is the point: this function must be incapable of
|
|
149
|
+
* changing what the shopper is charged.
|
|
150
|
+
*
|
|
151
|
+
* `returnUrl` is the caller's OWN `success_url` string, `{CHECKOUT_SESSION_ID}`
|
|
152
|
+
* placeholder and all. Stripe substitutes it on a `ui_mode: 'custom'` return
|
|
153
|
+
* exactly as it does on a hosted redirect (measured), so the storefront's
|
|
154
|
+
* post-purchase reporting — which reads `session_id` off the query to name the
|
|
155
|
+
* order it just completed (AGL-1641) — keeps working unchanged.
|
|
156
|
+
*/ export function applyNativeCheckoutParams(params, returnUrl) {
|
|
157
|
+
params.delete('success_url');
|
|
158
|
+
params.delete('cancel_url');
|
|
159
|
+
params.set('ui_mode', 'custom');
|
|
160
|
+
params.set('return_url', returnUrl);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* The `Stripe-Version` pin, as headers to spread into the session-create fetch.
|
|
164
|
+
* Empty for the hosted path so its request is byte-identical to today's.
|
|
165
|
+
*/ export function nativeCheckoutStripeHeaders(mode) {
|
|
166
|
+
return mode.native ? {
|
|
167
|
+
'Stripe-Version': NATIVE_CHECKOUT_STRIPE_VERSION
|
|
168
|
+
} : {};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Read the created session into the payload the storefront gets back.
|
|
172
|
+
*
|
|
173
|
+
* Returns `null` when the session is unusable, which BOTH callers already treat
|
|
174
|
+
* as "nothing was sold" — release the idempotency claim and answer 502. The
|
|
175
|
+
* null case is the whole reason this is a function rather than a ternary: the
|
|
176
|
+
* hosted path's liveness check was `!session.url`, and a `ui_mode` session
|
|
177
|
+
* never has one. Left unchanged, switching a store to native mode would have
|
|
178
|
+
* made every successful session look like a Stripe failure — a 502 to the
|
|
179
|
+
* shopper, a released claim, and a real Checkout Session left open on the
|
|
180
|
+
* account. So the check has to move with the mode, and it does.
|
|
181
|
+
*/ export function readCheckoutSessionPayload(session, mode) {
|
|
182
|
+
if (!mode.native) return (session == null ? void 0 : session.url) ? {
|
|
183
|
+
url: session.url
|
|
184
|
+
} : null;
|
|
185
|
+
if (!(session == null ? void 0 : session.client_secret) || !(session == null ? void 0 : session.id)) return null;
|
|
186
|
+
return {
|
|
187
|
+
clientSecret: String(session.client_secret),
|
|
188
|
+
publishableKey: mode.publishableKey,
|
|
189
|
+
sessionId: String(session.id)
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
//# sourceMappingURL=native-checkout.js.map
|