@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,144 @@
|
|
|
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
|
+
* Reserve-then-settle for promotion redemption slots (AGL-2453).
|
|
19
|
+
*
|
|
20
|
+
* The I/O half of `model/commerce-promotions.ts` — read that file first; it
|
|
21
|
+
* carries the decision this one implements. Three call sites share it, and
|
|
22
|
+
* sharing is the point: the typed coupon (`coupons/{code}`), the automatic
|
|
23
|
+
* AGL-305 discount (`discounts/{id}`) and buy-now's coupon are three doors onto
|
|
24
|
+
* the same counter, and a hold placed at one of them has to be visible at the
|
|
25
|
+
* other two. Three copies of this transaction would be three chances for one of
|
|
26
|
+
* them to drift back to a plain `.get()`.
|
|
27
|
+
*/
|
|
28
|
+
/** What a placed hold gives back to the caller. */
|
|
29
|
+
export interface PromotionSlotHold {
|
|
30
|
+
/**
|
|
31
|
+
* The key the hold was stored under, or `''` when the promotion is uncapped
|
|
32
|
+
* and no hold was needed. Carried in the session metadata so the webhook can
|
|
33
|
+
* settle exactly this reservation; its absence is what tells the webhook to
|
|
34
|
+
* fall back to the unconditional increment.
|
|
35
|
+
*/
|
|
36
|
+
holdKey: string;
|
|
37
|
+
/**
|
|
38
|
+
* Drop the hold. Called by every refusal BELOW the checkout claim, and by
|
|
39
|
+
* `checkout.session.expired`. Best-effort and never throws: the TTL is the
|
|
40
|
+
* backstop, and tidying a reservation must not turn a clean 400 into a 500.
|
|
41
|
+
*/
|
|
42
|
+
release: () => Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Why a slot could not be claimed.
|
|
46
|
+
*
|
|
47
|
+
* - `exhausted` — every slot is spoken for, by a settled redemption or a live
|
|
48
|
+
* hold. The one refusal a shopper is told about by name.
|
|
49
|
+
* - `missing` — the promotion document is gone, deleted between the resolver's
|
|
50
|
+
* read and this transaction.
|
|
51
|
+
* - `error` — Firestore refused. The discount is REFUSED rather than given
|
|
52
|
+
* away uncounted: a promotion applied against a counter that could not be
|
|
53
|
+
* reserved is the defect this file exists to close.
|
|
54
|
+
*/
|
|
55
|
+
export type PromotionSlotRefusal = 'exhausted' | 'missing' | 'error';
|
|
56
|
+
/**
|
|
57
|
+
* Written as a two-member union with `reason` on BOTH members rather than a
|
|
58
|
+
* `{ ok: true } & PromotionSlotHold` intersection. Discriminated-union
|
|
59
|
+
* narrowing does not reach through the intersection under this repo's compiler
|
|
60
|
+
* settings (`strictNullChecks` is off repo-wide), so the intersection form
|
|
61
|
+
* compiled to `Property 'reason' does not exist` at every call site.
|
|
62
|
+
*/
|
|
63
|
+
export type PromotionSlotOutcome = {
|
|
64
|
+
ok: true;
|
|
65
|
+
reason?: undefined;
|
|
66
|
+
holdKey: string;
|
|
67
|
+
release: () => Promise<void>;
|
|
68
|
+
} | {
|
|
69
|
+
ok: false;
|
|
70
|
+
reason: PromotionSlotRefusal;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* A stable per-attempt hold key.
|
|
74
|
+
*
|
|
75
|
+
* `claim.stripeKey` when the client sent an Idempotency-Key, so a retry of one
|
|
76
|
+
* attempt re-claims its OWN slot rather than being refused by it. When it did
|
|
77
|
+
* not, a fresh random key per request — NOT the literal string `'null'`, which
|
|
78
|
+
* is what `String(claim.stripeKey)` yields and which would make every keyless
|
|
79
|
+
* shopper on the site share one hold. Sharing a hold key is the defect: two
|
|
80
|
+
* shoppers would each read the other's reservation as their own retry and both
|
|
81
|
+
* pass a cap of one.
|
|
82
|
+
*
|
|
83
|
+
* A keyless retry therefore holds a second slot. That is the safe direction —
|
|
84
|
+
* it over-reserves and releases on refusal or expiry — and the keyless path is
|
|
85
|
+
* already recorded as a real hole in `api-idempotency.ts` (AGL-2163) rather
|
|
86
|
+
* than something this fix can close.
|
|
87
|
+
*/
|
|
88
|
+
export declare function promotionHoldKey(stripeKey: string | null): string;
|
|
89
|
+
/**
|
|
90
|
+
* Claim one redemption slot, or refuse.
|
|
91
|
+
*
|
|
92
|
+
* The re-check and the write are ONE transaction over the SAME document, which
|
|
93
|
+
* is the whole fix: the old code read the counter with a plain `.get()` at
|
|
94
|
+
* session creation and the webhook incremented it minutes later without ever
|
|
95
|
+
* re-asking. Firestore aborts and re-runs this callback if the promotion
|
|
96
|
+
* changed between the read and the commit, so two checkouts contending for the
|
|
97
|
+
* last slot cannot both observe it free.
|
|
98
|
+
*
|
|
99
|
+
* An UNCAPPED promotion writes nothing at all and returns an empty `holdKey`.
|
|
100
|
+
* There is no slot to reserve, and keeping the unlimited case off this write
|
|
101
|
+
* path keeps the fix's blast radius on the capped case.
|
|
102
|
+
*/
|
|
103
|
+
export declare function holdPromotionSlot(options: {
|
|
104
|
+
firestore: FirebaseFirestore.Firestore;
|
|
105
|
+
ref: FirebaseFirestore.DocumentReference;
|
|
106
|
+
holdKey: string;
|
|
107
|
+
nowMs?: number;
|
|
108
|
+
/** Label for the failure log; never shown to a shopper. */
|
|
109
|
+
label: string;
|
|
110
|
+
}): Promise<PromotionSlotOutcome>;
|
|
111
|
+
/**
|
|
112
|
+
* Give a slot back.
|
|
113
|
+
*
|
|
114
|
+
* A bare merge-set with the delete sentinel rather than a transaction: removing
|
|
115
|
+
* one key is not a read-modify-write, and a release must not be able to fail
|
|
116
|
+
* the response it is tidying up after. Swallows its own error for the same
|
|
117
|
+
* reason — the TTL still releases the slot.
|
|
118
|
+
*/
|
|
119
|
+
export declare function releasePromotionHold(ref: FirebaseFirestore.DocumentReference, holdKey: string): Promise<void>;
|
|
120
|
+
/** What a settlement did, for the caller's orphan reporting. */
|
|
121
|
+
export type PromotionSettlement =
|
|
122
|
+
/** The hold was found and converted into a redemption. */
|
|
123
|
+
'settled'
|
|
124
|
+
/** No hold under this key: a redelivery, already counted. Do nothing. */
|
|
125
|
+
| 'already-settled'
|
|
126
|
+
/** The promotion document is gone; the redemption cannot be recorded. */
|
|
127
|
+
| 'missing'
|
|
128
|
+
/** Firestore refused; the hold stands and lapses on its own. */
|
|
129
|
+
| 'error';
|
|
130
|
+
/**
|
|
131
|
+
* Convert a held slot into a counted redemption.
|
|
132
|
+
*
|
|
133
|
+
* Idempotent under Stripe's at-least-once delivery, which is the property
|
|
134
|
+
* `reconcile-stock.ts:52-58` asks any webhook-side guard on these counters to
|
|
135
|
+
* have: the redemption is owed only by the presence of the HOLD, so a second
|
|
136
|
+
* delivery finds none and takes nothing. The increment and the removal are one
|
|
137
|
+
* write, so there is no window in which the slot is both counted and held.
|
|
138
|
+
*/
|
|
139
|
+
export declare function settlePromotionSlot(options: {
|
|
140
|
+
firestore: FirebaseFirestore.Firestore;
|
|
141
|
+
ref: FirebaseFirestore.DocumentReference;
|
|
142
|
+
holdKey: string;
|
|
143
|
+
label: string;
|
|
144
|
+
}): Promise<PromotionSettlement>;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import * as CommerceModel from "../model/index.js";
|
|
18
|
+
import { firebaseAdmin } from "@aglyn/tenant-data-admin";
|
|
19
|
+
import { randomUUID } from "crypto";
|
|
20
|
+
/**
|
|
21
|
+
* A stable per-attempt hold key.
|
|
22
|
+
*
|
|
23
|
+
* `claim.stripeKey` when the client sent an Idempotency-Key, so a retry of one
|
|
24
|
+
* attempt re-claims its OWN slot rather than being refused by it. When it did
|
|
25
|
+
* not, a fresh random key per request — NOT the literal string `'null'`, which
|
|
26
|
+
* is what `String(claim.stripeKey)` yields and which would make every keyless
|
|
27
|
+
* shopper on the site share one hold. Sharing a hold key is the defect: two
|
|
28
|
+
* shoppers would each read the other's reservation as their own retry and both
|
|
29
|
+
* pass a cap of one.
|
|
30
|
+
*
|
|
31
|
+
* A keyless retry therefore holds a second slot. That is the safe direction —
|
|
32
|
+
* it over-reserves and releases on refusal or expiry — and the keyless path is
|
|
33
|
+
* already recorded as a real hole in `api-idempotency.ts` (AGL-2163) rather
|
|
34
|
+
* than something this fix can close.
|
|
35
|
+
*/ export function promotionHoldKey(stripeKey) {
|
|
36
|
+
return stripeKey || `anon-${randomUUID()}`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Claim one redemption slot, or refuse.
|
|
40
|
+
*
|
|
41
|
+
* The re-check and the write are ONE transaction over the SAME document, which
|
|
42
|
+
* is the whole fix: the old code read the counter with a plain `.get()` at
|
|
43
|
+
* session creation and the webhook incremented it minutes later without ever
|
|
44
|
+
* re-asking. Firestore aborts and re-runs this callback if the promotion
|
|
45
|
+
* changed between the read and the commit, so two checkouts contending for the
|
|
46
|
+
* last slot cannot both observe it free.
|
|
47
|
+
*
|
|
48
|
+
* An UNCAPPED promotion writes nothing at all and returns an empty `holdKey`.
|
|
49
|
+
* There is no slot to reserve, and keeping the unlimited case off this write
|
|
50
|
+
* path keeps the fix's blast radius on the capped case.
|
|
51
|
+
*/ export async function holdPromotionSlot(options) {
|
|
52
|
+
var _options_nowMs;
|
|
53
|
+
const { firestore, ref, holdKey, label } = options;
|
|
54
|
+
const nowMs = (_options_nowMs = options.nowMs) != null ? _options_nowMs : Date.now();
|
|
55
|
+
const outcome = await firestore.runTransaction(async (transaction)=>{
|
|
56
|
+
var _fresh_data, _promotion_holds;
|
|
57
|
+
const fresh = await transaction.get(ref);
|
|
58
|
+
if (!fresh.exists) return 'missing';
|
|
59
|
+
const promotion = (_fresh_data = fresh.data()) != null ? _fresh_data : {};
|
|
60
|
+
// Uncapped: nothing to reserve, and nothing written. The webhook keeps
|
|
61
|
+
// the plain `increment(1)` for these, exactly as before this issue.
|
|
62
|
+
if (CommerceModel.promotionRemainingSlots(promotion, nowMs, holdKey) == null) {
|
|
63
|
+
return 'uncapped';
|
|
64
|
+
}
|
|
65
|
+
// This attempt's own prior hold does not stand in its way — a retry must
|
|
66
|
+
// be able to re-claim what it already reserved, or the second press of
|
|
67
|
+
// the same button refuses the shopper a slot they are already holding.
|
|
68
|
+
if (CommerceModel.promotionExhausted(promotion, nowMs, holdKey)) {
|
|
69
|
+
return 'exhausted';
|
|
70
|
+
}
|
|
71
|
+
// Lapsed holds are removed by SENTINEL, never by writing back a locally
|
|
72
|
+
// pruned copy of the map: `set(…, { merge: true })` DEEP-merges nested
|
|
73
|
+
// maps, so an object with the key removed leaves the stored key exactly
|
|
74
|
+
// where it was. Correctness does not depend on the sweep — every read
|
|
75
|
+
// prunes again — but without it the document grows one dead key per
|
|
76
|
+
// abandoned checkout, forever. The same sentinel is load-bearing at
|
|
77
|
+
// SETTLEMENT below, where writing back a pruned map would leave the hold
|
|
78
|
+
// standing and let a webhook redelivery count the redemption twice.
|
|
79
|
+
const live = CommerceModel.prunePromotionHolds(promotion.holds, nowMs);
|
|
80
|
+
const swept = {};
|
|
81
|
+
for (const stale of Object.keys((_promotion_holds = promotion.holds) != null ? _promotion_holds : {})){
|
|
82
|
+
if (stale !== holdKey && !live[stale]) {
|
|
83
|
+
swept[stale] = firebaseAdmin.firestore.FieldValue.delete();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
transaction.set(ref, {
|
|
87
|
+
holds: _extends({}, swept, {
|
|
88
|
+
[holdKey]: {
|
|
89
|
+
expiresAtMs: nowMs + CommerceModel.PROMOTION_HOLD_TTL_MS
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
}, {
|
|
93
|
+
merge: true
|
|
94
|
+
});
|
|
95
|
+
return 'held';
|
|
96
|
+
}).catch((error)=>{
|
|
97
|
+
console.error('Promotion hold failed', label, error);
|
|
98
|
+
return 'error';
|
|
99
|
+
});
|
|
100
|
+
if (outcome === 'uncapped') {
|
|
101
|
+
return {
|
|
102
|
+
ok: true,
|
|
103
|
+
holdKey: '',
|
|
104
|
+
release: async ()=>undefined
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (outcome === 'held') {
|
|
108
|
+
return {
|
|
109
|
+
ok: true,
|
|
110
|
+
holdKey,
|
|
111
|
+
release: ()=>releasePromotionHold(ref, holdKey)
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
ok: false,
|
|
116
|
+
reason: outcome
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Give a slot back.
|
|
121
|
+
*
|
|
122
|
+
* A bare merge-set with the delete sentinel rather than a transaction: removing
|
|
123
|
+
* one key is not a read-modify-write, and a release must not be able to fail
|
|
124
|
+
* the response it is tidying up after. Swallows its own error for the same
|
|
125
|
+
* reason — the TTL still releases the slot.
|
|
126
|
+
*/ export async function releasePromotionHold(ref, holdKey) {
|
|
127
|
+
if (!holdKey) return;
|
|
128
|
+
await ref.set({
|
|
129
|
+
holds: {
|
|
130
|
+
[holdKey]: firebaseAdmin.firestore.FieldValue.delete()
|
|
131
|
+
}
|
|
132
|
+
}, {
|
|
133
|
+
merge: true
|
|
134
|
+
}).catch((error)=>{
|
|
135
|
+
console.error('Promotion hold release failed', ref.path, error);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Convert a held slot into a counted redemption.
|
|
140
|
+
*
|
|
141
|
+
* Idempotent under Stripe's at-least-once delivery, which is the property
|
|
142
|
+
* `reconcile-stock.ts:52-58` asks any webhook-side guard on these counters to
|
|
143
|
+
* have: the redemption is owed only by the presence of the HOLD, so a second
|
|
144
|
+
* delivery finds none and takes nothing. The increment and the removal are one
|
|
145
|
+
* write, so there is no window in which the slot is both counted and held.
|
|
146
|
+
*/ export async function settlePromotionSlot(options) {
|
|
147
|
+
const { firestore, ref, holdKey, label } = options;
|
|
148
|
+
return await firestore.runTransaction(async (transaction)=>{
|
|
149
|
+
var _fresh_data;
|
|
150
|
+
const fresh = await transaction.get(ref);
|
|
151
|
+
if (!fresh.exists) return 'missing';
|
|
152
|
+
const promotion = (_fresh_data = fresh.data()) != null ? _fresh_data : {};
|
|
153
|
+
if (!CommerceModel.promotionSettles(promotion, holdKey)) {
|
|
154
|
+
return 'already-settled';
|
|
155
|
+
}
|
|
156
|
+
transaction.set(ref, {
|
|
157
|
+
redemptions: firebaseAdmin.firestore.FieldValue.increment(1),
|
|
158
|
+
// `FieldValue.delete()`, NOT a pruned copy of the map — see the note
|
|
159
|
+
// in `holdPromotionSlot`. A merged-back object would leave the hold
|
|
160
|
+
// standing and a redelivery would increment a SECOND time.
|
|
161
|
+
holds: {
|
|
162
|
+
[holdKey]: firebaseAdmin.firestore.FieldValue.delete()
|
|
163
|
+
}
|
|
164
|
+
}, {
|
|
165
|
+
merge: true
|
|
166
|
+
});
|
|
167
|
+
return 'settled';
|
|
168
|
+
}).catch((error)=>{
|
|
169
|
+
console.error('Promotion settlement failed', label, error);
|
|
170
|
+
return 'error';
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
//# sourceMappingURL=promotion-hold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/promotion-hold.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 * as CommerceModel from '../model'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { randomUUID } from 'crypto'\n\n/**\n * Reserve-then-settle for promotion redemption slots (AGL-2453).\n *\n * The I/O half of `model/commerce-promotions.ts` — read that file first; it\n * carries the decision this one implements. Three call sites share it, and\n * sharing is the point: the typed coupon (`coupons/{code}`), the automatic\n * AGL-305 discount (`discounts/{id}`) and buy-now's coupon are three doors onto\n * the same counter, and a hold placed at one of them has to be visible at the\n * other two. Three copies of this transaction would be three chances for one of\n * them to drift back to a plain `.get()`.\n */\n\n/** What a placed hold gives back to the caller. */\nexport interface PromotionSlotHold {\n /**\n * The key the hold was stored under, or `''` when the promotion is uncapped\n * and no hold was needed. Carried in the session metadata so the webhook can\n * settle exactly this reservation; its absence is what tells the webhook to\n * fall back to the unconditional increment.\n */\n holdKey: string\n /**\n * Drop the hold. Called by every refusal BELOW the checkout claim, and by\n * `checkout.session.expired`. Best-effort and never throws: the TTL is the\n * backstop, and tidying a reservation must not turn a clean 400 into a 500.\n */\n release: () => Promise<void>\n}\n\n/**\n * Why a slot could not be claimed.\n *\n * - `exhausted` — every slot is spoken for, by a settled redemption or a live\n * hold. The one refusal a shopper is told about by name.\n * - `missing` — the promotion document is gone, deleted between the resolver's\n * read and this transaction.\n * - `error` — Firestore refused. The discount is REFUSED rather than given\n * away uncounted: a promotion applied against a counter that could not be\n * reserved is the defect this file exists to close.\n */\nexport type PromotionSlotRefusal = 'exhausted' | 'missing' | 'error'\n\n/**\n * Written as a two-member union with `reason` on BOTH members rather than a\n * `{ ok: true } & PromotionSlotHold` intersection. Discriminated-union\n * narrowing does not reach through the intersection under this repo's compiler\n * settings (`strictNullChecks` is off repo-wide), so the intersection form\n * compiled to `Property 'reason' does not exist` at every call site.\n */\nexport type PromotionSlotOutcome =\n | { ok: true; reason?: undefined; holdKey: string; release: () => Promise<void> }\n | { ok: false; reason: PromotionSlotRefusal }\n\n/**\n * A stable per-attempt hold key.\n *\n * `claim.stripeKey` when the client sent an Idempotency-Key, so a retry of one\n * attempt re-claims its OWN slot rather than being refused by it. When it did\n * not, a fresh random key per request — NOT the literal string `'null'`, which\n * is what `String(claim.stripeKey)` yields and which would make every keyless\n * shopper on the site share one hold. Sharing a hold key is the defect: two\n * shoppers would each read the other's reservation as their own retry and both\n * pass a cap of one.\n *\n * A keyless retry therefore holds a second slot. That is the safe direction —\n * it over-reserves and releases on refusal or expiry — and the keyless path is\n * already recorded as a real hole in `api-idempotency.ts` (AGL-2163) rather\n * than something this fix can close.\n */\nexport function promotionHoldKey(stripeKey: string | null): string {\n return stripeKey || `anon-${randomUUID()}`\n}\n\n/**\n * Claim one redemption slot, or refuse.\n *\n * The re-check and the write are ONE transaction over the SAME document, which\n * is the whole fix: the old code read the counter with a plain `.get()` at\n * session creation and the webhook incremented it minutes later without ever\n * re-asking. Firestore aborts and re-runs this callback if the promotion\n * changed between the read and the commit, so two checkouts contending for the\n * last slot cannot both observe it free.\n *\n * An UNCAPPED promotion writes nothing at all and returns an empty `holdKey`.\n * There is no slot to reserve, and keeping the unlimited case off this write\n * path keeps the fix's blast radius on the capped case.\n */\nexport async function holdPromotionSlot(options: {\n firestore: FirebaseFirestore.Firestore\n ref: FirebaseFirestore.DocumentReference\n holdKey: string\n nowMs?: number\n /** Label for the failure log; never shown to a shopper. */\n label: string\n}): Promise<PromotionSlotOutcome> {\n const { firestore, ref, holdKey, label } = options\n const nowMs = options.nowMs ?? Date.now()\n const outcome = await firestore\n .runTransaction(async (transaction) => {\n const fresh = await transaction.get(ref)\n if (!fresh.exists) return 'missing' as const\n const promotion = (fresh.data() ?? {}) as CommerceModel.HeldPromotion\n // Uncapped: nothing to reserve, and nothing written. The webhook keeps\n // the plain `increment(1)` for these, exactly as before this issue.\n if (\n CommerceModel.promotionRemainingSlots(promotion, nowMs, holdKey) == null\n ) {\n return 'uncapped' as const\n }\n // This attempt's own prior hold does not stand in its way — a retry must\n // be able to re-claim what it already reserved, or the second press of\n // the same button refuses the shopper a slot they are already holding.\n if (CommerceModel.promotionExhausted(promotion, nowMs, holdKey)) {\n return 'exhausted' as const\n }\n // Lapsed holds are removed by SENTINEL, never by writing back a locally\n // pruned copy of the map: `set(…, { merge: true })` DEEP-merges nested\n // maps, so an object with the key removed leaves the stored key exactly\n // where it was. Correctness does not depend on the sweep — every read\n // prunes again — but without it the document grows one dead key per\n // abandoned checkout, forever. The same sentinel is load-bearing at\n // SETTLEMENT below, where writing back a pruned map would leave the hold\n // standing and let a webhook redelivery count the redemption twice.\n const live = CommerceModel.prunePromotionHolds(promotion.holds, nowMs)\n const swept: Record<string, unknown> = {}\n for (const stale of Object.keys(promotion.holds ?? {})) {\n if (stale !== holdKey && !live[stale]) {\n swept[stale] = firebaseAdmin.firestore.FieldValue.delete()\n }\n }\n transaction.set(\n ref,\n {\n holds: {\n ...swept,\n [holdKey]: {\n expiresAtMs: nowMs + CommerceModel.PROMOTION_HOLD_TTL_MS,\n },\n },\n },\n { merge: true },\n )\n return 'held' as const\n })\n .catch((error: unknown) => {\n console.error('Promotion hold failed', label, error)\n return 'error' as const\n })\n if (outcome === 'uncapped') {\n return { ok: true, holdKey: '', release: async () => undefined }\n }\n if (outcome === 'held') {\n return { ok: true, holdKey, release: () => releasePromotionHold(ref, holdKey) }\n }\n return { ok: false, reason: outcome }\n}\n\n/**\n * Give a slot back.\n *\n * A bare merge-set with the delete sentinel rather than a transaction: removing\n * one key is not a read-modify-write, and a release must not be able to fail\n * the response it is tidying up after. Swallows its own error for the same\n * reason — the TTL still releases the slot.\n */\nexport async function releasePromotionHold(\n ref: FirebaseFirestore.DocumentReference,\n holdKey: string,\n): Promise<void> {\n if (!holdKey) return\n await ref\n .set(\n { holds: { [holdKey]: firebaseAdmin.firestore.FieldValue.delete() } },\n { merge: true },\n )\n .catch((error: unknown) => {\n console.error('Promotion hold release failed', ref.path, error)\n })\n}\n\n/** What a settlement did, for the caller's orphan reporting. */\nexport type PromotionSettlement =\n /** The hold was found and converted into a redemption. */\n | 'settled'\n /** No hold under this key: a redelivery, already counted. Do nothing. */\n | 'already-settled'\n /** The promotion document is gone; the redemption cannot be recorded. */\n | 'missing'\n /** Firestore refused; the hold stands and lapses on its own. */\n | 'error'\n\n/**\n * Convert a held slot into a counted redemption.\n *\n * Idempotent under Stripe's at-least-once delivery, which is the property\n * `reconcile-stock.ts:52-58` asks any webhook-side guard on these counters to\n * have: the redemption is owed only by the presence of the HOLD, so a second\n * delivery finds none and takes nothing. The increment and the removal are one\n * write, so there is no window in which the slot is both counted and held.\n */\nexport async function settlePromotionSlot(options: {\n firestore: FirebaseFirestore.Firestore\n ref: FirebaseFirestore.DocumentReference\n holdKey: string\n label: string\n}): Promise<PromotionSettlement> {\n const { firestore, ref, holdKey, label } = options\n return await firestore\n .runTransaction(async (transaction) => {\n const fresh = await transaction.get(ref)\n if (!fresh.exists) return 'missing' as const\n const promotion = (fresh.data() ?? {}) as CommerceModel.HeldPromotion\n if (!CommerceModel.promotionSettles(promotion, holdKey)) {\n return 'already-settled' as const\n }\n transaction.set(\n ref,\n {\n redemptions: firebaseAdmin.firestore.FieldValue.increment(1),\n // `FieldValue.delete()`, NOT a pruned copy of the map — see the note\n // in `holdPromotionSlot`. A merged-back object would leave the hold\n // standing and a redelivery would increment a SECOND time.\n holds: {\n [holdKey]: firebaseAdmin.firestore.FieldValue.delete(),\n },\n },\n { merge: true },\n )\n return 'settled' as const\n })\n .catch((error: unknown) => {\n console.error('Promotion settlement failed', label, error)\n return 'error' as const\n })\n}\n"],"names":["CommerceModel","firebaseAdmin","randomUUID","promotionHoldKey","stripeKey","holdPromotionSlot","options","firestore","ref","holdKey","label","nowMs","Date","now","outcome","runTransaction","transaction","fresh","promotion","get","exists","data","promotionRemainingSlots","promotionExhausted","live","prunePromotionHolds","holds","swept","stale","Object","keys","FieldValue","delete","set","expiresAtMs","PROMOTION_HOLD_TTL_MS","merge","catch","error","console","ok","release","undefined","releasePromotionHold","reason","path","settlePromotionSlot","promotionSettles","redemptions","increment"],"mappings":"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"}
|
|
@@ -0,0 +1,106 @@
|
|
|
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 PluginJobHostGate } from '@aglyn/aglyn/server';
|
|
18
|
+
export interface MissingSaleDecrement {
|
|
19
|
+
hostId: string;
|
|
20
|
+
orderId: string;
|
|
21
|
+
/** The merchant-facing sequential number, when the doc carries one. */
|
|
22
|
+
orderNumber: number | null;
|
|
23
|
+
orderStatus: string;
|
|
24
|
+
orderCreatedAtMs: number;
|
|
25
|
+
productId: string;
|
|
26
|
+
/** Live product name — the order's snapshot name is used as a fallback. */
|
|
27
|
+
productName: string;
|
|
28
|
+
variantId: string;
|
|
29
|
+
/** Units the order says were sold, and the shelf therefore still holds. */
|
|
30
|
+
quantity: number;
|
|
31
|
+
}
|
|
32
|
+
export interface StockReconciliationResult {
|
|
33
|
+
ordersScanned: number;
|
|
34
|
+
/** Orders that reached the ledger comparison (post status/grace filter). */
|
|
35
|
+
ordersChecked: number;
|
|
36
|
+
missing: MissingSaleDecrement[];
|
|
37
|
+
/**
|
|
38
|
+
* The ledger read hit its limit before reaching back as far as the oldest
|
|
39
|
+
* order considered, so orders older than the ledger window were DROPPED
|
|
40
|
+
* rather than judged against rows that were never read.
|
|
41
|
+
*
|
|
42
|
+
* Recorded and reported rather than swallowed, for the AGL-2321 reason:
|
|
43
|
+
* `siteSizeTruncated` exists because a number that is silently a lower
|
|
44
|
+
* bound is worse than one that says so. A truncated pass under-reports; it
|
|
45
|
+
* must never be read as "all matched".
|
|
46
|
+
*/
|
|
47
|
+
ledgerTruncated: boolean;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Compares one host's recently-decremented orders against the
|
|
51
|
+
* `reason: 'sale'` rows joined to them, and reports the lines whose decrement
|
|
52
|
+
* is missing.
|
|
53
|
+
*
|
|
54
|
+
* Reads are bounded and cheap in the healthy case: two collection queries per
|
|
55
|
+
* host, and a product read ONLY for a line that already looks divergent.
|
|
56
|
+
*
|
|
57
|
+
* Two equality-free queries so the automatic single-field indexes answer them
|
|
58
|
+
* (the `cancel-order.ts` rule): `orderBy(createdAtMs desc).limit()` and
|
|
59
|
+
* `orderBy(atMs desc).limit()`, with the status test applied in memory over
|
|
60
|
+
* the handful of documents that come back.
|
|
61
|
+
*/
|
|
62
|
+
export declare function reconcileHostStockDecrements(options: {
|
|
63
|
+
firestore: any;
|
|
64
|
+
hostRef: any;
|
|
65
|
+
hostId: string;
|
|
66
|
+
nowMs?: number;
|
|
67
|
+
graceMs?: number;
|
|
68
|
+
orderLimit?: number;
|
|
69
|
+
ledgerLimit?: number;
|
|
70
|
+
}): Promise<StockReconciliationResult>;
|
|
71
|
+
/**
|
|
72
|
+
* Reports a host's divergences to its managers, once per order.
|
|
73
|
+
*
|
|
74
|
+
* The marker in `hosts/{hostId}/inventoryReconciliation/{orderId}` is the only
|
|
75
|
+
* write this whole pass makes, and it lives in a collection nothing else on
|
|
76
|
+
* the platform reads or writes — the detector must not be able to affect the
|
|
77
|
+
* money path it is watching. Without it a beat would re-nag every hour about
|
|
78
|
+
* an order the merchant has already corrected, and an alert that repeats
|
|
79
|
+
* forever is one that gets muted.
|
|
80
|
+
*/
|
|
81
|
+
export declare function reportMissingSaleDecrements(hostId: string, missing: readonly MissingSaleDecrement[]): Promise<number>;
|
|
82
|
+
export interface StockReconciliationScan {
|
|
83
|
+
hosts: number;
|
|
84
|
+
ordersScanned: number;
|
|
85
|
+
missingLines: number;
|
|
86
|
+
reportedOrders: number;
|
|
87
|
+
truncatedHosts: number;
|
|
88
|
+
/** Hosts left unreconciled because they are locked (AGL-2495). */
|
|
89
|
+
skippedLocked: number;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The platform beat: the most recent orders across every host, grouped by
|
|
93
|
+
* host and reconciled one host at a time.
|
|
94
|
+
*
|
|
95
|
+
* Ordered by `createdAtMs` and NOT filtered by status, because an equality
|
|
96
|
+
* filter plus an ordering is a composite index and the ordering alone is a
|
|
97
|
+
* field override — the same trade `cancel-order.ts` takes. Every order writer
|
|
98
|
+
* in the plugin stamps `createdAtMs` (cart, buy-now, draft, POS cash, POS
|
|
99
|
+
* card, subscription cycle), so nothing is invisible to the ordering.
|
|
100
|
+
*/
|
|
101
|
+
export declare function scanStockDecrements(
|
|
102
|
+
/** The lockdown gate, injected by the caller (AGL-2495). Not optional. */
|
|
103
|
+
gate: PluginJobHostGate, options?: {
|
|
104
|
+
orderLimit?: number;
|
|
105
|
+
nowMs?: number;
|
|
106
|
+
}): Promise<StockReconciliationScan>;
|