@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,349 @@
|
|
|
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 { _ as _extends } from "@swc/helpers/_/_extends";
|
|
17
|
+
import { firebaseAdmin, notifyHostManagers } from "@aglyn/tenant-data-admin";
|
|
18
|
+
import * as CommerceModel from "../model/index.js";
|
|
19
|
+
/**
|
|
20
|
+
* THE MISSING-DECREMENT DETECTOR (AGL-2358).
|
|
21
|
+
*
|
|
22
|
+
* Every order-minting branch of `billing-webhook.ts` commits the order in a
|
|
23
|
+
* transaction and decrements stock AFTER it, and every branch guards on a
|
|
24
|
+
* replay flag that transaction set — `created` for cart and buy-now,
|
|
25
|
+
* `flipped` for the draft/POS-card branch, the invoice-id guard for a
|
|
26
|
+
* subscription cycle. So a process that dies in the window between the two
|
|
27
|
+
* loses the decrement PERMANENTLY: Stripe redelivers, the guard says the
|
|
28
|
+
* order already exists, and the handler returns without ever reaching the
|
|
29
|
+
* stock write. The merchant has a paid order and an untouched shelf, and
|
|
30
|
+
* before this nothing anywhere recorded that they had diverged.
|
|
31
|
+
*
|
|
32
|
+
* `/api/billing/webhook`'s own claim marker makes the same window wider, not
|
|
33
|
+
* narrower: AGL-2157 decided that an event which throws mid-dispatch KEEPS
|
|
34
|
+
* its claim — deliberately, because re-running the dispatch could double-apply
|
|
35
|
+
* a gift-card spend or a coupon redemption — and told staff to "reconcile by
|
|
36
|
+
* hand". This is the thing they reconcile with. It is also the only detector
|
|
37
|
+
* for the death AGL-2157 cannot catch at all: a timeout, an OOM or a deploy
|
|
38
|
+
* kill unwinds no stack, so that route's `catch` never runs and no staff
|
|
39
|
+
* notice is ever raised.
|
|
40
|
+
*
|
|
41
|
+
* WHY A DETECTOR AND NOT THE FIX. The two real fixes were both weighed and
|
|
42
|
+
* both rejected for this side of the Sept 1 freeze:
|
|
43
|
+
*
|
|
44
|
+
* - Folding the decrement into the order transaction would restructure the
|
|
45
|
+
* checkout path's largest and most contended transaction, days before
|
|
46
|
+
* launch, on the path that takes money. The cart branch alone would have
|
|
47
|
+
* to pull N product reads into a transaction that currently reads two
|
|
48
|
+
* documents, and every contended retry would then re-run order numbering.
|
|
49
|
+
* - A second replay flag with a re-entry path is worse, not better. The
|
|
50
|
+
* `created` guard is not protecting the decrement — it is protecting
|
|
51
|
+
* licence-key assignment, coupon redemption counters, gift-card balances,
|
|
52
|
+
* receipt email and contact ingestion, all behind ONE flag. Any re-entry
|
|
53
|
+
* past it needs every one of those to become individually idempotent
|
|
54
|
+
* first, which is precisely the "per-EFFECT idempotency sweep" AGL-2157
|
|
55
|
+
* named as the real fix and deferred for the same reason.
|
|
56
|
+
*
|
|
57
|
+
* And the flag is not even the only lock. Deleting it does not make a
|
|
58
|
+
* redelivery decrement: `cartRef.delete()` runs between the guard and the
|
|
59
|
+
* inventory loop, so the second delivery iterates an empty cart. A
|
|
60
|
+
* resumable path would additionally have to stop reading the cart and
|
|
61
|
+
* start reading the order's own `lineItems`. That is measured, not
|
|
62
|
+
* assumed — `stock-decrement-reconciliation.spec.ts` pins it.
|
|
63
|
+
*
|
|
64
|
+
* This is read-only against orders, products and the ledger. The only thing
|
|
65
|
+
* it writes is its own report marker, in a collection nothing else touches.
|
|
66
|
+
*/ /**
|
|
67
|
+
* The statuses an order can only be in if its stock decrement was ATTEMPTED.
|
|
68
|
+
*
|
|
69
|
+
* `pending` is excluded because it is the pre-payment state of a draft link
|
|
70
|
+
* and a POS card sale — neither has decremented yet, and both are supposed to
|
|
71
|
+
* look exactly like this. `cancelled` and `refunded` are excluded because
|
|
72
|
+
* `cancel-order.ts` and `refund.ts` write `reason: 'cancellation'` /
|
|
73
|
+
* `'refund'` rows and may have released the units already; a sale row missing
|
|
74
|
+
* from an order that has been through a release is a question this detector
|
|
75
|
+
* cannot answer from the ledger alone, and guessing would be worse than
|
|
76
|
+
* staying quiet.
|
|
77
|
+
*/ const DECREMENTED_STATUSES = new Set([
|
|
78
|
+
'paid',
|
|
79
|
+
'partially_fulfilled',
|
|
80
|
+
'fulfilled',
|
|
81
|
+
'delivered'
|
|
82
|
+
]);
|
|
83
|
+
/**
|
|
84
|
+
* How long an order gets to finish its own decrement before it counts as
|
|
85
|
+
* diverged. The decrement runs milliseconds to seconds after the order
|
|
86
|
+
* commits, so ten minutes is far past any healthy webhook — and a beat that
|
|
87
|
+
* flagged an order the webhook was still working on would report a divergence
|
|
88
|
+
* that fixed itself, which is the fastest way to make a signal ignored.
|
|
89
|
+
*/ const DEFAULT_GRACE_MS = 10 * 60 * 1000;
|
|
90
|
+
/**
|
|
91
|
+
* Compares one host's recently-decremented orders against the
|
|
92
|
+
* `reason: 'sale'` rows joined to them, and reports the lines whose decrement
|
|
93
|
+
* is missing.
|
|
94
|
+
*
|
|
95
|
+
* Reads are bounded and cheap in the healthy case: two collection queries per
|
|
96
|
+
* host, and a product read ONLY for a line that already looks divergent.
|
|
97
|
+
*
|
|
98
|
+
* Two equality-free queries so the automatic single-field indexes answer them
|
|
99
|
+
* (the `cancel-order.ts` rule): `orderBy(createdAtMs desc).limit()` and
|
|
100
|
+
* `orderBy(atMs desc).limit()`, with the status test applied in memory over
|
|
101
|
+
* the handful of documents that come back.
|
|
102
|
+
*/ export async function reconcileHostStockDecrements(options) {
|
|
103
|
+
var _orderSnapshot_docs, _ledgerSnapshot_docs;
|
|
104
|
+
const { hostRef, hostId, nowMs = Date.now(), graceMs = DEFAULT_GRACE_MS, orderLimit = 100, ledgerLimit = 500 } = options;
|
|
105
|
+
const empty = {
|
|
106
|
+
ordersScanned: 0,
|
|
107
|
+
ordersChecked: 0,
|
|
108
|
+
missing: [],
|
|
109
|
+
ledgerTruncated: false
|
|
110
|
+
};
|
|
111
|
+
const orderSnapshot = await hostRef.collection('orders').orderBy('createdAtMs', 'desc').limit(orderLimit).get().catch(()=>null);
|
|
112
|
+
if (!orderSnapshot) return empty;
|
|
113
|
+
const orderDocs = (_orderSnapshot_docs = orderSnapshot.docs) != null ? _orderSnapshot_docs : [];
|
|
114
|
+
const settledBefore = nowMs - graceMs;
|
|
115
|
+
let candidates = orderDocs.map((doc)=>{
|
|
116
|
+
var _doc_get, _doc_get1, _doc_get2;
|
|
117
|
+
return {
|
|
118
|
+
id: String(doc.id),
|
|
119
|
+
status: String((_doc_get = doc.get('status')) != null ? _doc_get : ''),
|
|
120
|
+
createdAtMs: Number((_doc_get1 = doc.get('createdAtMs')) != null ? _doc_get1 : 0),
|
|
121
|
+
number: doc.get('number') == null ? null : Number(doc.get('number')),
|
|
122
|
+
lineItems: (_doc_get2 = doc.get('lineItems')) != null ? _doc_get2 : []
|
|
123
|
+
};
|
|
124
|
+
}).filter((order)=>DECREMENTED_STATUSES.has(order.status) && order.createdAtMs > 0 && order.createdAtMs <= settledBefore);
|
|
125
|
+
if (!candidates.length) {
|
|
126
|
+
return _extends({}, empty, {
|
|
127
|
+
ordersScanned: orderDocs.length
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const ledgerSnapshot = await hostRef.collection('inventoryAdjustments').orderBy('atMs', 'desc').limit(ledgerLimit).get().catch(()=>null);
|
|
131
|
+
if (!ledgerSnapshot) {
|
|
132
|
+
return _extends({}, empty, {
|
|
133
|
+
ordersScanned: orderDocs.length
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
const ledgerDocs = (_ledgerSnapshot_docs = ledgerSnapshot.docs) != null ? _ledgerSnapshot_docs : [];
|
|
137
|
+
// `orderId|productId` -> the variant ids that order's sale rows name.
|
|
138
|
+
const soldVariants = new Map();
|
|
139
|
+
let oldestLedgerMs = Number.POSITIVE_INFINITY;
|
|
140
|
+
for (const row of ledgerDocs){
|
|
141
|
+
var _row_get, _row_get1, _row_get2, _soldVariants_get, _row_get3;
|
|
142
|
+
const atMs = Number((_row_get = row.get('atMs')) != null ? _row_get : 0);
|
|
143
|
+
if (atMs > 0) oldestLedgerMs = Math.min(oldestLedgerMs, atMs);
|
|
144
|
+
if (row.get('reason') !== 'sale') continue;
|
|
145
|
+
const orderId = String((_row_get1 = row.get('orderId')) != null ? _row_get1 : '');
|
|
146
|
+
const productId = String((_row_get2 = row.get('productId')) != null ? _row_get2 : '');
|
|
147
|
+
if (!orderId || !productId) continue;
|
|
148
|
+
const key = `${orderId}|${productId}`;
|
|
149
|
+
const seen = (_soldVariants_get = soldVariants.get(key)) != null ? _soldVariants_get : new Set();
|
|
150
|
+
seen.add(String((_row_get3 = row.get('variantId')) != null ? _row_get3 : ''));
|
|
151
|
+
soldVariants.set(key, seen);
|
|
152
|
+
}
|
|
153
|
+
// A ledger read that hit its limit says nothing about anything older than
|
|
154
|
+
// its oldest row. Those orders are dropped, not judged.
|
|
155
|
+
const ledgerTruncated = ledgerDocs.length >= ledgerLimit && Number.isFinite(oldestLedgerMs) && candidates.some((order)=>order.createdAtMs < oldestLedgerMs);
|
|
156
|
+
if (ledgerTruncated) {
|
|
157
|
+
candidates = candidates.filter((order)=>order.createdAtMs >= oldestLedgerMs);
|
|
158
|
+
}
|
|
159
|
+
const missing = [];
|
|
160
|
+
const productCache = new Map();
|
|
161
|
+
for (const order of candidates){
|
|
162
|
+
var _order_lineItems;
|
|
163
|
+
for (const line of (_order_lineItems = order.lineItems) != null ? _order_lineItems : []){
|
|
164
|
+
var _ref, _ref1, _ref2, _ref3, _product_name;
|
|
165
|
+
var _product_variants_;
|
|
166
|
+
const productId = String((_ref = line == null ? void 0 : line.productId) != null ? _ref : '');
|
|
167
|
+
const quantity = Number((_ref1 = line == null ? void 0 : line.quantity) != null ? _ref1 : 0);
|
|
168
|
+
if (!productId || quantity <= 0) continue;
|
|
169
|
+
// A digital or service line never decrements — `decrementVariantStock`
|
|
170
|
+
// refuses an untracked variant — so its missing sale row is the correct
|
|
171
|
+
// outcome and must not cost a product read on every pass. The line's
|
|
172
|
+
// own purchase-time snapshot answers this; anything else falls through
|
|
173
|
+
// to the live tracked-ness test below.
|
|
174
|
+
if ((line == null ? void 0 : line.productType) === 'digital' || (line == null ? void 0 : line.productType) === 'service') {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const sold = soldVariants.get(`${order.id}|${productId}`);
|
|
178
|
+
const declaredVariantId = (line == null ? void 0 : line.variantId) ? String(line.variantId) : '';
|
|
179
|
+
if (declaredVariantId && (sold == null ? void 0 : sold.has(declaredVariantId))) continue;
|
|
180
|
+
if (!declaredVariantId && (sold == null ? void 0 : sold.size)) continue;
|
|
181
|
+
// Only now is a product read worth taking: this line already looks
|
|
182
|
+
// divergent, and the live product is what says whether it could ever
|
|
183
|
+
// have decremented at all.
|
|
184
|
+
if (!productCache.has(productId)) {
|
|
185
|
+
const productDoc = await hostRef.collection('products').doc(productId).get().catch(()=>null);
|
|
186
|
+
productCache.set(productId, (productDoc == null ? void 0 : productDoc.exists) ? CommerceModel.liftLegacyProduct(productDoc.data()) : null);
|
|
187
|
+
}
|
|
188
|
+
const product = productCache.get(productId);
|
|
189
|
+
// A product deleted since the sale is the `unresolvedLines` case the
|
|
190
|
+
// webhook already reports on the order itself (AGL-2149). Nothing here
|
|
191
|
+
// can tell whether its variant was tracked, so it is left alone.
|
|
192
|
+
if (!product) continue;
|
|
193
|
+
const variantId = declaredVariantId || String((_ref2 = (_product_variants_ = product.variants[0]) == null ? void 0 : _product_variants_.id) != null ? _ref2 : '');
|
|
194
|
+
if (!variantId) continue;
|
|
195
|
+
// Re-asked for a line that named no variant: the ledger row records the
|
|
196
|
+
// RESOLVED id, so the default variant has to be resolved before the
|
|
197
|
+
// absence means anything.
|
|
198
|
+
if (!declaredVariantId && (sold == null ? void 0 : sold.has(variantId))) continue;
|
|
199
|
+
const tracked = product.variants.some((variant)=>variant.id === variantId && variant.inventory != null);
|
|
200
|
+
// An untracked variant is a shelf nobody is counting. Its sale row is
|
|
201
|
+
// absent by design, and this is also the one place the detector can be
|
|
202
|
+
// wrong in the merchant's favour: tracking turned ON after the sale
|
|
203
|
+
// makes an order that correctly never decremented look divergent. The
|
|
204
|
+
// window is bounded by `orderLimit` and the report says what it is.
|
|
205
|
+
if (!tracked) continue;
|
|
206
|
+
missing.push({
|
|
207
|
+
hostId,
|
|
208
|
+
orderId: order.id,
|
|
209
|
+
orderNumber: order.number,
|
|
210
|
+
orderStatus: order.status,
|
|
211
|
+
orderCreatedAtMs: order.createdAtMs,
|
|
212
|
+
productId,
|
|
213
|
+
productName: String((_ref3 = (_product_name = product.name) != null ? _product_name : line == null ? void 0 : line.name) != null ? _ref3 : 'Product'),
|
|
214
|
+
variantId,
|
|
215
|
+
quantity
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
ordersScanned: orderDocs.length,
|
|
221
|
+
ordersChecked: candidates.length,
|
|
222
|
+
missing,
|
|
223
|
+
ledgerTruncated
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Reports a host's divergences to its managers, once per order.
|
|
228
|
+
*
|
|
229
|
+
* The marker in `hosts/{hostId}/inventoryReconciliation/{orderId}` is the only
|
|
230
|
+
* write this whole pass makes, and it lives in a collection nothing else on
|
|
231
|
+
* the platform reads or writes — the detector must not be able to affect the
|
|
232
|
+
* money path it is watching. Without it a beat would re-nag every hour about
|
|
233
|
+
* an order the merchant has already corrected, and an alert that repeats
|
|
234
|
+
* forever is one that gets muted.
|
|
235
|
+
*/ export async function reportMissingSaleDecrements(hostId, missing) {
|
|
236
|
+
if (!missing.length) return 0;
|
|
237
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
238
|
+
const hostRef = firestore.collection('hosts').doc(hostId);
|
|
239
|
+
const byOrder = new Map();
|
|
240
|
+
for (const row of missing){
|
|
241
|
+
var _byOrder_get;
|
|
242
|
+
byOrder.set(row.orderId, [
|
|
243
|
+
...(_byOrder_get = byOrder.get(row.orderId)) != null ? _byOrder_get : [],
|
|
244
|
+
row
|
|
245
|
+
]);
|
|
246
|
+
}
|
|
247
|
+
let reported = 0;
|
|
248
|
+
for (const [orderId, lines] of byOrder){
|
|
249
|
+
const markerRef = hostRef.collection('inventoryReconciliation').doc(orderId);
|
|
250
|
+
const marker = await markerRef.get().catch(()=>null);
|
|
251
|
+
// A read that FAILED is not a marker that is absent. Skipping here costs
|
|
252
|
+
// one delayed report; treating it as absent would re-notify every pass
|
|
253
|
+
// for as long as the read kept failing.
|
|
254
|
+
if (!marker || marker.exists) continue;
|
|
255
|
+
const units = lines.reduce((sum, line)=>sum + line.quantity, 0);
|
|
256
|
+
const label = lines[0].orderNumber == null ? orderId : `#${lines[0].orderNumber}`;
|
|
257
|
+
await markerRef.set({
|
|
258
|
+
orderId,
|
|
259
|
+
reportedAtMs: Date.now(),
|
|
260
|
+
lines: lines.map((line)=>({
|
|
261
|
+
productId: line.productId,
|
|
262
|
+
variantId: line.variantId,
|
|
263
|
+
quantity: line.quantity
|
|
264
|
+
}))
|
|
265
|
+
}).catch(()=>undefined);
|
|
266
|
+
reported += 1;
|
|
267
|
+
console.error('commerce stock decrement missing', {
|
|
268
|
+
hostId,
|
|
269
|
+
orderId,
|
|
270
|
+
lines: lines.map((line)=>({
|
|
271
|
+
productId: line.productId,
|
|
272
|
+
variantId: line.variantId,
|
|
273
|
+
quantity: line.quantity
|
|
274
|
+
}))
|
|
275
|
+
});
|
|
276
|
+
void notifyHostManagers(hostId, {
|
|
277
|
+
type: 'content.lowStock',
|
|
278
|
+
title: `Stock never came off the shelf — order ${label}`,
|
|
279
|
+
body: `Order ${label} is ${lines[0].orderStatus} but ${units} unit` + `${units === 1 ? '' : 's'} of ` + `${lines.map((line)=>line.productName).join(', ')} were never ` + 'taken off the count, so the storefront is still offering stock ' + 'that has been sold. Adjust the count by hand.',
|
|
280
|
+
link: `/${hostId}/products`
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
return reported;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* The platform beat: the most recent orders across every host, grouped by
|
|
287
|
+
* host and reconciled one host at a time.
|
|
288
|
+
*
|
|
289
|
+
* Ordered by `createdAtMs` and NOT filtered by status, because an equality
|
|
290
|
+
* filter plus an ordering is a composite index and the ordering alone is a
|
|
291
|
+
* field override — the same trade `cancel-order.ts` takes. Every order writer
|
|
292
|
+
* in the plugin stamps `createdAtMs` (cart, buy-now, draft, POS cash, POS
|
|
293
|
+
* card, subscription cycle), so nothing is invisible to the ordering.
|
|
294
|
+
*/ export async function scanStockDecrements(/** The lockdown gate, injected by the caller (AGL-2495). Not optional. */ gate, options) {
|
|
295
|
+
var _ref, _snapshot_docs;
|
|
296
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
297
|
+
const snapshot = await firestore.collectionGroup('orders').orderBy('createdAtMs', 'desc').limit((_ref = options == null ? void 0 : options.orderLimit) != null ? _ref : 300).get().catch((error)=>{
|
|
298
|
+
console.error('commerce stock reconciliation scan failed', error);
|
|
299
|
+
return null;
|
|
300
|
+
});
|
|
301
|
+
const scan = {
|
|
302
|
+
hosts: 0,
|
|
303
|
+
ordersScanned: 0,
|
|
304
|
+
missingLines: 0,
|
|
305
|
+
reportedOrders: 0,
|
|
306
|
+
truncatedHosts: 0,
|
|
307
|
+
skippedLocked: 0
|
|
308
|
+
};
|
|
309
|
+
if (!snapshot) return scan;
|
|
310
|
+
const hostIds = new Set();
|
|
311
|
+
for (const doc of (_snapshot_docs = snapshot.docs) != null ? _snapshot_docs : []){
|
|
312
|
+
var _doc_ref_parent, _doc_ref, _hostRef_parent;
|
|
313
|
+
const hostRef = (_doc_ref = doc.ref) == null ? void 0 : (_doc_ref_parent = _doc_ref.parent) == null ? void 0 : _doc_ref_parent.parent;
|
|
314
|
+
// `hosts/{hostId}/orders` is the only shape this collection group has
|
|
315
|
+
// today; the check is what keeps that true if a second one appears.
|
|
316
|
+
if (!hostRef || ((_hostRef_parent = hostRef.parent) == null ? void 0 : _hostRef_parent.id) !== 'hosts') continue;
|
|
317
|
+
hostIds.add(String(hostRef.id));
|
|
318
|
+
}
|
|
319
|
+
scan.hosts = hostIds.size;
|
|
320
|
+
for (const hostId of hostIds){
|
|
321
|
+
// LOCKDOWN (AGL-2495). This pass posts a console notification and stamps
|
|
322
|
+
// the order timeline, so it is a write for the host — and on a site taken
|
|
323
|
+
// down for abuse or a legal order, telling its managers about stock is
|
|
324
|
+
// both a mutation and a message the takedown was meant to stop.
|
|
325
|
+
//
|
|
326
|
+
// SKIPPED, NOT DROPPED: the reconciliation is a DETECTOR with no cursor.
|
|
327
|
+
// It re-derives everything from the last N orders on every beat, so a
|
|
328
|
+
// host skipped now is reconciled in full on the first hourly beat after
|
|
329
|
+
// the lift, and nothing it would have found is lost by waiting.
|
|
330
|
+
if (await gate.isLocked(hostId)) {
|
|
331
|
+
scan.skippedLocked += 1;
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
const result = await reconcileHostStockDecrements(_extends({
|
|
335
|
+
firestore,
|
|
336
|
+
hostRef: firestore.collection('hosts').doc(hostId),
|
|
337
|
+
hostId
|
|
338
|
+
}, (options == null ? void 0 : options.nowMs) == null ? {} : {
|
|
339
|
+
nowMs: options.nowMs
|
|
340
|
+
}));
|
|
341
|
+
scan.ordersScanned += result.ordersScanned;
|
|
342
|
+
scan.missingLines += result.missing.length;
|
|
343
|
+
if (result.ledgerTruncated) scan.truncatedHosts += 1;
|
|
344
|
+
scan.reportedOrders += await reportMissingSaleDecrements(hostId, result.missing);
|
|
345
|
+
}
|
|
346
|
+
return scan;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
//# sourceMappingURL=reconcile-stock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/reconcile-stock.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 PluginJobHostGate } from '@aglyn/aglyn/server'\nimport { firebaseAdmin, notifyHostManagers } from '@aglyn/tenant-data-admin'\nimport * as CommerceModel from '../model'\n\n/**\n * THE MISSING-DECREMENT DETECTOR (AGL-2358).\n *\n * Every order-minting branch of `billing-webhook.ts` commits the order in a\n * transaction and decrements stock AFTER it, and every branch guards on a\n * replay flag that transaction set — `created` for cart and buy-now,\n * `flipped` for the draft/POS-card branch, the invoice-id guard for a\n * subscription cycle. So a process that dies in the window between the two\n * loses the decrement PERMANENTLY: Stripe redelivers, the guard says the\n * order already exists, and the handler returns without ever reaching the\n * stock write. The merchant has a paid order and an untouched shelf, and\n * before this nothing anywhere recorded that they had diverged.\n *\n * `/api/billing/webhook`'s own claim marker makes the same window wider, not\n * narrower: AGL-2157 decided that an event which throws mid-dispatch KEEPS\n * its claim — deliberately, because re-running the dispatch could double-apply\n * a gift-card spend or a coupon redemption — and told staff to \"reconcile by\n * hand\". This is the thing they reconcile with. It is also the only detector\n * for the death AGL-2157 cannot catch at all: a timeout, an OOM or a deploy\n * kill unwinds no stack, so that route's `catch` never runs and no staff\n * notice is ever raised.\n *\n * WHY A DETECTOR AND NOT THE FIX. The two real fixes were both weighed and\n * both rejected for this side of the Sept 1 freeze:\n *\n * - Folding the decrement into the order transaction would restructure the\n * checkout path's largest and most contended transaction, days before\n * launch, on the path that takes money. The cart branch alone would have\n * to pull N product reads into a transaction that currently reads two\n * documents, and every contended retry would then re-run order numbering.\n * - A second replay flag with a re-entry path is worse, not better. The\n * `created` guard is not protecting the decrement — it is protecting\n * licence-key assignment, coupon redemption counters, gift-card balances,\n * receipt email and contact ingestion, all behind ONE flag. Any re-entry\n * past it needs every one of those to become individually idempotent\n * first, which is precisely the \"per-EFFECT idempotency sweep\" AGL-2157\n * named as the real fix and deferred for the same reason.\n *\n * And the flag is not even the only lock. Deleting it does not make a\n * redelivery decrement: `cartRef.delete()` runs between the guard and the\n * inventory loop, so the second delivery iterates an empty cart. A\n * resumable path would additionally have to stop reading the cart and\n * start reading the order's own `lineItems`. That is measured, not\n * assumed — `stock-decrement-reconciliation.spec.ts` pins it.\n *\n * This is read-only against orders, products and the ledger. The only thing\n * it writes is its own report marker, in a collection nothing else touches.\n */\n\n/**\n * The statuses an order can only be in if its stock decrement was ATTEMPTED.\n *\n * `pending` is excluded because it is the pre-payment state of a draft link\n * and a POS card sale — neither has decremented yet, and both are supposed to\n * look exactly like this. `cancelled` and `refunded` are excluded because\n * `cancel-order.ts` and `refund.ts` write `reason: 'cancellation'` /\n * `'refund'` rows and may have released the units already; a sale row missing\n * from an order that has been through a release is a question this detector\n * cannot answer from the ledger alone, and guessing would be worse than\n * staying quiet.\n */\nconst DECREMENTED_STATUSES: ReadonlySet<string> = new Set([\n 'paid',\n 'partially_fulfilled',\n 'fulfilled',\n 'delivered',\n])\n\n/**\n * How long an order gets to finish its own decrement before it counts as\n * diverged. The decrement runs milliseconds to seconds after the order\n * commits, so ten minutes is far past any healthy webhook — and a beat that\n * flagged an order the webhook was still working on would report a divergence\n * that fixed itself, which is the fastest way to make a signal ignored.\n */\nconst DEFAULT_GRACE_MS = 10 * 60 * 1000\n\nexport interface MissingSaleDecrement {\n hostId: string\n orderId: string\n /** The merchant-facing sequential number, when the doc carries one. */\n orderNumber: number | null\n orderStatus: string\n orderCreatedAtMs: number\n productId: string\n /** Live product name — the order's snapshot name is used as a fallback. */\n productName: string\n variantId: string\n /** Units the order says were sold, and the shelf therefore still holds. */\n quantity: number\n}\n\nexport interface StockReconciliationResult {\n ordersScanned: number\n /** Orders that reached the ledger comparison (post status/grace filter). */\n ordersChecked: number\n missing: MissingSaleDecrement[]\n /**\n * The ledger read hit its limit before reaching back as far as the oldest\n * order considered, so orders older than the ledger window were DROPPED\n * rather than judged against rows that were never read.\n *\n * Recorded and reported rather than swallowed, for the AGL-2321 reason:\n * `siteSizeTruncated` exists because a number that is silently a lower\n * bound is worse than one that says so. A truncated pass under-reports; it\n * must never be read as \"all matched\".\n */\n ledgerTruncated: boolean\n}\n\n/**\n * Compares one host's recently-decremented orders against the\n * `reason: 'sale'` rows joined to them, and reports the lines whose decrement\n * is missing.\n *\n * Reads are bounded and cheap in the healthy case: two collection queries per\n * host, and a product read ONLY for a line that already looks divergent.\n *\n * Two equality-free queries so the automatic single-field indexes answer them\n * (the `cancel-order.ts` rule): `orderBy(createdAtMs desc).limit()` and\n * `orderBy(atMs desc).limit()`, with the status test applied in memory over\n * the handful of documents that come back.\n */\nexport async function reconcileHostStockDecrements(options: {\n firestore: any\n hostRef: any\n hostId: string\n nowMs?: number\n graceMs?: number\n orderLimit?: number\n ledgerLimit?: number\n}): Promise<StockReconciliationResult> {\n const {\n hostRef,\n hostId,\n nowMs = Date.now(),\n graceMs = DEFAULT_GRACE_MS,\n orderLimit = 100,\n ledgerLimit = 500,\n } = options\n const empty: StockReconciliationResult = {\n ordersScanned: 0,\n ordersChecked: 0,\n missing: [],\n ledgerTruncated: false,\n }\n const orderSnapshot = await hostRef\n .collection('orders')\n .orderBy('createdAtMs', 'desc')\n .limit(orderLimit)\n .get()\n .catch(() => null)\n if (!orderSnapshot) return empty\n const orderDocs: any[] = orderSnapshot.docs ?? []\n const settledBefore = nowMs - graceMs\n let candidates = orderDocs\n .map((doc) => ({\n id: String(doc.id),\n status: String(doc.get('status') ?? ''),\n createdAtMs: Number(doc.get('createdAtMs') ?? 0),\n number: doc.get('number') == null ? null : Number(doc.get('number')),\n lineItems: (doc.get('lineItems') ?? []) as CommerceModel.OrderLineItem[],\n }))\n .filter(\n (order) =>\n DECREMENTED_STATUSES.has(order.status) &&\n order.createdAtMs > 0 &&\n order.createdAtMs <= settledBefore,\n )\n if (!candidates.length) {\n return { ...empty, ordersScanned: orderDocs.length }\n }\n const ledgerSnapshot = await hostRef\n .collection('inventoryAdjustments')\n .orderBy('atMs', 'desc')\n .limit(ledgerLimit)\n .get()\n .catch(() => null)\n if (!ledgerSnapshot) {\n return { ...empty, ordersScanned: orderDocs.length }\n }\n const ledgerDocs: any[] = ledgerSnapshot.docs ?? []\n // `orderId|productId` -> the variant ids that order's sale rows name.\n const soldVariants = new Map<string, Set<string>>()\n let oldestLedgerMs = Number.POSITIVE_INFINITY\n for (const row of ledgerDocs) {\n const atMs = Number(row.get('atMs') ?? 0)\n if (atMs > 0) oldestLedgerMs = Math.min(oldestLedgerMs, atMs)\n if (row.get('reason') !== 'sale') continue\n const orderId = String(row.get('orderId') ?? '')\n const productId = String(row.get('productId') ?? '')\n if (!orderId || !productId) continue\n const key = `${orderId}|${productId}`\n const seen = soldVariants.get(key) ?? new Set<string>()\n seen.add(String(row.get('variantId') ?? ''))\n soldVariants.set(key, seen)\n }\n // A ledger read that hit its limit says nothing about anything older than\n // its oldest row. Those orders are dropped, not judged.\n const ledgerTruncated =\n ledgerDocs.length >= ledgerLimit &&\n Number.isFinite(oldestLedgerMs) &&\n candidates.some((order) => order.createdAtMs < oldestLedgerMs)\n if (ledgerTruncated) {\n candidates = candidates.filter(\n (order) => order.createdAtMs >= oldestLedgerMs,\n )\n }\n const missing: MissingSaleDecrement[] = []\n const productCache = new Map<string, CommerceModel.HostProduct | null>()\n for (const order of candidates) {\n for (const line of order.lineItems ?? []) {\n const productId = String(line?.productId ?? '')\n const quantity = Number(line?.quantity ?? 0)\n if (!productId || quantity <= 0) continue\n // A digital or service line never decrements — `decrementVariantStock`\n // refuses an untracked variant — so its missing sale row is the correct\n // outcome and must not cost a product read on every pass. The line's\n // own purchase-time snapshot answers this; anything else falls through\n // to the live tracked-ness test below.\n if (line?.productType === 'digital' || line?.productType === 'service') {\n continue\n }\n const sold = soldVariants.get(`${order.id}|${productId}`)\n const declaredVariantId = line?.variantId ? String(line.variantId) : ''\n if (declaredVariantId && sold?.has(declaredVariantId)) continue\n if (!declaredVariantId && sold?.size) continue\n // Only now is a product read worth taking: this line already looks\n // divergent, and the live product is what says whether it could ever\n // have decremented at all.\n if (!productCache.has(productId)) {\n const productDoc = await hostRef\n .collection('products')\n .doc(productId)\n .get()\n .catch(() => null)\n productCache.set(\n productId,\n productDoc?.exists\n ? CommerceModel.liftLegacyProduct(productDoc.data() as any)\n : null,\n )\n }\n const product = productCache.get(productId)\n // A product deleted since the sale is the `unresolvedLines` case the\n // webhook already reports on the order itself (AGL-2149). Nothing here\n // can tell whether its variant was tracked, so it is left alone.\n if (!product) continue\n const variantId =\n declaredVariantId || String(product.variants[0]?.id ?? '')\n if (!variantId) continue\n // Re-asked for a line that named no variant: the ledger row records the\n // RESOLVED id, so the default variant has to be resolved before the\n // absence means anything.\n if (!declaredVariantId && sold?.has(variantId)) continue\n const tracked = product.variants.some(\n (variant) => variant.id === variantId && variant.inventory != null,\n )\n // An untracked variant is a shelf nobody is counting. Its sale row is\n // absent by design, and this is also the one place the detector can be\n // wrong in the merchant's favour: tracking turned ON after the sale\n // makes an order that correctly never decremented look divergent. The\n // window is bounded by `orderLimit` and the report says what it is.\n if (!tracked) continue\n missing.push({\n hostId,\n orderId: order.id,\n orderNumber: order.number,\n orderStatus: order.status,\n orderCreatedAtMs: order.createdAtMs,\n productId,\n productName: String(product.name ?? line?.name ?? 'Product'),\n variantId,\n quantity,\n })\n }\n }\n return {\n ordersScanned: orderDocs.length,\n ordersChecked: candidates.length,\n missing,\n ledgerTruncated,\n }\n}\n\n/**\n * Reports a host's divergences to its managers, once per order.\n *\n * The marker in `hosts/{hostId}/inventoryReconciliation/{orderId}` is the only\n * write this whole pass makes, and it lives in a collection nothing else on\n * the platform reads or writes — the detector must not be able to affect the\n * money path it is watching. Without it a beat would re-nag every hour about\n * an order the merchant has already corrected, and an alert that repeats\n * forever is one that gets muted.\n */\nexport async function reportMissingSaleDecrements(\n hostId: string,\n missing: readonly MissingSaleDecrement[],\n): Promise<number> {\n if (!missing.length) return 0\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const byOrder = new Map<string, MissingSaleDecrement[]>()\n for (const row of missing) {\n byOrder.set(row.orderId, [...(byOrder.get(row.orderId) ?? []), row])\n }\n let reported = 0\n for (const [orderId, lines] of byOrder) {\n const markerRef = hostRef.collection('inventoryReconciliation').doc(orderId)\n const marker = await markerRef.get().catch(() => null)\n // A read that FAILED is not a marker that is absent. Skipping here costs\n // one delayed report; treating it as absent would re-notify every pass\n // for as long as the read kept failing.\n if (!marker || marker.exists) continue\n const units = lines.reduce((sum, line) => sum + line.quantity, 0)\n const label =\n lines[0].orderNumber == null ? orderId : `#${lines[0].orderNumber}`\n await markerRef\n .set({\n orderId,\n reportedAtMs: Date.now(),\n lines: lines.map((line) => ({\n productId: line.productId,\n variantId: line.variantId,\n quantity: line.quantity,\n })),\n })\n .catch(() => undefined)\n reported += 1\n console.error('commerce stock decrement missing', {\n hostId,\n orderId,\n lines: lines.map((line) => ({\n productId: line.productId,\n variantId: line.variantId,\n quantity: line.quantity,\n })),\n })\n void notifyHostManagers(hostId, {\n type: 'content.lowStock',\n title: `Stock never came off the shelf — order ${label}`,\n body:\n `Order ${label} is ${lines[0].orderStatus} but ${units} unit` +\n `${units === 1 ? '' : 's'} of ` +\n `${lines.map((line) => line.productName).join(', ')} were never ` +\n 'taken off the count, so the storefront is still offering stock ' +\n 'that has been sold. Adjust the count by hand.',\n link: `/${hostId}/products`,\n })\n }\n return reported\n}\n\nexport interface StockReconciliationScan {\n hosts: number\n ordersScanned: number\n missingLines: number\n reportedOrders: number\n truncatedHosts: number\n /** Hosts left unreconciled because they are locked (AGL-2495). */\n skippedLocked: number\n}\n\n/**\n * The platform beat: the most recent orders across every host, grouped by\n * host and reconciled one host at a time.\n *\n * Ordered by `createdAtMs` and NOT filtered by status, because an equality\n * filter plus an ordering is a composite index and the ordering alone is a\n * field override — the same trade `cancel-order.ts` takes. Every order writer\n * in the plugin stamps `createdAtMs` (cart, buy-now, draft, POS cash, POS\n * card, subscription cycle), so nothing is invisible to the ordering.\n */\nexport async function scanStockDecrements(\n /** The lockdown gate, injected by the caller (AGL-2495). Not optional. */\n gate: PluginJobHostGate,\n options?: {\n orderLimit?: number\n nowMs?: number\n },\n): Promise<StockReconciliationScan> {\n const firestore = firebaseAdmin.app().firestore()\n const snapshot = await firestore\n .collectionGroup('orders')\n .orderBy('createdAtMs', 'desc')\n .limit(options?.orderLimit ?? 300)\n .get()\n .catch((error: unknown) => {\n console.error('commerce stock reconciliation scan failed', error)\n return null\n })\n const scan: StockReconciliationScan = {\n hosts: 0,\n ordersScanned: 0,\n missingLines: 0,\n reportedOrders: 0,\n truncatedHosts: 0,\n skippedLocked: 0,\n }\n if (!snapshot) return scan\n const hostIds = new Set<string>()\n for (const doc of (snapshot.docs ?? []) as any[]) {\n const hostRef = doc.ref?.parent?.parent\n // `hosts/{hostId}/orders` is the only shape this collection group has\n // today; the check is what keeps that true if a second one appears.\n if (!hostRef || hostRef.parent?.id !== 'hosts') continue\n hostIds.add(String(hostRef.id))\n }\n scan.hosts = hostIds.size\n for (const hostId of hostIds) {\n // LOCKDOWN (AGL-2495). This pass posts a console notification and stamps\n // the order timeline, so it is a write for the host — and on a site taken\n // down for abuse or a legal order, telling its managers about stock is\n // both a mutation and a message the takedown was meant to stop.\n //\n // SKIPPED, NOT DROPPED: the reconciliation is a DETECTOR with no cursor.\n // It re-derives everything from the last N orders on every beat, so a\n // host skipped now is reconciled in full on the first hourly beat after\n // the lift, and nothing it would have found is lost by waiting.\n if (await gate.isLocked(hostId)) {\n scan.skippedLocked += 1\n continue\n }\n const result = await reconcileHostStockDecrements({\n firestore,\n hostRef: firestore.collection('hosts').doc(hostId),\n hostId,\n ...(options?.nowMs == null ? {} : { nowMs: options.nowMs }),\n })\n scan.ordersScanned += result.ordersScanned\n scan.missingLines += result.missing.length\n if (result.ledgerTruncated) scan.truncatedHosts += 1\n scan.reportedOrders += await reportMissingSaleDecrements(\n hostId,\n result.missing,\n )\n }\n return scan\n}\n"],"names":["firebaseAdmin","notifyHostManagers","CommerceModel","DECREMENTED_STATUSES","Set","DEFAULT_GRACE_MS","reconcileHostStockDecrements","options","orderSnapshot","ledgerSnapshot","hostRef","hostId","nowMs","Date","now","graceMs","orderLimit","ledgerLimit","empty","ordersScanned","ordersChecked","missing","ledgerTruncated","collection","orderBy","limit","get","catch","orderDocs","docs","settledBefore","candidates","map","doc","id","String","status","createdAtMs","Number","number","lineItems","filter","order","has","length","ledgerDocs","soldVariants","Map","oldestLedgerMs","POSITIVE_INFINITY","row","atMs","Math","min","orderId","productId","key","seen","add","set","isFinite","some","productCache","line","product","quantity","productType","sold","declaredVariantId","variantId","size","productDoc","exists","liftLegacyProduct","data","variants","tracked","variant","inventory","push","orderNumber","orderStatus","orderCreatedAtMs","productName","name","reportMissingSaleDecrements","firestore","app","byOrder","reported","lines","markerRef","marker","units","reduce","sum","label","reportedAtMs","undefined","console","error","type","title","body","join","link","scanStockDecrements","gate","snapshot","collectionGroup","scan","hosts","missingLines","reportedOrders","truncatedHosts","skippedLocked","hostIds","ref","parent","isLocked","result"],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
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 PluginApiHandler } from '@aglyn/aglyn/server';
|
|
18
|
+
/**
|
|
19
|
+
* Order refunds (AGL-287): full or partial via Stripe, site-admin only
|
|
20
|
+
* (it moves money). Destination charges reverse the transfer and the
|
|
21
|
+
* platform fee proportionally. Full refunds transition the order to
|
|
22
|
+
* `refunded`; partial refunds accumulate `refundedCents` and stay in
|
|
23
|
+
* the current status.
|
|
24
|
+
*
|
|
25
|
+
* Two SEPARATE controls guard the money (AGL-1696), and conflating them is
|
|
26
|
+
* how the original went wrong:
|
|
27
|
+
*
|
|
28
|
+
* - The idempotency key stops a DUPLICATE refund — one attempt sent twice
|
|
29
|
+
* because the response was lost, the admin double-clicked, or a client
|
|
30
|
+
* retried. It is minted per attempt by the console and deliberately not
|
|
31
|
+
* derived from the order or the amount: two $10 refunds on a $50 order are
|
|
32
|
+
* two real refunds, exactly as a cashier ringing the same coffee twice is a
|
|
33
|
+
* real second sale.
|
|
34
|
+
* - The cap stops an OVER-refund — several partials summing past what was
|
|
35
|
+
* captured, including two admins refunding at once, where the two attempts
|
|
36
|
+
* are genuinely distinct and no key can help. That needs the counter read
|
|
37
|
+
* and written inside one transaction.
|
|
38
|
+
*
|
|
39
|
+
* The original had a cap that looked like both and was neither: it read
|
|
40
|
+
* `refundedCents` and wrote it back only AFTER the Stripe call, outside any
|
|
41
|
+
* transaction. A guard that reads state the guarded operation writes too late
|
|
42
|
+
* is not a guard — measured, two concurrent refunds each sent a full $50 to
|
|
43
|
+
* Stripe with no idempotency header on either.
|
|
44
|
+
*/
|
|
45
|
+
export declare const refundHandler: PluginApiHandler;
|