@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,199 @@
|
|
|
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
|
+
import type { HostProduct } from './commerce';
|
|
18
|
+
/**
|
|
19
|
+
* RESERVED STOCK: the arithmetic half of the checkout hold (AGL-2356).
|
|
20
|
+
*
|
|
21
|
+
* ## The defect this closes
|
|
22
|
+
*
|
|
23
|
+
* AGL-2320 made every stock decrement atomic, so a decrement is never LOST and
|
|
24
|
+
* the count never shows phantom stock. It could not make the storefront REFUSE
|
|
25
|
+
* an oversell, and said so: the gate and the decrement are minutes apart with
|
|
26
|
+
* money moving in between.
|
|
27
|
+
*
|
|
28
|
+
* - `checkout.ts` / `cart-checkout.ts` called `canPurchase` against a plain
|
|
29
|
+
* `.get()` when the Stripe Checkout Session was created. Nothing was
|
|
30
|
+
* written, so there was no document to contend on — N concurrent shoppers
|
|
31
|
+
* all read `inventory: 1`, all passed, and no locking discipline anywhere
|
|
32
|
+
* could change that, because a read races nothing.
|
|
33
|
+
* - the decrement happened in `billing-webhook.ts` when the session
|
|
34
|
+
* completed. By then the shopper has paid, so refusing there would take
|
|
35
|
+
* money and withhold goods — worse than the oversell.
|
|
36
|
+
*
|
|
37
|
+
* The only correct place to refuse is session creation, and refusing there
|
|
38
|
+
* requires WRITING. That is what a hold is: a unit spoken for by a live
|
|
39
|
+
* checkout, subtracted from what the next shopper is offered.
|
|
40
|
+
*
|
|
41
|
+
* ## Why the hold does not touch `inventory`
|
|
42
|
+
*
|
|
43
|
+
* `inventory` means UNITS ON THE SHELF and half the product reads it that way —
|
|
44
|
+
* the low-stock crossing alert, the restock queue, `reconcile-stock.ts`, the
|
|
45
|
+
* ledger's `appliedDelta` cap, the console's own count. A hold is not a unit
|
|
46
|
+
* leaving the shelf; it is a unit promised to someone who has not paid yet.
|
|
47
|
+
* Decrementing at hold time and "converting" at settlement would make every one
|
|
48
|
+
* of those readers wrong for the duration of a checkout, and would make a
|
|
49
|
+
* released hold indistinguishable from a restock in the ledger.
|
|
50
|
+
*
|
|
51
|
+
* So the shelf count is untouched and AVAILABILITY becomes a derived figure:
|
|
52
|
+
* `inventory` minus the live holds. `decrementVariantStock` is unchanged, which
|
|
53
|
+
* is the point — settlement is still the same single atomic decrement it was
|
|
54
|
+
* before this issue, and the hold is simply dropped afterwards.
|
|
55
|
+
*
|
|
56
|
+
* ## Why a lapsed hold is a release
|
|
57
|
+
*
|
|
58
|
+
* Every read prunes (`pruneStockHolds`), so a hold whose release never arrived
|
|
59
|
+
* lapses on its own the next time anybody asks. That is what makes a leaked
|
|
60
|
+
* hold a bounded inconvenience rather than the unbounded, invisible UNDER-sell
|
|
61
|
+
* this issue was held back for: stock is never stranded waiting on a webhook.
|
|
62
|
+
* The explicit release paths exist so the merchant and the shopper see the unit
|
|
63
|
+
* come back promptly, not because correctness depends on them.
|
|
64
|
+
*/
|
|
65
|
+
/** One live reservation on a product, keyed by checkout attempt. */
|
|
66
|
+
export interface StockHold {
|
|
67
|
+
/** Wall-clock ms after which this hold is void. */
|
|
68
|
+
expiresAtMs: number;
|
|
69
|
+
/**
|
|
70
|
+
* Variant id → units held. A map and not a single number because one cart
|
|
71
|
+
* can hold two variants of one product, and a product document is the thing
|
|
72
|
+
* the transaction contends on — collapsing them would make a two-variant
|
|
73
|
+
* cart reserve the wrong shelf.
|
|
74
|
+
*/
|
|
75
|
+
units?: Record<string, number>;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The product fields this file reads. Structural on purpose: `HostProduct`
|
|
79
|
+
* carries forty fields and the hold arithmetic must not be able to depend on
|
|
80
|
+
* any of them but these.
|
|
81
|
+
*/
|
|
82
|
+
export interface HeldStockProduct extends Pick<HostProduct, 'variants' | 'oversellPolicy'> {
|
|
83
|
+
/** Hold key → hold. Absent on every product written before AGL-2356. */
|
|
84
|
+
stockHolds?: Record<string, StockHold>;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* HOW LONG A UNIT STAYS SPOKEN FOR.
|
|
88
|
+
*
|
|
89
|
+
* This number is not free to choose. A hold that lapses while its Checkout
|
|
90
|
+
* Session can still be PAID reopens the exact window this file closes: the
|
|
91
|
+
* shopper pays, the webhook decrements, and the unit was sold to someone else
|
|
92
|
+
* in between. So the floor is the session's own lifetime.
|
|
93
|
+
*
|
|
94
|
+
* Stripe's default session lifetime is 24 HOURS, and a 24-hour stock hold is
|
|
95
|
+
* precisely the trade this issue was blocked on — one abandoned cart standing
|
|
96
|
+
* on a merchant's last unit for a day. The resolution is not to shorten the
|
|
97
|
+
* hold below the session; it is to shorten the SESSION. `checkout.ts` and
|
|
98
|
+
* `cart-checkout.ts` now send `expires_at` at `CHECKOUT_SESSION_TTL_MS`, which
|
|
99
|
+
* is Stripe's own minimum and the same half-hour the bookings plugin has always
|
|
100
|
+
* given a held room.
|
|
101
|
+
*
|
|
102
|
+
* The hold then outlives the session by `STOCK_HOLD_GRACE_MS`, so the ordering
|
|
103
|
+
* is always: session dies → `checkout.session.expired` releases → the hold was
|
|
104
|
+
* still standing when it did. The grace is what keeps clock skew between Stripe
|
|
105
|
+
* and this process from lapsing a hold under a session that is still payable.
|
|
106
|
+
*/
|
|
107
|
+
/**
|
|
108
|
+
* 31 minutes, not 30. Stripe REJECTS an `expires_at` less than 30 minutes in
|
|
109
|
+
* the future, and it measures against its own clock at the moment the request
|
|
110
|
+
* lands — so a value computed here at exactly the floor fails whenever network
|
|
111
|
+
* latency or clock skew eats a second. The extra minute is the margin; it is
|
|
112
|
+
* not a product opinion, and the product opinion is "the shortest session
|
|
113
|
+
* Stripe will sell us".
|
|
114
|
+
*/
|
|
115
|
+
export declare const CHECKOUT_SESSION_TTL_MS: number;
|
|
116
|
+
/** Cushion between the session expiring and its hold lapsing. */
|
|
117
|
+
export declare const STOCK_HOLD_GRACE_MS: number;
|
|
118
|
+
export declare const STOCK_HOLD_TTL_MS: number;
|
|
119
|
+
/**
|
|
120
|
+
* What a shopper is told when the units exist but are spoken for.
|
|
121
|
+
*
|
|
122
|
+
* Deliberately NOT the bare "Sold out" the empty-shelf refusal uses. The
|
|
123
|
+
* merchant has stock; someone else is in checkout with it. Naming the wait is
|
|
124
|
+
* what stops a shopper concluding the store is empty and leaving — the hold
|
|
125
|
+
* lapses within the half hour and the unit comes back.
|
|
126
|
+
*/
|
|
127
|
+
export declare const STOCK_HELD_MESSAGE: string;
|
|
128
|
+
/**
|
|
129
|
+
* The product's holds with the lapsed ones dropped.
|
|
130
|
+
*
|
|
131
|
+
* Total, not a filter over a trusted shape: a hold whose `expiresAtMs` is
|
|
132
|
+
* absent or non-numeric is treated as EXPIRED rather than eternal. A merchant
|
|
133
|
+
* losing a unit forever to one corrupt map entry is the only direction with no
|
|
134
|
+
* recovery inside the product, and it is exactly the failure mode that kept
|
|
135
|
+
* this issue out of the launch build.
|
|
136
|
+
*/
|
|
137
|
+
export declare function pruneStockHolds(holds: Record<string, StockHold> | undefined, nowMs: number): Record<string, StockHold>;
|
|
138
|
+
/**
|
|
139
|
+
* Units of one variant spoken for by live checkouts right now.
|
|
140
|
+
*
|
|
141
|
+
* `exceptHoldKey` is how a retry of one attempt does not stand in its own way:
|
|
142
|
+
* the shopper presses the same button twice, the second press re-derives the
|
|
143
|
+
* same hold key, and without this exclusion it would be refused the unit it is
|
|
144
|
+
* already holding.
|
|
145
|
+
*/
|
|
146
|
+
export declare function heldVariantUnits(product: HeldStockProduct | undefined, variantId: string | undefined, nowMs: number, exceptHoldKey?: string): number;
|
|
147
|
+
/** Units held across every variant of a product — the console's figure. */
|
|
148
|
+
export declare function heldProductUnits(product: HeldStockProduct | undefined, nowMs: number): number;
|
|
149
|
+
/**
|
|
150
|
+
* Units a NEW checkout could take: the shelf minus the live holds.
|
|
151
|
+
*
|
|
152
|
+
* `null` when there is no number to answer with — an untracked variant, a
|
|
153
|
+
* backorder product (whose merchant chose to sell past zero, so availability is
|
|
154
|
+
* unbounded by construction), or a variant that does not exist. `null` rather
|
|
155
|
+
* than `Infinity` so a caller cannot do arithmetic on "no limit" and get a
|
|
156
|
+
* number, and never negative: a shelf that went short under a `backorder`
|
|
157
|
+
* policy reads as zero available, not as a debt.
|
|
158
|
+
*
|
|
159
|
+
* NOTE FOR THIS REPO: `strictNullChecks` is off, so `if (!available)` does NOT
|
|
160
|
+
* distinguish `null` from `0` here — and `0` is a legitimate, load-bearing
|
|
161
|
+
* answer meaning "tracked, and none left". Every caller must test `== null`.
|
|
162
|
+
*/
|
|
163
|
+
export declare function availableVariantUnits(product: HeldStockProduct | undefined, variantId: string | undefined, nowMs: number, exceptHoldKey?: string): number | null;
|
|
164
|
+
/**
|
|
165
|
+
* Is there a hold to take at all?
|
|
166
|
+
*
|
|
167
|
+
* False for the cases `availableVariantUnits` answers `null` for — an untracked
|
|
168
|
+
* or backorder variant has no finite shelf to reserve against, and writing a
|
|
169
|
+
* hold for one would put a growing map on a document whose availability it can
|
|
170
|
+
* never change. The same shape as `holdPromotionSlot` skipping an UNCAPPED
|
|
171
|
+
* promotion: the unlimited case stays off the write path entirely.
|
|
172
|
+
*/
|
|
173
|
+
export declare function stockIsReservable(product: HeldStockProduct | undefined, variantId: string | undefined, nowMs: number): boolean;
|
|
174
|
+
/**
|
|
175
|
+
* Can this attempt reserve `quantity` units?
|
|
176
|
+
*
|
|
177
|
+
* The hold-aware twin of `canPurchase`, and it must agree with it everywhere
|
|
178
|
+
* `canPurchase` says NO: an untracked variant is always purchasable, a
|
|
179
|
+
* backorder product always is, a missing variant never is. The single
|
|
180
|
+
* difference is that a tracked deny-policy variant is measured against the
|
|
181
|
+
* shelf MINUS what live checkouts are already holding.
|
|
182
|
+
*
|
|
183
|
+
* `canPurchase` is still the first test rather than re-derived arithmetic, so
|
|
184
|
+
* the two cannot drift on the cases they share.
|
|
185
|
+
*/
|
|
186
|
+
export declare function canReserveStock(product: HeldStockProduct | undefined, variantId: string | undefined, quantity?: number, nowMs?: number, exceptHoldKey?: string): boolean;
|
|
187
|
+
/**
|
|
188
|
+
* What the console prints beside a stock count (AGL-2356).
|
|
189
|
+
*
|
|
190
|
+
* The merchant-facing half of the decision, and the reason a hold is not just
|
|
191
|
+
* an internal optimisation. `inventory` deliberately does not move while a unit
|
|
192
|
+
* is held, so a merchant looking at "3 in stock" while the storefront refuses
|
|
193
|
+
* the third sale has no way to reason about it — the number is right and the
|
|
194
|
+
* behaviour looks wrong. This is what names the gap.
|
|
195
|
+
*
|
|
196
|
+
* Empty string when nothing is held, so the row stays exactly as it reads today
|
|
197
|
+
* on the overwhelming majority of products.
|
|
198
|
+
*/
|
|
199
|
+
export declare function stockHoldLabel(product: HeldStockProduct | undefined, nowMs?: number): string;
|
|
@@ -0,0 +1,190 @@
|
|
|
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 { canPurchase } from "./commerce.js";
|
|
17
|
+
/**
|
|
18
|
+
* HOW LONG A UNIT STAYS SPOKEN FOR.
|
|
19
|
+
*
|
|
20
|
+
* This number is not free to choose. A hold that lapses while its Checkout
|
|
21
|
+
* Session can still be PAID reopens the exact window this file closes: the
|
|
22
|
+
* shopper pays, the webhook decrements, and the unit was sold to someone else
|
|
23
|
+
* in between. So the floor is the session's own lifetime.
|
|
24
|
+
*
|
|
25
|
+
* Stripe's default session lifetime is 24 HOURS, and a 24-hour stock hold is
|
|
26
|
+
* precisely the trade this issue was blocked on — one abandoned cart standing
|
|
27
|
+
* on a merchant's last unit for a day. The resolution is not to shorten the
|
|
28
|
+
* hold below the session; it is to shorten the SESSION. `checkout.ts` and
|
|
29
|
+
* `cart-checkout.ts` now send `expires_at` at `CHECKOUT_SESSION_TTL_MS`, which
|
|
30
|
+
* is Stripe's own minimum and the same half-hour the bookings plugin has always
|
|
31
|
+
* given a held room.
|
|
32
|
+
*
|
|
33
|
+
* The hold then outlives the session by `STOCK_HOLD_GRACE_MS`, so the ordering
|
|
34
|
+
* is always: session dies → `checkout.session.expired` releases → the hold was
|
|
35
|
+
* still standing when it did. The grace is what keeps clock skew between Stripe
|
|
36
|
+
* and this process from lapsing a hold under a session that is still payable.
|
|
37
|
+
*/ /**
|
|
38
|
+
* 31 minutes, not 30. Stripe REJECTS an `expires_at` less than 30 minutes in
|
|
39
|
+
* the future, and it measures against its own clock at the moment the request
|
|
40
|
+
* lands — so a value computed here at exactly the floor fails whenever network
|
|
41
|
+
* latency or clock skew eats a second. The extra minute is the margin; it is
|
|
42
|
+
* not a product opinion, and the product opinion is "the shortest session
|
|
43
|
+
* Stripe will sell us".
|
|
44
|
+
*/ export const CHECKOUT_SESSION_TTL_MS = 31 * 60 * 1000;
|
|
45
|
+
/** Cushion between the session expiring and its hold lapsing. */ export const STOCK_HOLD_GRACE_MS = 10 * 60 * 1000;
|
|
46
|
+
export const STOCK_HOLD_TTL_MS = CHECKOUT_SESSION_TTL_MS + STOCK_HOLD_GRACE_MS;
|
|
47
|
+
/**
|
|
48
|
+
* What a shopper is told when the units exist but are spoken for.
|
|
49
|
+
*
|
|
50
|
+
* Deliberately NOT the bare "Sold out" the empty-shelf refusal uses. The
|
|
51
|
+
* merchant has stock; someone else is in checkout with it. Naming the wait is
|
|
52
|
+
* what stops a shopper concluding the store is empty and leaving — the hold
|
|
53
|
+
* lapses within the half hour and the unit comes back.
|
|
54
|
+
*/ export const STOCK_HELD_MESSAGE = 'The last of these is in someone else’s checkout. ' + 'Try again in a few minutes.';
|
|
55
|
+
/**
|
|
56
|
+
* The product's holds with the lapsed ones dropped.
|
|
57
|
+
*
|
|
58
|
+
* Total, not a filter over a trusted shape: a hold whose `expiresAtMs` is
|
|
59
|
+
* absent or non-numeric is treated as EXPIRED rather than eternal. A merchant
|
|
60
|
+
* losing a unit forever to one corrupt map entry is the only direction with no
|
|
61
|
+
* recovery inside the product, and it is exactly the failure mode that kept
|
|
62
|
+
* this issue out of the launch build.
|
|
63
|
+
*/ export function pruneStockHolds(holds, nowMs) {
|
|
64
|
+
const live = {};
|
|
65
|
+
for (const [key, hold] of Object.entries(holds != null ? holds : {})){
|
|
66
|
+
var _ref;
|
|
67
|
+
const expiresAtMs = Number(hold == null ? void 0 : hold.expiresAtMs);
|
|
68
|
+
if (!Number.isFinite(expiresAtMs) || expiresAtMs <= nowMs) continue;
|
|
69
|
+
const units = {};
|
|
70
|
+
for (const [variantId, raw] of Object.entries((_ref = hold == null ? void 0 : hold.units) != null ? _ref : {})){
|
|
71
|
+
const count = Math.round(Number(raw));
|
|
72
|
+
// A non-numeric or non-positive count holds nothing. Coerced rather than
|
|
73
|
+
// trusted for the same reason as the expiry: a corrupt entry must not be
|
|
74
|
+
// able to reserve `NaN` units and make every comparison below false.
|
|
75
|
+
if (Number.isFinite(count) && count > 0) units[variantId] = count;
|
|
76
|
+
}
|
|
77
|
+
live[key] = {
|
|
78
|
+
expiresAtMs,
|
|
79
|
+
units
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return live;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Units of one variant spoken for by live checkouts right now.
|
|
86
|
+
*
|
|
87
|
+
* `exceptHoldKey` is how a retry of one attempt does not stand in its own way:
|
|
88
|
+
* the shopper presses the same button twice, the second press re-derives the
|
|
89
|
+
* same hold key, and without this exclusion it would be refused the unit it is
|
|
90
|
+
* already holding.
|
|
91
|
+
*/ export function heldVariantUnits(product, variantId, nowMs, exceptHoldKey) {
|
|
92
|
+
if (!variantId) return 0;
|
|
93
|
+
const live = pruneStockHolds(product == null ? void 0 : product.stockHolds, nowMs);
|
|
94
|
+
if (exceptHoldKey) delete live[exceptHoldKey];
|
|
95
|
+
let held = 0;
|
|
96
|
+
for (const hold of Object.values(live)){
|
|
97
|
+
var _ref;
|
|
98
|
+
var _hold_units;
|
|
99
|
+
held += Number((_ref = (_hold_units = hold.units) == null ? void 0 : _hold_units[variantId]) != null ? _ref : 0);
|
|
100
|
+
}
|
|
101
|
+
return held;
|
|
102
|
+
}
|
|
103
|
+
/** Units held across every variant of a product — the console's figure. */ export function heldProductUnits(product, nowMs) {
|
|
104
|
+
let held = 0;
|
|
105
|
+
for (const hold of Object.values(pruneStockHolds(product == null ? void 0 : product.stockHolds, nowMs))){
|
|
106
|
+
var _hold_units;
|
|
107
|
+
for (const units of Object.values((_hold_units = hold.units) != null ? _hold_units : {}))held += Number(units);
|
|
108
|
+
}
|
|
109
|
+
return held;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Resolve the variant a purchase refers to, EXACTLY as `canPurchase` does.
|
|
113
|
+
*
|
|
114
|
+
* Shared rather than re-derived, because the pre-filter and the in-transaction
|
|
115
|
+
* re-check disagreeing about which variant they are talking about is a defect
|
|
116
|
+
* with no visible symptom until the wrong shelf goes negative.
|
|
117
|
+
*/ function resolveVariant(product, variantId) {
|
|
118
|
+
var _product_variants, _product_variants1;
|
|
119
|
+
return variantId ? product == null ? void 0 : (_product_variants = product.variants) == null ? void 0 : _product_variants.find((item)=>item.id === variantId) : product == null ? void 0 : (_product_variants1 = product.variants) == null ? void 0 : _product_variants1[0];
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Units a NEW checkout could take: the shelf minus the live holds.
|
|
123
|
+
*
|
|
124
|
+
* `null` when there is no number to answer with — an untracked variant, a
|
|
125
|
+
* backorder product (whose merchant chose to sell past zero, so availability is
|
|
126
|
+
* unbounded by construction), or a variant that does not exist. `null` rather
|
|
127
|
+
* than `Infinity` so a caller cannot do arithmetic on "no limit" and get a
|
|
128
|
+
* number, and never negative: a shelf that went short under a `backorder`
|
|
129
|
+
* policy reads as zero available, not as a debt.
|
|
130
|
+
*
|
|
131
|
+
* NOTE FOR THIS REPO: `strictNullChecks` is off, so `if (!available)` does NOT
|
|
132
|
+
* distinguish `null` from `0` here — and `0` is a legitimate, load-bearing
|
|
133
|
+
* answer meaning "tracked, and none left". Every caller must test `== null`.
|
|
134
|
+
*/ export function availableVariantUnits(product, variantId, nowMs, exceptHoldKey) {
|
|
135
|
+
const variant = resolveVariant(product, variantId);
|
|
136
|
+
if (!variant) return null;
|
|
137
|
+
if (variant.inventory == null) return null;
|
|
138
|
+
if ((product == null ? void 0 : product.oversellPolicy) === 'backorder') return null;
|
|
139
|
+
const onShelf = Math.round(Number(variant.inventory));
|
|
140
|
+
if (!Number.isFinite(onShelf)) return null;
|
|
141
|
+
return Math.max(0, onShelf - heldVariantUnits(product, variant.id, nowMs, exceptHoldKey));
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Is there a hold to take at all?
|
|
145
|
+
*
|
|
146
|
+
* False for the cases `availableVariantUnits` answers `null` for — an untracked
|
|
147
|
+
* or backorder variant has no finite shelf to reserve against, and writing a
|
|
148
|
+
* hold for one would put a growing map on a document whose availability it can
|
|
149
|
+
* never change. The same shape as `holdPromotionSlot` skipping an UNCAPPED
|
|
150
|
+
* promotion: the unlimited case stays off the write path entirely.
|
|
151
|
+
*/ export function stockIsReservable(product, variantId, nowMs) {
|
|
152
|
+
return availableVariantUnits(product, variantId, nowMs) != null;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Can this attempt reserve `quantity` units?
|
|
156
|
+
*
|
|
157
|
+
* The hold-aware twin of `canPurchase`, and it must agree with it everywhere
|
|
158
|
+
* `canPurchase` says NO: an untracked variant is always purchasable, a
|
|
159
|
+
* backorder product always is, a missing variant never is. The single
|
|
160
|
+
* difference is that a tracked deny-policy variant is measured against the
|
|
161
|
+
* shelf MINUS what live checkouts are already holding.
|
|
162
|
+
*
|
|
163
|
+
* `canPurchase` is still the first test rather than re-derived arithmetic, so
|
|
164
|
+
* the two cannot drift on the cases they share.
|
|
165
|
+
*/ export function canReserveStock(product, variantId, quantity = 1, nowMs = Date.now(), exceptHoldKey) {
|
|
166
|
+
if (!product) return false;
|
|
167
|
+
if (!canPurchase(product, variantId, quantity)) return false;
|
|
168
|
+
const available = availableVariantUnits(product, variantId, nowMs, exceptHoldKey);
|
|
169
|
+
// `== null`, never `!available` — see the note on `availableVariantUnits`.
|
|
170
|
+
if (available == null) return true;
|
|
171
|
+
return available >= Math.max(1, Math.round(Number(quantity) || 1));
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* What the console prints beside a stock count (AGL-2356).
|
|
175
|
+
*
|
|
176
|
+
* The merchant-facing half of the decision, and the reason a hold is not just
|
|
177
|
+
* an internal optimisation. `inventory` deliberately does not move while a unit
|
|
178
|
+
* is held, so a merchant looking at "3 in stock" while the storefront refuses
|
|
179
|
+
* the third sale has no way to reason about it — the number is right and the
|
|
180
|
+
* behaviour looks wrong. This is what names the gap.
|
|
181
|
+
*
|
|
182
|
+
* Empty string when nothing is held, so the row stays exactly as it reads today
|
|
183
|
+
* on the overwhelming majority of products.
|
|
184
|
+
*/ export function stockHoldLabel(product, nowMs = Date.now()) {
|
|
185
|
+
const held = heldProductUnits(product, nowMs);
|
|
186
|
+
if (held <= 0) return '';
|
|
187
|
+
return `${held} reserved in checkout`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
//# sourceMappingURL=commerce-stock-holds.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-stock-holds.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 type { HostProduct } from './commerce'\nimport { canPurchase } from './commerce'\n\n/**\n * RESERVED STOCK: the arithmetic half of the checkout hold (AGL-2356).\n *\n * ## The defect this closes\n *\n * AGL-2320 made every stock decrement atomic, so a decrement is never LOST and\n * the count never shows phantom stock. It could not make the storefront REFUSE\n * an oversell, and said so: the gate and the decrement are minutes apart with\n * money moving in between.\n *\n * - `checkout.ts` / `cart-checkout.ts` called `canPurchase` against a plain\n * `.get()` when the Stripe Checkout Session was created. Nothing was\n * written, so there was no document to contend on — N concurrent shoppers\n * all read `inventory: 1`, all passed, and no locking discipline anywhere\n * could change that, because a read races nothing.\n * - the decrement happened in `billing-webhook.ts` when the session\n * completed. By then the shopper has paid, so refusing there would take\n * money and withhold goods — worse than the oversell.\n *\n * The only correct place to refuse is session creation, and refusing there\n * requires WRITING. That is what a hold is: a unit spoken for by a live\n * checkout, subtracted from what the next shopper is offered.\n *\n * ## Why the hold does not touch `inventory`\n *\n * `inventory` means UNITS ON THE SHELF and half the product reads it that way —\n * the low-stock crossing alert, the restock queue, `reconcile-stock.ts`, the\n * ledger's `appliedDelta` cap, the console's own count. A hold is not a unit\n * leaving the shelf; it is a unit promised to someone who has not paid yet.\n * Decrementing at hold time and \"converting\" at settlement would make every one\n * of those readers wrong for the duration of a checkout, and would make a\n * released hold indistinguishable from a restock in the ledger.\n *\n * So the shelf count is untouched and AVAILABILITY becomes a derived figure:\n * `inventory` minus the live holds. `decrementVariantStock` is unchanged, which\n * is the point — settlement is still the same single atomic decrement it was\n * before this issue, and the hold is simply dropped afterwards.\n *\n * ## Why a lapsed hold is a release\n *\n * Every read prunes (`pruneStockHolds`), so a hold whose release never arrived\n * lapses on its own the next time anybody asks. That is what makes a leaked\n * hold a bounded inconvenience rather than the unbounded, invisible UNDER-sell\n * this issue was held back for: stock is never stranded waiting on a webhook.\n * The explicit release paths exist so the merchant and the shopper see the unit\n * come back promptly, not because correctness depends on them.\n */\n\n/** One live reservation on a product, keyed by checkout attempt. */\nexport interface StockHold {\n /** Wall-clock ms after which this hold is void. */\n expiresAtMs: number\n /**\n * Variant id → units held. A map and not a single number because one cart\n * can hold two variants of one product, and a product document is the thing\n * the transaction contends on — collapsing them would make a two-variant\n * cart reserve the wrong shelf.\n */\n units?: Record<string, number>\n}\n\n/**\n * The product fields this file reads. Structural on purpose: `HostProduct`\n * carries forty fields and the hold arithmetic must not be able to depend on\n * any of them but these.\n */\nexport interface HeldStockProduct extends Pick<\n HostProduct,\n 'variants' | 'oversellPolicy'\n> {\n /** Hold key → hold. Absent on every product written before AGL-2356. */\n stockHolds?: Record<string, StockHold>\n}\n\n/**\n * HOW LONG A UNIT STAYS SPOKEN FOR.\n *\n * This number is not free to choose. A hold that lapses while its Checkout\n * Session can still be PAID reopens the exact window this file closes: the\n * shopper pays, the webhook decrements, and the unit was sold to someone else\n * in between. So the floor is the session's own lifetime.\n *\n * Stripe's default session lifetime is 24 HOURS, and a 24-hour stock hold is\n * precisely the trade this issue was blocked on — one abandoned cart standing\n * on a merchant's last unit for a day. The resolution is not to shorten the\n * hold below the session; it is to shorten the SESSION. `checkout.ts` and\n * `cart-checkout.ts` now send `expires_at` at `CHECKOUT_SESSION_TTL_MS`, which\n * is Stripe's own minimum and the same half-hour the bookings plugin has always\n * given a held room.\n *\n * The hold then outlives the session by `STOCK_HOLD_GRACE_MS`, so the ordering\n * is always: session dies → `checkout.session.expired` releases → the hold was\n * still standing when it did. The grace is what keeps clock skew between Stripe\n * and this process from lapsing a hold under a session that is still payable.\n */\n/**\n * 31 minutes, not 30. Stripe REJECTS an `expires_at` less than 30 minutes in\n * the future, and it measures against its own clock at the moment the request\n * lands — so a value computed here at exactly the floor fails whenever network\n * latency or clock skew eats a second. The extra minute is the margin; it is\n * not a product opinion, and the product opinion is \"the shortest session\n * Stripe will sell us\".\n */\nexport const CHECKOUT_SESSION_TTL_MS = 31 * 60 * 1000\n\n/** Cushion between the session expiring and its hold lapsing. */\nexport const STOCK_HOLD_GRACE_MS = 10 * 60 * 1000\n\nexport const STOCK_HOLD_TTL_MS = CHECKOUT_SESSION_TTL_MS + STOCK_HOLD_GRACE_MS\n\n/**\n * What a shopper is told when the units exist but are spoken for.\n *\n * Deliberately NOT the bare \"Sold out\" the empty-shelf refusal uses. The\n * merchant has stock; someone else is in checkout with it. Naming the wait is\n * what stops a shopper concluding the store is empty and leaving — the hold\n * lapses within the half hour and the unit comes back.\n */\nexport const STOCK_HELD_MESSAGE =\n 'The last of these is in someone else’s checkout. ' +\n 'Try again in a few minutes.'\n\n/**\n * The product's holds with the lapsed ones dropped.\n *\n * Total, not a filter over a trusted shape: a hold whose `expiresAtMs` is\n * absent or non-numeric is treated as EXPIRED rather than eternal. A merchant\n * losing a unit forever to one corrupt map entry is the only direction with no\n * recovery inside the product, and it is exactly the failure mode that kept\n * this issue out of the launch build.\n */\nexport function pruneStockHolds(\n holds: Record<string, StockHold> | undefined,\n nowMs: number,\n): Record<string, StockHold> {\n const live: Record<string, StockHold> = {}\n for (const [key, hold] of Object.entries(holds ?? {})) {\n const expiresAtMs = Number(hold?.expiresAtMs)\n if (!Number.isFinite(expiresAtMs) || expiresAtMs <= nowMs) continue\n const units: Record<string, number> = {}\n for (const [variantId, raw] of Object.entries(hold?.units ?? {})) {\n const count = Math.round(Number(raw))\n // A non-numeric or non-positive count holds nothing. Coerced rather than\n // trusted for the same reason as the expiry: a corrupt entry must not be\n // able to reserve `NaN` units and make every comparison below false.\n if (Number.isFinite(count) && count > 0) units[variantId] = count\n }\n live[key] = { expiresAtMs, units }\n }\n return live\n}\n\n/**\n * Units of one variant spoken for by live checkouts right now.\n *\n * `exceptHoldKey` is how a retry of one attempt does not stand in its own way:\n * the shopper presses the same button twice, the second press re-derives the\n * same hold key, and without this exclusion it would be refused the unit it is\n * already holding.\n */\nexport function heldVariantUnits(\n product: HeldStockProduct | undefined,\n variantId: string | undefined,\n nowMs: number,\n exceptHoldKey?: string,\n): number {\n if (!variantId) return 0\n const live = pruneStockHolds(product?.stockHolds, nowMs)\n if (exceptHoldKey) delete live[exceptHoldKey]\n let held = 0\n for (const hold of Object.values(live)) {\n held += Number(hold.units?.[variantId] ?? 0)\n }\n return held\n}\n\n/** Units held across every variant of a product — the console's figure. */\nexport function heldProductUnits(\n product: HeldStockProduct | undefined,\n nowMs: number,\n): number {\n let held = 0\n for (const hold of Object.values(\n pruneStockHolds(product?.stockHolds, nowMs),\n )) {\n for (const units of Object.values(hold.units ?? {})) held += Number(units)\n }\n return held\n}\n\n/**\n * Resolve the variant a purchase refers to, EXACTLY as `canPurchase` does.\n *\n * Shared rather than re-derived, because the pre-filter and the in-transaction\n * re-check disagreeing about which variant they are talking about is a defect\n * with no visible symptom until the wrong shelf goes negative.\n */\nfunction resolveVariant(\n product: HeldStockProduct | undefined,\n variantId: string | undefined,\n) {\n return variantId\n ? product?.variants?.find((item) => item.id === variantId)\n : product?.variants?.[0]\n}\n\n/**\n * Units a NEW checkout could take: the shelf minus the live holds.\n *\n * `null` when there is no number to answer with — an untracked variant, a\n * backorder product (whose merchant chose to sell past zero, so availability is\n * unbounded by construction), or a variant that does not exist. `null` rather\n * than `Infinity` so a caller cannot do arithmetic on \"no limit\" and get a\n * number, and never negative: a shelf that went short under a `backorder`\n * policy reads as zero available, not as a debt.\n *\n * NOTE FOR THIS REPO: `strictNullChecks` is off, so `if (!available)` does NOT\n * distinguish `null` from `0` here — and `0` is a legitimate, load-bearing\n * answer meaning \"tracked, and none left\". Every caller must test `== null`.\n */\nexport function availableVariantUnits(\n product: HeldStockProduct | undefined,\n variantId: string | undefined,\n nowMs: number,\n exceptHoldKey?: string,\n): number | null {\n const variant = resolveVariant(product, variantId)\n if (!variant) return null\n if (variant.inventory == null) return null\n if (product?.oversellPolicy === 'backorder') return null\n const onShelf = Math.round(Number(variant.inventory))\n if (!Number.isFinite(onShelf)) return null\n return Math.max(\n 0,\n onShelf - heldVariantUnits(product, variant.id, nowMs, exceptHoldKey),\n )\n}\n\n/**\n * Is there a hold to take at all?\n *\n * False for the cases `availableVariantUnits` answers `null` for — an untracked\n * or backorder variant has no finite shelf to reserve against, and writing a\n * hold for one would put a growing map on a document whose availability it can\n * never change. The same shape as `holdPromotionSlot` skipping an UNCAPPED\n * promotion: the unlimited case stays off the write path entirely.\n */\nexport function stockIsReservable(\n product: HeldStockProduct | undefined,\n variantId: string | undefined,\n nowMs: number,\n): boolean {\n return availableVariantUnits(product, variantId, nowMs) != null\n}\n\n/**\n * Can this attempt reserve `quantity` units?\n *\n * The hold-aware twin of `canPurchase`, and it must agree with it everywhere\n * `canPurchase` says NO: an untracked variant is always purchasable, a\n * backorder product always is, a missing variant never is. The single\n * difference is that a tracked deny-policy variant is measured against the\n * shelf MINUS what live checkouts are already holding.\n *\n * `canPurchase` is still the first test rather than re-derived arithmetic, so\n * the two cannot drift on the cases they share.\n */\nexport function canReserveStock(\n product: HeldStockProduct | undefined,\n variantId: string | undefined,\n quantity = 1,\n nowMs: number = Date.now(),\n exceptHoldKey?: string,\n): boolean {\n if (!product) return false\n if (!canPurchase(product as HostProduct, variantId, quantity)) return false\n const available = availableVariantUnits(\n product,\n variantId,\n nowMs,\n exceptHoldKey,\n )\n // `== null`, never `!available` — see the note on `availableVariantUnits`.\n if (available == null) return true\n return available >= Math.max(1, Math.round(Number(quantity) || 1))\n}\n\n/**\n * What the console prints beside a stock count (AGL-2356).\n *\n * The merchant-facing half of the decision, and the reason a hold is not just\n * an internal optimisation. `inventory` deliberately does not move while a unit\n * is held, so a merchant looking at \"3 in stock\" while the storefront refuses\n * the third sale has no way to reason about it — the number is right and the\n * behaviour looks wrong. This is what names the gap.\n *\n * Empty string when nothing is held, so the row stays exactly as it reads today\n * on the overwhelming majority of products.\n */\nexport function stockHoldLabel(\n product: HeldStockProduct | undefined,\n nowMs: number = Date.now(),\n): string {\n const held = heldProductUnits(product, nowMs)\n if (held <= 0) return ''\n return `${held} reserved in checkout`\n}\n"],"names":["canPurchase","CHECKOUT_SESSION_TTL_MS","STOCK_HOLD_GRACE_MS","STOCK_HOLD_TTL_MS","STOCK_HELD_MESSAGE","pruneStockHolds","holds","nowMs","live","key","hold","Object","entries","expiresAtMs","Number","isFinite","units","variantId","raw","count","Math","round","heldVariantUnits","product","exceptHoldKey","stockHolds","held","values","heldProductUnits","resolveVariant","variants","find","item","id","availableVariantUnits","variant","inventory","oversellPolicy","onShelf","max","stockIsReservable","canReserveStock","quantity","Date","now","available","stockHoldLabel"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SAASA,WAAW,QAAQ,gBAAY;AA4ExC;;;;;;;;;;;;;;;;;;;;CAoBC,GACD;;;;;;;CAOC,GACD,OAAO,MAAMC,0BAA0B,KAAK,KAAK,KAAI;AAErD,+DAA+D,GAC/D,OAAO,MAAMC,sBAAsB,KAAK,KAAK,KAAI;AAEjD,OAAO,MAAMC,oBAAoBF,0BAA0BC,oBAAmB;AAE9E;;;;;;;CAOC,GACD,OAAO,MAAME,qBACX,sDACA,8BAA6B;AAE/B;;;;;;;;CAQC,GACD,OAAO,SAASC,gBACdC,KAA4C,EAC5CC,KAAa;IAEb,MAAMC,OAAkC,CAAC;IACzC,KAAK,MAAM,CAACC,KAAKC,KAAK,IAAIC,OAAOC,OAAO,CAACN,gBAAAA,QAAS,CAAC,GAAI;;QACrD,MAAMO,cAAcC,OAAOJ,wBAAAA,KAAMG,WAAW;QAC5C,IAAI,CAACC,OAAOC,QAAQ,CAACF,gBAAgBA,eAAeN,OAAO;QAC3D,MAAMS,QAAgC,CAAC;QACvC,KAAK,MAAM,CAACC,WAAWC,IAAI,IAAIP,OAAOC,OAAO,SAACF,wBAAAA,KAAMM,KAAK,mBAAI,CAAC,GAAI;YAChE,MAAMG,QAAQC,KAAKC,KAAK,CAACP,OAAOI;YAChC,yEAAyE;YACzE,yEAAyE;YACzE,qEAAqE;YACrE,IAAIJ,OAAOC,QAAQ,CAACI,UAAUA,QAAQ,GAAGH,KAAK,CAACC,UAAU,GAAGE;QAC9D;QACAX,IAAI,CAACC,IAAI,GAAG;YAAEI;YAAaG;QAAM;IACnC;IACA,OAAOR;AACT;AAEA;;;;;;;CAOC,GACD,OAAO,SAASc,iBACdC,OAAqC,EACrCN,SAA6B,EAC7BV,KAAa,EACbiB,aAAsB;IAEtB,IAAI,CAACP,WAAW,OAAO;IACvB,MAAMT,OAAOH,gBAAgBkB,2BAAAA,QAASE,UAAU,EAAElB;IAClD,IAAIiB,eAAe,OAAOhB,IAAI,CAACgB,cAAc;IAC7C,IAAIE,OAAO;IACX,KAAK,MAAMhB,QAAQC,OAAOgB,MAAM,CAACnB,MAAO;;YACvBE;QAAfgB,QAAQZ,gBAAOJ,cAAAA,KAAKM,KAAK,qBAAVN,WAAY,CAACO,UAAU,mBAAI;IAC5C;IACA,OAAOS;AACT;AAEA,yEAAyE,GACzE,OAAO,SAASE,iBACdL,OAAqC,EACrChB,KAAa;IAEb,IAAImB,OAAO;IACX,KAAK,MAAMhB,QAAQC,OAAOgB,MAAM,CAC9BtB,gBAAgBkB,2BAAAA,QAASE,UAAU,EAAElB,QACpC;YACiCG;QAAlC,KAAK,MAAMM,SAASL,OAAOgB,MAAM,EAACjB,cAAAA,KAAKM,KAAK,YAAVN,cAAc,CAAC,GAAIgB,QAAQZ,OAAOE;IACtE;IACA,OAAOU;AACT;AAEA;;;;;;CAMC,GACD,SAASG,eACPN,OAAqC,EACrCN,SAA6B;QAGzBM,mBACAA;IAFJ,OAAON,YACHM,4BAAAA,oBAAAA,QAASO,QAAQ,qBAAjBP,kBAAmBQ,IAAI,CAAC,CAACC,OAASA,KAAKC,EAAE,KAAKhB,aAC9CM,4BAAAA,qBAAAA,QAASO,QAAQ,qBAAjBP,kBAAmB,CAAC,EAAE;AAC5B;AAEA;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASW,sBACdX,OAAqC,EACrCN,SAA6B,EAC7BV,KAAa,EACbiB,aAAsB;IAEtB,MAAMW,UAAUN,eAAeN,SAASN;IACxC,IAAI,CAACkB,SAAS,OAAO;IACrB,IAAIA,QAAQC,SAAS,IAAI,MAAM,OAAO;IACtC,IAAIb,CAAAA,2BAAAA,QAASc,cAAc,MAAK,aAAa,OAAO;IACpD,MAAMC,UAAUlB,KAAKC,KAAK,CAACP,OAAOqB,QAAQC,SAAS;IACnD,IAAI,CAACtB,OAAOC,QAAQ,CAACuB,UAAU,OAAO;IACtC,OAAOlB,KAAKmB,GAAG,CACb,GACAD,UAAUhB,iBAAiBC,SAASY,QAAQF,EAAE,EAAE1B,OAAOiB;AAE3D;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASgB,kBACdjB,OAAqC,EACrCN,SAA6B,EAC7BV,KAAa;IAEb,OAAO2B,sBAAsBX,SAASN,WAAWV,UAAU;AAC7D;AAEA;;;;;;;;;;;CAWC,GACD,OAAO,SAASkC,gBACdlB,OAAqC,EACrCN,SAA6B,EAC7ByB,WAAW,CAAC,EACZnC,QAAgBoC,KAAKC,GAAG,EAAE,EAC1BpB,aAAsB;IAEtB,IAAI,CAACD,SAAS,OAAO;IACrB,IAAI,CAACvB,YAAYuB,SAAwBN,WAAWyB,WAAW,OAAO;IACtE,MAAMG,YAAYX,sBAChBX,SACAN,WACAV,OACAiB;IAEF,2EAA2E;IAC3E,IAAIqB,aAAa,MAAM,OAAO;IAC9B,OAAOA,aAAazB,KAAKmB,GAAG,CAAC,GAAGnB,KAAKC,KAAK,CAACP,OAAO4B,aAAa;AACjE;AAEA;;;;;;;;;;;CAWC,GACD,OAAO,SAASI,eACdvB,OAAqC,EACrChB,QAAgBoC,KAAKC,GAAG,EAAE;IAE1B,MAAMlB,OAAOE,iBAAiBL,SAAShB;IACvC,IAAImB,QAAQ,GAAG,OAAO;IACtB,OAAO,GAAGA,KAAK,qBAAqB,CAAC;AACvC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
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 TENANT-side live-subscription list — one copy, for the whole plugin
|
|
19
|
+
* (AGL-1849).
|
|
20
|
+
*
|
|
21
|
+
* ## Why this is not `isLiveSubscriptionStatus`
|
|
22
|
+
*
|
|
23
|
+
* AGL-1715-EXEMPT: this is a decision about the TENANT's own site members'
|
|
24
|
+
* subscriptions to the TENANT's products (`hosts/{hostId}/subscriptions`) — a
|
|
25
|
+
* different Stripe account, a different buyer and a different question from
|
|
26
|
+
* `isLiveSubscriptionStatus` in `@aglyn/aglyn`, which asks whether an Aglyn org
|
|
27
|
+
* is paying US. The words coincide only because both are Stripe subscription
|
|
28
|
+
* statuses. Converging them would tie a shopper's access to a tenant's store to
|
|
29
|
+
* Aglyn's own billing rules, so the two stay apart deliberately.
|
|
30
|
+
*
|
|
31
|
+
* ## Why it is its own file
|
|
32
|
+
*
|
|
33
|
+
* It used to be two private `LIVE_STATUSES` sets, in `server/gate.ts` and
|
|
34
|
+
* `server/member-post.ts`, each carrying its own copy of the marker and its own
|
|
35
|
+
* allowlist entry. Two copies of a list that decides who has already paid is
|
|
36
|
+
* the shape AGL-1715 exists to prevent, and the third caller — the
|
|
37
|
+
* duplicate-subscription guard in `server/checkout.ts` — would have made it
|
|
38
|
+
* three. Hoisting it to the model gives the tenant side the same single source
|
|
39
|
+
* the org side has, rather than a fourth transcription of the same three words.
|
|
40
|
+
*
|
|
41
|
+
* ## Why `past_due` counts as live
|
|
42
|
+
*
|
|
43
|
+
* A subscription in dunning is still a subscription: Stripe is retrying the
|
|
44
|
+
* card, the member has not cancelled, and access has not lapsed. Dropping it
|
|
45
|
+
* would revoke a paying member's content the moment a card expired, and — for
|
|
46
|
+
* the checkout guard — would invite them to buy a SECOND subscription to a
|
|
47
|
+
* product they are already being billed for, which is precisely the duplicate
|
|
48
|
+
* this list is used to refuse.
|
|
49
|
+
*
|
|
50
|
+
* A cancelled or `incomplete_expired` subscription is NOT live, and that is
|
|
51
|
+
* equally load-bearing: a member who cancelled must be able to re-subscribe.
|
|
52
|
+
* A guard built on "has a subscription row" instead of "has a LIVE one" would
|
|
53
|
+
* lock them out forever (the AGL-1715 lesson, restated on the tenant side).
|
|
54
|
+
*/
|
|
55
|
+
export declare const TENANT_LIVE_SUBSCRIPTION_STATUSES: readonly ["active", "trialing", "past_due"];
|
|
56
|
+
export type TenantLiveSubscriptionStatus = (typeof TENANT_LIVE_SUBSCRIPTION_STATUSES)[number];
|
|
57
|
+
/**
|
|
58
|
+
* Is this tenant-side subscription status one that still grants access / still
|
|
59
|
+
* counts as an existing subscription?
|
|
60
|
+
*
|
|
61
|
+
* Takes `unknown` on purpose: every caller reads it straight off a Firestore
|
|
62
|
+
* snapshot, where the field may be absent, a number, or anything else a bad
|
|
63
|
+
* write left behind. Narrowing here means no caller has to `String(...)` it
|
|
64
|
+
* defensively and get a `'undefined'` that quietly fails the test for the
|
|
65
|
+
* wrong reason.
|
|
66
|
+
*/
|
|
67
|
+
export declare function isTenantSubscriptionLive(status: unknown): boolean;
|
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
* The TENANT-side live-subscription list — one copy, for the whole plugin
|
|
18
|
+
* (AGL-1849).
|
|
19
|
+
*
|
|
20
|
+
* ## Why this is not `isLiveSubscriptionStatus`
|
|
21
|
+
*
|
|
22
|
+
* AGL-1715-EXEMPT: this is a decision about the TENANT's own site members'
|
|
23
|
+
* subscriptions to the TENANT's products (`hosts/{hostId}/subscriptions`) — a
|
|
24
|
+
* different Stripe account, a different buyer and a different question from
|
|
25
|
+
* `isLiveSubscriptionStatus` in `@aglyn/aglyn`, which asks whether an Aglyn org
|
|
26
|
+
* is paying US. The words coincide only because both are Stripe subscription
|
|
27
|
+
* statuses. Converging them would tie a shopper's access to a tenant's store to
|
|
28
|
+
* Aglyn's own billing rules, so the two stay apart deliberately.
|
|
29
|
+
*
|
|
30
|
+
* ## Why it is its own file
|
|
31
|
+
*
|
|
32
|
+
* It used to be two private `LIVE_STATUSES` sets, in `server/gate.ts` and
|
|
33
|
+
* `server/member-post.ts`, each carrying its own copy of the marker and its own
|
|
34
|
+
* allowlist entry. Two copies of a list that decides who has already paid is
|
|
35
|
+
* the shape AGL-1715 exists to prevent, and the third caller — the
|
|
36
|
+
* duplicate-subscription guard in `server/checkout.ts` — would have made it
|
|
37
|
+
* three. Hoisting it to the model gives the tenant side the same single source
|
|
38
|
+
* the org side has, rather than a fourth transcription of the same three words.
|
|
39
|
+
*
|
|
40
|
+
* ## Why `past_due` counts as live
|
|
41
|
+
*
|
|
42
|
+
* A subscription in dunning is still a subscription: Stripe is retrying the
|
|
43
|
+
* card, the member has not cancelled, and access has not lapsed. Dropping it
|
|
44
|
+
* would revoke a paying member's content the moment a card expired, and — for
|
|
45
|
+
* the checkout guard — would invite them to buy a SECOND subscription to a
|
|
46
|
+
* product they are already being billed for, which is precisely the duplicate
|
|
47
|
+
* this list is used to refuse.
|
|
48
|
+
*
|
|
49
|
+
* A cancelled or `incomplete_expired` subscription is NOT live, and that is
|
|
50
|
+
* equally load-bearing: a member who cancelled must be able to re-subscribe.
|
|
51
|
+
* A guard built on "has a subscription row" instead of "has a LIVE one" would
|
|
52
|
+
* lock them out forever (the AGL-1715 lesson, restated on the tenant side).
|
|
53
|
+
*/ // AGL-1715-EXEMPT: the tenant side's own list, for the reasons set out above —
|
|
54
|
+
// a tenant's site member subscribing to a tenant's product, on the tenant's
|
|
55
|
+
// Stripe account. Not the Aglyn org's subscription to us, so not
|
|
56
|
+
// `isLiveSubscriptionStatus`. (Restated here because the guard wants the marker
|
|
57
|
+
// within 25 lines of the literal, and the reasoning above is longer than that.)
|
|
58
|
+
export const TENANT_LIVE_SUBSCRIPTION_STATUSES = [
|
|
59
|
+
'active',
|
|
60
|
+
'trialing',
|
|
61
|
+
'past_due'
|
|
62
|
+
];
|
|
63
|
+
/**
|
|
64
|
+
* Is this tenant-side subscription status one that still grants access / still
|
|
65
|
+
* counts as an existing subscription?
|
|
66
|
+
*
|
|
67
|
+
* Takes `unknown` on purpose: every caller reads it straight off a Firestore
|
|
68
|
+
* snapshot, where the field may be absent, a number, or anything else a bad
|
|
69
|
+
* write left behind. Narrowing here means no caller has to `String(...)` it
|
|
70
|
+
* defensively and get a `'undefined'` that quietly fails the test for the
|
|
71
|
+
* wrong reason.
|
|
72
|
+
*/ export function isTenantSubscriptionLive(status) {
|
|
73
|
+
return typeof status === 'string' && TENANT_LIVE_SUBSCRIPTION_STATUSES.includes(status);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
//# sourceMappingURL=commerce-subscription-status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-subscription-status.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\n/**\n * The TENANT-side live-subscription list — one copy, for the whole plugin\n * (AGL-1849).\n *\n * ## Why this is not `isLiveSubscriptionStatus`\n *\n * AGL-1715-EXEMPT: this is a decision about the TENANT's own site members'\n * subscriptions to the TENANT's products (`hosts/{hostId}/subscriptions`) — a\n * different Stripe account, a different buyer and a different question from\n * `isLiveSubscriptionStatus` in `@aglyn/aglyn`, which asks whether an Aglyn org\n * is paying US. The words coincide only because both are Stripe subscription\n * statuses. Converging them would tie a shopper's access to a tenant's store to\n * Aglyn's own billing rules, so the two stay apart deliberately.\n *\n * ## Why it is its own file\n *\n * It used to be two private `LIVE_STATUSES` sets, in `server/gate.ts` and\n * `server/member-post.ts`, each carrying its own copy of the marker and its own\n * allowlist entry. Two copies of a list that decides who has already paid is\n * the shape AGL-1715 exists to prevent, and the third caller — the\n * duplicate-subscription guard in `server/checkout.ts` — would have made it\n * three. Hoisting it to the model gives the tenant side the same single source\n * the org side has, rather than a fourth transcription of the same three words.\n *\n * ## Why `past_due` counts as live\n *\n * A subscription in dunning is still a subscription: Stripe is retrying the\n * card, the member has not cancelled, and access has not lapsed. Dropping it\n * would revoke a paying member's content the moment a card expired, and — for\n * the checkout guard — would invite them to buy a SECOND subscription to a\n * product they are already being billed for, which is precisely the duplicate\n * this list is used to refuse.\n *\n * A cancelled or `incomplete_expired` subscription is NOT live, and that is\n * equally load-bearing: a member who cancelled must be able to re-subscribe.\n * A guard built on \"has a subscription row\" instead of \"has a LIVE one\" would\n * lock them out forever (the AGL-1715 lesson, restated on the tenant side).\n */\n// AGL-1715-EXEMPT: the tenant side's own list, for the reasons set out above —\n// a tenant's site member subscribing to a tenant's product, on the tenant's\n// Stripe account. Not the Aglyn org's subscription to us, so not\n// `isLiveSubscriptionStatus`. (Restated here because the guard wants the marker\n// within 25 lines of the literal, and the reasoning above is longer than that.)\nexport const TENANT_LIVE_SUBSCRIPTION_STATUSES = [\n 'active',\n 'trialing',\n 'past_due',\n] as const\n\nexport type TenantLiveSubscriptionStatus =\n (typeof TENANT_LIVE_SUBSCRIPTION_STATUSES)[number]\n\n/**\n * Is this tenant-side subscription status one that still grants access / still\n * counts as an existing subscription?\n *\n * Takes `unknown` on purpose: every caller reads it straight off a Firestore\n * snapshot, where the field may be absent, a number, or anything else a bad\n * write left behind. Narrowing here means no caller has to `String(...)` it\n * defensively and get a `'undefined'` that quietly fails the test for the\n * wrong reason.\n */\nexport function isTenantSubscriptionLive(status: unknown): boolean {\n return (\n typeof status === 'string' &&\n (TENANT_LIVE_SUBSCRIPTION_STATUSES as readonly string[]).includes(status)\n )\n}\n"],"names":["TENANT_LIVE_SUBSCRIPTION_STATUSES","isTenantSubscriptionLive","status","includes"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCC,GACD,+EAA+E;AAC/E,4EAA4E;AAC5E,iEAAiE;AACjE,gFAAgF;AAChF,gFAAgF;AAChF,OAAO,MAAMA,oCAAoC;IAC/C;IACA;IACA;CACD,CAAS;AAKV;;;;;;;;;CASC,GACD,OAAO,SAASC,yBAAyBC,MAAe;IACtD,OACE,OAAOA,WAAW,YAClB,AAACF,kCAAwDG,QAAQ,CAACD;AAEtE"}
|