@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,449 @@
|
|
|
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
|
+
/**
|
|
20
|
+
* Stock left off the shelf by a reversed order (AGL-1797).
|
|
21
|
+
*
|
|
22
|
+
* The checkout webhook decrements variant inventory on a sale and NOTHING put
|
|
23
|
+
* it back — not `refund.ts`, not the lost-dispute handler (AGL-1787) — so a
|
|
24
|
+
* fully reversed order left the merchant's count permanently one lower than
|
|
25
|
+
* their shelf, compounding with every return.
|
|
26
|
+
*
|
|
27
|
+
* THIS FLAGS. IT DOES NOT RELEASE, and that is the decision.
|
|
28
|
+
*
|
|
29
|
+
* "Increment on reversal" is the obvious fix and it is wrong in two of the
|
|
30
|
+
* three cases it would fire on:
|
|
31
|
+
*
|
|
32
|
+
* - a **returned** item does come back, but only once it is RECEIVED, and no
|
|
33
|
+
* fulfilment event records receipt — `OrderFulfillment` has no returned
|
|
34
|
+
* state, so the moment an automatic increment would need does not exist;
|
|
35
|
+
* - a **refund with no return** (goodwill, damaged, lost in post) leaves the
|
|
36
|
+
* goods gone. Incrementing there invents inventory the merchant does not
|
|
37
|
+
* have, and that is strictly worse than the bug it replaces: the bug
|
|
38
|
+
* under-counts and refuses a sale that could have been made, while the
|
|
39
|
+
* "fix" over-counts and TAKES a sale that cannot be filled;
|
|
40
|
+
* - a **chargeback** is the clearest do-not-restock case there is. The shopper
|
|
41
|
+
* kept the item and took the money, and `product_not_received` — the most
|
|
42
|
+
* common reason code — asserts the goods shipped.
|
|
43
|
+
*
|
|
44
|
+
* The quantities are not always knowable on a partial. A refund requested as an
|
|
45
|
+
* AMOUNT selects no line — "$17 of a $62 order" names nothing — and that is
|
|
46
|
+
* recorded rather than papered over: `quantity` is at most the units the line
|
|
47
|
+
* sold, capped to what the count actually gave up, and `fullyReversed` says
|
|
48
|
+
* whether that bound is a tight one.
|
|
49
|
+
*
|
|
50
|
+
* A refund that NAMES ITS LINES is scoped to them (AGL-2325). When this was
|
|
51
|
+
* written no reversal recorded which lines it covered; AGL-2454 changed that,
|
|
52
|
+
* and `refund.ts` commits `refundedLineItemIds` with the money, before this
|
|
53
|
+
* flag re-reads the order. A one-line refund on a three-line order that still
|
|
54
|
+
* asked about all three was the same over-statement this module already fixed
|
|
55
|
+
* on the units axis, arriving by a door that opened afterwards. See
|
|
56
|
+
* `reversalLineScope` for the three cases that stay whole-order.
|
|
57
|
+
*
|
|
58
|
+
* So the merchant is asked. They are the only party who knows whether the
|
|
59
|
+
* goods came back, and by the time they read this they know it.
|
|
60
|
+
*
|
|
61
|
+
* WHAT IS DELIBERATELY NOT BUILT HERE. No stock writer and no
|
|
62
|
+
* `InventoryAdjustment` row: the products hub's "Adjust stock" already writes
|
|
63
|
+
* the variant counts and logs the adjustment with a reason, so a second writer
|
|
64
|
+
* for one number would be a re-implementation, not an extension. And an
|
|
65
|
+
* adjustment row for stock that has NOT moved would corrupt the very ledger
|
|
66
|
+
* that hub renders as history. This writes the question; the existing action
|
|
67
|
+
* answers it.
|
|
68
|
+
*
|
|
69
|
+
* WHERE THE MERCHANT SEES IT. The order timeline, which the console order
|
|
70
|
+
* dialog already renders, so this needs no UI change to be visible at all. The
|
|
71
|
+
* `restockCheck` record beside it is the structured half a badge and a
|
|
72
|
+
* one-click action read; that console work is filed, not smuggled in.
|
|
73
|
+
*
|
|
74
|
+
* NO NOTIFICATION. A refund is initiated BY an admin in the console, so telling
|
|
75
|
+
* them about their own click is noise, and the chargeback door already sends a
|
|
76
|
+
* manager notification for the dispute itself — a second one for the same event
|
|
77
|
+
* would train them to ignore both.
|
|
78
|
+
*
|
|
79
|
+
* ONLY WHAT THE SALE DECREMENTED IS FLAGGED. Tracking lives on the product
|
|
80
|
+
* variant, not the order line, so this reads the products and applies the sale
|
|
81
|
+
* path's own test (`variant.inventory != null`). A store that tracks no stock
|
|
82
|
+
* therefore gets NO flag rather than a prompt it can never act on, which is the
|
|
83
|
+
* difference between a signal and noise. It costs a handful of reads on an
|
|
84
|
+
* event as rare as a reversal.
|
|
85
|
+
*
|
|
86
|
+
* The `inventoryAdjustments` ledger was the tempting shortcut for SELECTING the
|
|
87
|
+
* lines and it is not usable for that: the buy-now/subscription sale path
|
|
88
|
+
* decrements stock without writing a row (`billing-webhook.ts`, the
|
|
89
|
+
* `productId` branch), so the ledger has holes the order's own line items do
|
|
90
|
+
* not. The lines are still chosen from the order.
|
|
91
|
+
*
|
|
92
|
+
* IT IS READ TO BOUND THE NUMBER, THOUGH (AGL-2325), and the holes are exactly
|
|
93
|
+
* why that is safe in this direction. `line.quantity` is the units the line
|
|
94
|
+
* SOLD, and on a backorder product the inventory floor absorbed part or all of
|
|
95
|
+
* the decrement, so the prompt named units the count never gave up. The sale
|
|
96
|
+
* rows' `appliedDelta` says how many it did, and only pairs the ledger
|
|
97
|
+
* DESCRIBES are capped — a line it is silent about keeps the upper bound, so a
|
|
98
|
+
* hole costs an over-stated question rather than stock stranded off the shelf.
|
|
99
|
+
* `saleReleaseCaps` and `capRestockLines` are shared with the cancellation
|
|
100
|
+
* writer, so what this ASKS and what that RETURNS cannot drift apart.
|
|
101
|
+
*
|
|
102
|
+
* FAILURES ARE SWALLOWED, like `recordContactRefund`'s. The money has already
|
|
103
|
+
* moved and the order already records it; a flag that cannot be written must
|
|
104
|
+
* not fail a refund that has left the merchant's account.
|
|
105
|
+
*/ export async function flagOrderRestock(options) {
|
|
106
|
+
try {
|
|
107
|
+
var _snapshot_data, _order_timeline;
|
|
108
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
109
|
+
const hostRef = firestore.collection('hosts').doc(options.hostId);
|
|
110
|
+
const orderRef = hostRef.collection('orders').doc(options.orderId);
|
|
111
|
+
const snapshot = await orderRef.get();
|
|
112
|
+
if (!snapshot.exists) return;
|
|
113
|
+
const order = CommerceModel.liftLegacyOrder((_snapshot_data = snapshot.data()) != null ? _snapshot_data : {});
|
|
114
|
+
// Once per open question — but "the question" is now a set of LINES, not
|
|
115
|
+
// the order (AGL-2325). A second partial that withdraws a line the open
|
|
116
|
+
// prompt already names adds nothing the merchant does not have; one that
|
|
117
|
+
// withdraws a DIFFERENT line is a genuinely new question, and swallowing it
|
|
118
|
+
// left those units off the shelf with nothing recording that they had gone.
|
|
119
|
+
// A check they already ANSWERED is a new question either way, because
|
|
120
|
+
// stock moved since.
|
|
121
|
+
const open = order.restockCheck && !order.restockCheck.resolution ? order.restockCheck : null;
|
|
122
|
+
const covered = open ? openCheckCoverage(open) : null;
|
|
123
|
+
// An open question that names no lines cannot be told apart from one that
|
|
124
|
+
// names these, so it stands. See `openCheckCoverage`.
|
|
125
|
+
if (open && covered === null) return;
|
|
126
|
+
// AN ORDER THAT WAS CANCELLED HAS NO RESTOCK QUESTION LEFT (AGL-2149).
|
|
127
|
+
// Until `cancelled: ['refunded']` this was unreachable — a cancelled order
|
|
128
|
+
// could not refund at all — and reaching it now would be the double-count
|
|
129
|
+
// this flag exists to avoid: `cancel-order.ts` already put the units back
|
|
130
|
+
// and wrote the `reason: 'cancellation'` ledger rows, so a fresh "N units
|
|
131
|
+
// may need restocking" prompt on top of that invites the merchant to
|
|
132
|
+
// restock the same units a SECOND time. (`cancel-order.ts` handles the
|
|
133
|
+
// opposite order of events — refund first, then cancel — by resolving the
|
|
134
|
+
// open check with what it actually did.)
|
|
135
|
+
//
|
|
136
|
+
// Read from the TIMELINE, not from `order.status`: a full refund flips the
|
|
137
|
+
// order to `refunded` in the transaction above this call, so by the time
|
|
138
|
+
// the flag runs the cancellation is no longer the current status — but the
|
|
139
|
+
// event it stamped is still there, and it is the durable record of the
|
|
140
|
+
// release. A partial refund would leave `cancelled` standing and a status
|
|
141
|
+
// test would catch that one case and silently miss the full one.
|
|
142
|
+
//
|
|
143
|
+
// The cases where the cancel released nothing are covered by the same skip:
|
|
144
|
+
// a `pending` cancel and a POS card order with no `sale` ledger row never
|
|
145
|
+
// decremented anything, so nothing is missing from the shelf to ask about.
|
|
146
|
+
if (((_order_timeline = order.timeline) != null ? _order_timeline : []).some((event)=>event.event === 'cancelled')) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// WHICH LINES THIS REVERSAL TOOK BACK, plus the ones an open question
|
|
150
|
+
// already names (AGL-2325). Extending rather than replacing is what keeps
|
|
151
|
+
// ONE prompt on the order: a second partial that withdraws a new line
|
|
152
|
+
// re-asks the whole standing question, so the merchant answers once and
|
|
153
|
+
// the units of the earlier line are not dropped on the way.
|
|
154
|
+
const scope = reversalLineScope(order, options.kind, options.closedTheOrder);
|
|
155
|
+
const extended = scope && covered ? new Set([
|
|
156
|
+
...scope,
|
|
157
|
+
...covered
|
|
158
|
+
]) : scope;
|
|
159
|
+
const { lines: allLines } = await resolveTrackedRestockLines(hostRef, order, // A product this flag cannot read drops out rather than failing the
|
|
160
|
+
// flag: the money has already moved. The cancellation writer (AGL-1808)
|
|
161
|
+
// passes its TRANSACTION's read instead, because a line it silently
|
|
162
|
+
// dropped would leave stock off the shelf it just promised to return.
|
|
163
|
+
(ref)=>ref.get().catch(()=>null));
|
|
164
|
+
const lines = extended == null ? allLines : allLines.filter((line)=>extended.has(line.lineIndex));
|
|
165
|
+
// Nothing was ever decremented for this order, so nothing is missing from
|
|
166
|
+
// the shelf. Writing a prompt here would be the noise that teaches a
|
|
167
|
+
// merchant to ignore the ones that matter.
|
|
168
|
+
if (lines.length === 0) return;
|
|
169
|
+
// THE PROMPT COUNTS WHAT THE SHELF ACTUALLY LOST, NOT WHAT THE SALE SOLD
|
|
170
|
+
// (AGL-2325). `line.quantity` is the units the line sold, and on a
|
|
171
|
+
// BACKORDER product the inventory floor absorbed part or all of the
|
|
172
|
+
// decrement — stock 0, three units sell, the count stays 0 — so the
|
|
173
|
+
// merchant was asked to restock units the count never gave up. The same
|
|
174
|
+
// `appliedDelta` treatment AGL-2149 gave the cancellation cap, from the
|
|
175
|
+
// same ledger rows and through the same shared helpers, so the question
|
|
176
|
+
// this asks and the units a cancellation would return cannot disagree.
|
|
177
|
+
//
|
|
178
|
+
// The under-prompt this could have caused is what the cap rule already
|
|
179
|
+
// rules out: only pairs the ledger DESCRIBES are capped, so a line it says
|
|
180
|
+
// nothing about keeps the old upper bound rather than being zeroed by a
|
|
181
|
+
// ledger with holes in it. A read that FAILS degrades the same way, to no
|
|
182
|
+
// caps at all — an over-stated prompt is a question the merchant can
|
|
183
|
+
// answer, a missing one is stock left off the shelf for good.
|
|
184
|
+
const capped = capRestockLines(lines, await readSaleReleaseCaps(hostRef, options.orderId));
|
|
185
|
+
// Every tracked line was absorbed by the floor, so nothing is missing from
|
|
186
|
+
// the shelf and there is no question to ask.
|
|
187
|
+
if (capped.length === 0) return;
|
|
188
|
+
// Asked of the CAPPED lines, so a line the floor absorbed is not counted
|
|
189
|
+
// as something the open prompt is missing — it would re-flag on every
|
|
190
|
+
// reversal forever.
|
|
191
|
+
const asking = new Set(capped.map((line)=>line.lineIndex));
|
|
192
|
+
if (covered && alreadyCovered(asking, covered)) return;
|
|
193
|
+
const units = capped.reduce((sum, line)=>sum + line.quantity, 0);
|
|
194
|
+
const flaggedAtMs = Date.now();
|
|
195
|
+
const record = {
|
|
196
|
+
kind: options.kind,
|
|
197
|
+
lines: capped,
|
|
198
|
+
units,
|
|
199
|
+
fullyReversed: options.closedTheOrder,
|
|
200
|
+
flaggedAtMs
|
|
201
|
+
};
|
|
202
|
+
await firestore.runTransaction(async (transaction)=>{
|
|
203
|
+
var _fresh_data;
|
|
204
|
+
const fresh = await transaction.get(orderRef);
|
|
205
|
+
if (!fresh.exists) return;
|
|
206
|
+
const current = CommerceModel.liftLegacyOrder((_fresh_data = fresh.data()) != null ? _fresh_data : {});
|
|
207
|
+
// Re-asked inside the transaction that writes it, so two reversals
|
|
208
|
+
// settling at once cannot both flag — the same mechanism AGL-1754 used to
|
|
209
|
+
// make its status flip observable exactly once, and the reason this is
|
|
210
|
+
// not a bare `set(…, { merge: true })`. Re-asked in the same LINE terms
|
|
211
|
+
// as the check above (AGL-2325): a concurrent flag that already covers
|
|
212
|
+
// every line this one would ask about has asked the question, while one
|
|
213
|
+
// covering a different line leaves this one still worth writing.
|
|
214
|
+
const currentOpen = current.restockCheck && !current.restockCheck.resolution ? current.restockCheck : null;
|
|
215
|
+
if (currentOpen) {
|
|
216
|
+
const currentCoverage = openCheckCoverage(currentOpen);
|
|
217
|
+
if (currentCoverage === null || alreadyCovered(asking, currentCoverage)) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// `update()`, and the whole `restockCheck` map at that. A merge recurses,
|
|
222
|
+
// so a re-flag after a merchant answered would inherit the previous
|
|
223
|
+
// `resolution`/`resolvedAtMs` and read as already handled while asking a
|
|
224
|
+
// brand-new question. `update()` replaces a nested map wholesale, which
|
|
225
|
+
// is the semantics this wants, and the transaction has just proven the
|
|
226
|
+
// document exists so it cannot `NOT_FOUND`.
|
|
227
|
+
transaction.update(orderRef, {
|
|
228
|
+
restockCheck: record,
|
|
229
|
+
timeline: CommerceModel.appendOrderEvent(current, 'restock-check', `${units} ${units === 1 ? 'unit' : 'units'} may need restocking` + (options.kind === 'chargeback' ? ' — the shopper kept the goods unless they came back' : '') + (options.closedTheOrder ? '' : ' (partial reversal, at most)'), flaggedAtMs)
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.error('flagOrderRestock failed', error);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* The order lines THIS reversal took back, or `null` for "all of them"
|
|
238
|
+
* (AGL-2325).
|
|
239
|
+
*
|
|
240
|
+
* When AGL-2325 was filed a refund was an AMOUNT and nothing else — "$17 of a
|
|
241
|
+
* $62 order" selected no line — so naming every tracked line and labelling the
|
|
242
|
+
* total an upper bound was the only honest answer available. AGL-2454 ended
|
|
243
|
+
* that: `refund.ts` records `refundedLineItemIds` in the same transaction that
|
|
244
|
+
* moves the money, and it commits before this flag re-reads the order. A
|
|
245
|
+
* one-line refund on a three-line order that still asks about all three is the
|
|
246
|
+
* same over-statement this issue is about, arriving by a door that opened after
|
|
247
|
+
* it was written.
|
|
248
|
+
*
|
|
249
|
+
* `null` — the whole order — for the three cases where a line list is not the
|
|
250
|
+
* scope:
|
|
251
|
+
*
|
|
252
|
+
* - a **chargeback** reverses the entire payment, so an earlier line-scoped
|
|
253
|
+
* partial must not narrow it to the one line that admin picked;
|
|
254
|
+
* - a refund that **closed the order** took everything back, however the
|
|
255
|
+
* individual attempts were requested;
|
|
256
|
+
* - a refund by **amount alone**, which names no line. That is the pre-AGL-2454
|
|
257
|
+
* shape and still the shape of one requested without a line selection, and
|
|
258
|
+
* it degrades to the upper bound this has always carried rather than to
|
|
259
|
+
* silence.
|
|
260
|
+
*/ function reversalLineScope(order, kind, closedTheOrder) {
|
|
261
|
+
var _order_refundedLineItemIds;
|
|
262
|
+
if (kind === 'chargeback' || closedTheOrder) return null;
|
|
263
|
+
const withdrawn = new Set();
|
|
264
|
+
for (const value of (_order_refundedLineItemIds = order.refundedLineItemIds) != null ? _order_refundedLineItemIds : []){
|
|
265
|
+
const index = Math.round(Number(value));
|
|
266
|
+
if (Number.isFinite(index) && index >= 0) withdrawn.add(index);
|
|
267
|
+
}
|
|
268
|
+
return withdrawn.size > 0 ? withdrawn : null;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Which order lines an OPEN restock question already covers (AGL-2325), or
|
|
272
|
+
* `null` when that cannot be told.
|
|
273
|
+
*
|
|
274
|
+
* `null` for a check written before `lineIndex` existed, and it is read as
|
|
275
|
+
* "covers the whole order" by the one caller — suppressing, which is the
|
|
276
|
+
* behaviour those checks were written under and the safe way to be wrong: a
|
|
277
|
+
* duplicate prompt for units already asked about would teach a merchant to
|
|
278
|
+
* ignore the ones that matter.
|
|
279
|
+
*/ function openCheckCoverage(check) {
|
|
280
|
+
var _check_lines;
|
|
281
|
+
const covered = new Set();
|
|
282
|
+
for (const line of (_check_lines = check.lines) != null ? _check_lines : []){
|
|
283
|
+
if (line.lineIndex == null) return null;
|
|
284
|
+
covered.add(line.lineIndex);
|
|
285
|
+
}
|
|
286
|
+
return covered;
|
|
287
|
+
}
|
|
288
|
+
/** Does the open question already cover every line this reversal names? */ function alreadyCovered(scope, covered) {
|
|
289
|
+
for (const index of scope)if (!covered.has(index)) return false;
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* This order's sale-release caps, read outside any transaction (AGL-2325).
|
|
294
|
+
*
|
|
295
|
+
* SWALLOWS, like everything else on this path: the money has already moved,
|
|
296
|
+
* and an empty map means "cap nothing", which leaves the prompt at the upper
|
|
297
|
+
* bound it has always carried. One equality filter, so the automatic
|
|
298
|
+
* single-field index answers it; the `reason` test runs here, over the handful
|
|
299
|
+
* of rows one order has — the same query shape the cancellation writer runs
|
|
300
|
+
* inside its transaction.
|
|
301
|
+
*/ async function readSaleReleaseCaps(hostRef, orderId) {
|
|
302
|
+
try {
|
|
303
|
+
const rows = await hostRef.collection('inventoryAdjustments').where('orderId', '==', orderId).get();
|
|
304
|
+
return saleReleaseCaps(rows.docs.filter((row)=>row.get('reason') === 'sale'));
|
|
305
|
+
} catch (error) {
|
|
306
|
+
console.error('readSaleReleaseCaps failed', error);
|
|
307
|
+
return new Map();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Key for one product+variant pair in a release-cap map.
|
|
312
|
+
*
|
|
313
|
+
* The separator is U+0000 written as an ESCAPE, not as a raw byte (AGL-2355).
|
|
314
|
+
* Emitted literally it made `file(1)` call the source `data`, so grep skipped
|
|
315
|
+
* the whole file as binary and an AGL-2320 sweep missed a call site in it.
|
|
316
|
+
* Same character at runtime. NUL is still the right separator: it cannot occur
|
|
317
|
+
* in a Firestore document id, so no pair can collide.
|
|
318
|
+
*/ export const restockCapKey = (productId, variantId)=>`${productId}\u0000${variantId}`;
|
|
319
|
+
/**
|
|
320
|
+
* How many units of each product+variant a sale ACTUALLY took off the shelf,
|
|
321
|
+
* read from that order's `reason: 'sale'` ledger rows (AGL-2149).
|
|
322
|
+
*
|
|
323
|
+
* The variant inventory writer floors at zero, and that floor silently absorbs
|
|
324
|
+
* the overshoot on a BACKORDER product: stock 0, `canPurchase` admits the sale
|
|
325
|
+
* because the policy says to, three units sell, `Math.max(0, 0 + -3)` leaves
|
|
326
|
+
* the count at 0. The rows carry both numbers for exactly this — `delta` stays
|
|
327
|
+
* the units SOLD, which is the merchant's history, and `appliedDelta` says how
|
|
328
|
+
* many the count could give up.
|
|
329
|
+
*
|
|
330
|
+
* The writer is deliberately NOT named here: `stock-decrement-race.spec.ts`
|
|
331
|
+
* sweeps every server source for that identifier to prove no file outside its
|
|
332
|
+
* allowlist hand-rolls a stock write, and it matches the bare name, so even a
|
|
333
|
+
* comment mentioning it would read as a call site. This module writes no stock
|
|
334
|
+
* at all — it writes the question.
|
|
335
|
+
*
|
|
336
|
+
* Shared by the cancellation writer, which uses it to bound how much stock
|
|
337
|
+
* goes back, and by the restock flag, which uses it to bound what it CLAIMS
|
|
338
|
+
* went missing (AGL-2325). Extracted rather than copied: the two must not
|
|
339
|
+
* drift, because a flag that names more units than the cancellation would
|
|
340
|
+
* return is the double-count both of them exist to avoid.
|
|
341
|
+
*/ export function saleReleaseCaps(saleRows) {
|
|
342
|
+
const caps = new Map();
|
|
343
|
+
for (const row of saleRows){
|
|
344
|
+
var _row_get, _row_get1, _ref, _row_get2, _caps_get;
|
|
345
|
+
const productId = String((_row_get = row.get('productId')) != null ? _row_get : '');
|
|
346
|
+
const variantId = String((_row_get1 = row.get('variantId')) != null ? _row_get1 : '');
|
|
347
|
+
if (!productId || !variantId) continue;
|
|
348
|
+
const moved = Number((_ref = (_row_get2 = row.get('appliedDelta')) != null ? _row_get2 : row.get('delta')) != null ? _ref : 0);
|
|
349
|
+
// Sale rows are negative; the cap is how many units left the shelf.
|
|
350
|
+
caps.set(restockCapKey(productId, variantId), ((_caps_get = caps.get(restockCapKey(productId, variantId))) != null ? _caps_get : 0) + Math.max(0, -moved));
|
|
351
|
+
}
|
|
352
|
+
return caps;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Lines narrowed to what the count actually gave up, dropping any that gave up
|
|
356
|
+
* nothing (AGL-2149, AGL-2325).
|
|
357
|
+
*
|
|
358
|
+
* ONLY PAIRS THE LEDGER DESCRIBES ARE CAPPED, and that asymmetry is the whole
|
|
359
|
+
* safety argument. A pair with no row is left at the units it sold, which is
|
|
360
|
+
* what keeps the pre-AGL-1807 draft links — decremented with no row at all —
|
|
361
|
+
* and the buy-now path from being silently zeroed by a ledger that has holes
|
|
362
|
+
* in it. So an absent ledger degrades to the old upper bound rather than to
|
|
363
|
+
* saying nothing moved.
|
|
364
|
+
*
|
|
365
|
+
* The budget is consumed as it is spent, so two lines of the same
|
|
366
|
+
* product+variant share one rather than each claiming it whole. Takes a COPY,
|
|
367
|
+
* so a caller may reuse the map it passed.
|
|
368
|
+
*/ export function capRestockLines(lines, caps) {
|
|
369
|
+
const budgets = new Map(caps);
|
|
370
|
+
const capped = [];
|
|
371
|
+
for (const line of lines){
|
|
372
|
+
const key = restockCapKey(line.productId, line.variantId);
|
|
373
|
+
const budget = budgets.get(key);
|
|
374
|
+
const quantity = budget == null ? line.quantity : Math.min(line.quantity, budget);
|
|
375
|
+
if (budget != null) budgets.set(key, budget - quantity);
|
|
376
|
+
if (quantity <= 0) continue;
|
|
377
|
+
capped.push(_extends({}, line, {
|
|
378
|
+
quantity
|
|
379
|
+
}));
|
|
380
|
+
}
|
|
381
|
+
return capped;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* The order's lines whose stock the sale actually took off the shelf.
|
|
385
|
+
*
|
|
386
|
+
* The test is the sale path's own — `variant.inventory != null` on the variant
|
|
387
|
+
* that sold — read from the product as it stands NOW, because that is the
|
|
388
|
+
* count a restock would be applied to. A product deleted since the sale, or a
|
|
389
|
+
* variant removed from it, therefore drops out: there is no number left to put
|
|
390
|
+
* anything back into.
|
|
391
|
+
*
|
|
392
|
+
* Products are read once each, so a three-line order of the same product costs
|
|
393
|
+
* one read rather than three.
|
|
394
|
+
*
|
|
395
|
+
* `read` is the caller's, and the choice is not cosmetic (AGL-1808): the flag
|
|
396
|
+
* passes a swallowing `get()` because a product it cannot read must not fail a
|
|
397
|
+
* refund that already moved, while the cancellation writer passes its
|
|
398
|
+
* TRANSACTION's `get` so every product it is about to increment is read under
|
|
399
|
+
* the same transaction that writes it — and so a read failure aborts rather
|
|
400
|
+
* than quietly shortening the list.
|
|
401
|
+
*/ export async function resolveTrackedRestockLines(hostRef, order, read) {
|
|
402
|
+
var _order_lineItems;
|
|
403
|
+
const products = new Map();
|
|
404
|
+
const lines = [];
|
|
405
|
+
const orderLines = (_order_lineItems = order.lineItems) != null ? _order_lineItems : [];
|
|
406
|
+
for(let lineIndex = 0; lineIndex < orderLines.length; lineIndex += 1){
|
|
407
|
+
var _ref, _ref1, _line_variantId, _product_variants;
|
|
408
|
+
var _product_variants_, _product_variants1;
|
|
409
|
+
const line = orderLines[lineIndex];
|
|
410
|
+
const productId = String((_ref = line == null ? void 0 : line.productId) != null ? _ref : '');
|
|
411
|
+
const quantity = Math.round(Number((_ref1 = line == null ? void 0 : line.quantity) != null ? _ref1 : 0));
|
|
412
|
+
if (!productId || !(quantity > 0)) continue;
|
|
413
|
+
// Firestore reserves `__…__` document ids and `.doc()` throws on one
|
|
414
|
+
// SYNCHRONOUSLY, which no `.catch()` on the returned promise would see. A
|
|
415
|
+
// single corrupt line item must not cost the whole flag.
|
|
416
|
+
if (/^__.*__$/.test(productId)) continue;
|
|
417
|
+
if (!products.has(productId)) {
|
|
418
|
+
var _snapshot_data;
|
|
419
|
+
const snapshot = await read(hostRef.collection('products').doc(productId));
|
|
420
|
+
products.set(productId, (snapshot == null ? void 0 : snapshot.exists) ? CommerceModel.liftLegacyProduct((_snapshot_data = snapshot.data()) != null ? _snapshot_data : {}) : null);
|
|
421
|
+
}
|
|
422
|
+
const product = products.get(productId);
|
|
423
|
+
if (!product) continue;
|
|
424
|
+
const variantId = (_line_variantId = line.variantId) != null ? _line_variantId : (_product_variants1 = product.variants) == null ? void 0 : (_product_variants_ = _product_variants1[0]) == null ? void 0 : _product_variants_.id;
|
|
425
|
+
const tracked = ((_product_variants = product.variants) != null ? _product_variants : []).some((variant)=>variant.id === variantId && variant.inventory != null);
|
|
426
|
+
if (!variantId || !tracked) continue;
|
|
427
|
+
lines.push(_extends({
|
|
428
|
+
productId,
|
|
429
|
+
variantId,
|
|
430
|
+
quantity,
|
|
431
|
+
lineIndex
|
|
432
|
+
}, line.name ? {
|
|
433
|
+
name: line.name
|
|
434
|
+
} : {}, line.variantLabel ? {
|
|
435
|
+
variantLabel: line.variantLabel
|
|
436
|
+
} : {}));
|
|
437
|
+
}
|
|
438
|
+
const resolved = new Map();
|
|
439
|
+
for (const line of lines){
|
|
440
|
+
const product = products.get(line.productId);
|
|
441
|
+
if (product) resolved.set(line.productId, product);
|
|
442
|
+
}
|
|
443
|
+
return {
|
|
444
|
+
lines,
|
|
445
|
+
products: resolved
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
//# sourceMappingURL=restock-flag.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/restock-flag.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'\n\n/**\n * Stock left off the shelf by a reversed order (AGL-1797).\n *\n * The checkout webhook decrements variant inventory on a sale and NOTHING put\n * it back — not `refund.ts`, not the lost-dispute handler (AGL-1787) — so a\n * fully reversed order left the merchant's count permanently one lower than\n * their shelf, compounding with every return.\n *\n * THIS FLAGS. IT DOES NOT RELEASE, and that is the decision.\n *\n * \"Increment on reversal\" is the obvious fix and it is wrong in two of the\n * three cases it would fire on:\n *\n * - a **returned** item does come back, but only once it is RECEIVED, and no\n * fulfilment event records receipt — `OrderFulfillment` has no returned\n * state, so the moment an automatic increment would need does not exist;\n * - a **refund with no return** (goodwill, damaged, lost in post) leaves the\n * goods gone. Incrementing there invents inventory the merchant does not\n * have, and that is strictly worse than the bug it replaces: the bug\n * under-counts and refuses a sale that could have been made, while the\n * \"fix\" over-counts and TAKES a sale that cannot be filled;\n * - a **chargeback** is the clearest do-not-restock case there is. The shopper\n * kept the item and took the money, and `product_not_received` — the most\n * common reason code — asserts the goods shipped.\n *\n * The quantities are not always knowable on a partial. A refund requested as an\n * AMOUNT selects no line — \"$17 of a $62 order\" names nothing — and that is\n * recorded rather than papered over: `quantity` is at most the units the line\n * sold, capped to what the count actually gave up, and `fullyReversed` says\n * whether that bound is a tight one.\n *\n * A refund that NAMES ITS LINES is scoped to them (AGL-2325). When this was\n * written no reversal recorded which lines it covered; AGL-2454 changed that,\n * and `refund.ts` commits `refundedLineItemIds` with the money, before this\n * flag re-reads the order. A one-line refund on a three-line order that still\n * asked about all three was the same over-statement this module already fixed\n * on the units axis, arriving by a door that opened afterwards. See\n * `reversalLineScope` for the three cases that stay whole-order.\n *\n * So the merchant is asked. They are the only party who knows whether the\n * goods came back, and by the time they read this they know it.\n *\n * WHAT IS DELIBERATELY NOT BUILT HERE. No stock writer and no\n * `InventoryAdjustment` row: the products hub's \"Adjust stock\" already writes\n * the variant counts and logs the adjustment with a reason, so a second writer\n * for one number would be a re-implementation, not an extension. And an\n * adjustment row for stock that has NOT moved would corrupt the very ledger\n * that hub renders as history. This writes the question; the existing action\n * answers it.\n *\n * WHERE THE MERCHANT SEES IT. The order timeline, which the console order\n * dialog already renders, so this needs no UI change to be visible at all. The\n * `restockCheck` record beside it is the structured half a badge and a\n * one-click action read; that console work is filed, not smuggled in.\n *\n * NO NOTIFICATION. A refund is initiated BY an admin in the console, so telling\n * them about their own click is noise, and the chargeback door already sends a\n * manager notification for the dispute itself — a second one for the same event\n * would train them to ignore both.\n *\n * ONLY WHAT THE SALE DECREMENTED IS FLAGGED. Tracking lives on the product\n * variant, not the order line, so this reads the products and applies the sale\n * path's own test (`variant.inventory != null`). A store that tracks no stock\n * therefore gets NO flag rather than a prompt it can never act on, which is the\n * difference between a signal and noise. It costs a handful of reads on an\n * event as rare as a reversal.\n *\n * The `inventoryAdjustments` ledger was the tempting shortcut for SELECTING the\n * lines and it is not usable for that: the buy-now/subscription sale path\n * decrements stock without writing a row (`billing-webhook.ts`, the\n * `productId` branch), so the ledger has holes the order's own line items do\n * not. The lines are still chosen from the order.\n *\n * IT IS READ TO BOUND THE NUMBER, THOUGH (AGL-2325), and the holes are exactly\n * why that is safe in this direction. `line.quantity` is the units the line\n * SOLD, and on a backorder product the inventory floor absorbed part or all of\n * the decrement, so the prompt named units the count never gave up. The sale\n * rows' `appliedDelta` says how many it did, and only pairs the ledger\n * DESCRIBES are capped — a line it is silent about keeps the upper bound, so a\n * hole costs an over-stated question rather than stock stranded off the shelf.\n * `saleReleaseCaps` and `capRestockLines` are shared with the cancellation\n * writer, so what this ASKS and what that RETURNS cannot drift apart.\n *\n * FAILURES ARE SWALLOWED, like `recordContactRefund`'s. The money has already\n * moved and the order already records it; a flag that cannot be written must\n * not fail a refund that has left the merchant's account.\n */\nexport async function flagOrderRestock(options: {\n hostId: string\n orderId: string\n /** Which door the money left by. Only the wording differs. */\n kind: 'refund' | 'chargeback'\n /**\n * True only for the write that moved the order INTO `refunded` — the same\n * signal `recordContactRefund` takes, decided by the caller from the status\n * transition it made rather than from the total it read, because\n * `fullyRefunded` is not a once-only signal (AGL-1754).\n */\n closedTheOrder: boolean\n}): Promise<void> {\n try {\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(options.hostId)\n const orderRef = hostRef.collection('orders').doc(options.orderId)\n const snapshot = await orderRef.get()\n if (!snapshot.exists) return\n const order = CommerceModel.liftLegacyOrder(\n (snapshot.data() ?? {}) as never,\n )\n // Once per open question — but \"the question\" is now a set of LINES, not\n // the order (AGL-2325). A second partial that withdraws a line the open\n // prompt already names adds nothing the merchant does not have; one that\n // withdraws a DIFFERENT line is a genuinely new question, and swallowing it\n // left those units off the shelf with nothing recording that they had gone.\n // A check they already ANSWERED is a new question either way, because\n // stock moved since.\n const open =\n order.restockCheck && !order.restockCheck.resolution\n ? order.restockCheck\n : null\n const covered = open ? openCheckCoverage(open) : null\n // An open question that names no lines cannot be told apart from one that\n // names these, so it stands. See `openCheckCoverage`.\n if (open && covered === null) return\n // AN ORDER THAT WAS CANCELLED HAS NO RESTOCK QUESTION LEFT (AGL-2149).\n // Until `cancelled: ['refunded']` this was unreachable — a cancelled order\n // could not refund at all — and reaching it now would be the double-count\n // this flag exists to avoid: `cancel-order.ts` already put the units back\n // and wrote the `reason: 'cancellation'` ledger rows, so a fresh \"N units\n // may need restocking\" prompt on top of that invites the merchant to\n // restock the same units a SECOND time. (`cancel-order.ts` handles the\n // opposite order of events — refund first, then cancel — by resolving the\n // open check with what it actually did.)\n //\n // Read from the TIMELINE, not from `order.status`: a full refund flips the\n // order to `refunded` in the transaction above this call, so by the time\n // the flag runs the cancellation is no longer the current status — but the\n // event it stamped is still there, and it is the durable record of the\n // release. A partial refund would leave `cancelled` standing and a status\n // test would catch that one case and silently miss the full one.\n //\n // The cases where the cancel released nothing are covered by the same skip:\n // a `pending` cancel and a POS card order with no `sale` ledger row never\n // decremented anything, so nothing is missing from the shelf to ask about.\n if ((order.timeline ?? []).some((event) => event.event === 'cancelled')) {\n return\n }\n\n // WHICH LINES THIS REVERSAL TOOK BACK, plus the ones an open question\n // already names (AGL-2325). Extending rather than replacing is what keeps\n // ONE prompt on the order: a second partial that withdraws a new line\n // re-asks the whole standing question, so the merchant answers once and\n // the units of the earlier line are not dropped on the way.\n const scope = reversalLineScope(order, options.kind, options.closedTheOrder)\n const extended = scope && covered ? new Set([...scope, ...covered]) : scope\n const { lines: allLines } = await resolveTrackedRestockLines(\n hostRef,\n order,\n // A product this flag cannot read drops out rather than failing the\n // flag: the money has already moved. The cancellation writer (AGL-1808)\n // passes its TRANSACTION's read instead, because a line it silently\n // dropped would leave stock off the shelf it just promised to return.\n (ref) => ref.get().catch(() => null),\n )\n const lines =\n extended == null\n ? allLines\n : allLines.filter((line) => extended.has(line.lineIndex))\n // Nothing was ever decremented for this order, so nothing is missing from\n // the shelf. Writing a prompt here would be the noise that teaches a\n // merchant to ignore the ones that matter.\n if (lines.length === 0) return\n\n // THE PROMPT COUNTS WHAT THE SHELF ACTUALLY LOST, NOT WHAT THE SALE SOLD\n // (AGL-2325). `line.quantity` is the units the line sold, and on a\n // BACKORDER product the inventory floor absorbed part or all of the\n // decrement — stock 0, three units sell, the count stays 0 — so the\n // merchant was asked to restock units the count never gave up. The same\n // `appliedDelta` treatment AGL-2149 gave the cancellation cap, from the\n // same ledger rows and through the same shared helpers, so the question\n // this asks and the units a cancellation would return cannot disagree.\n //\n // The under-prompt this could have caused is what the cap rule already\n // rules out: only pairs the ledger DESCRIBES are capped, so a line it says\n // nothing about keeps the old upper bound rather than being zeroed by a\n // ledger with holes in it. A read that FAILS degrades the same way, to no\n // caps at all — an over-stated prompt is a question the merchant can\n // answer, a missing one is stock left off the shelf for good.\n const capped = capRestockLines(\n lines,\n await readSaleReleaseCaps(hostRef, options.orderId),\n )\n // Every tracked line was absorbed by the floor, so nothing is missing from\n // the shelf and there is no question to ask.\n if (capped.length === 0) return\n\n // Asked of the CAPPED lines, so a line the floor absorbed is not counted\n // as something the open prompt is missing — it would re-flag on every\n // reversal forever.\n const asking = new Set(capped.map((line) => line.lineIndex))\n if (covered && alreadyCovered(asking, covered)) return\n\n const units = capped.reduce((sum, line) => sum + line.quantity, 0)\n const flaggedAtMs = Date.now()\n const record: CommerceModel.OrderRestockCheck = {\n kind: options.kind,\n lines: capped,\n units,\n fullyReversed: options.closedTheOrder,\n flaggedAtMs,\n }\n await firestore.runTransaction(async (transaction) => {\n const fresh = await transaction.get(orderRef)\n if (!fresh.exists) return\n const current = CommerceModel.liftLegacyOrder(\n (fresh.data() ?? {}) as never,\n )\n // Re-asked inside the transaction that writes it, so two reversals\n // settling at once cannot both flag — the same mechanism AGL-1754 used to\n // make its status flip observable exactly once, and the reason this is\n // not a bare `set(…, { merge: true })`. Re-asked in the same LINE terms\n // as the check above (AGL-2325): a concurrent flag that already covers\n // every line this one would ask about has asked the question, while one\n // covering a different line leaves this one still worth writing.\n const currentOpen =\n current.restockCheck && !current.restockCheck.resolution\n ? current.restockCheck\n : null\n if (currentOpen) {\n const currentCoverage = openCheckCoverage(currentOpen)\n if (\n currentCoverage === null ||\n alreadyCovered(asking, currentCoverage)\n ) {\n return\n }\n }\n // `update()`, and the whole `restockCheck` map at that. A merge recurses,\n // so a re-flag after a merchant answered would inherit the previous\n // `resolution`/`resolvedAtMs` and read as already handled while asking a\n // brand-new question. `update()` replaces a nested map wholesale, which\n // is the semantics this wants, and the transaction has just proven the\n // document exists so it cannot `NOT_FOUND`.\n transaction.update(orderRef, {\n restockCheck: record,\n timeline: CommerceModel.appendOrderEvent(\n current,\n 'restock-check',\n `${units} ${units === 1 ? 'unit' : 'units'} may need restocking` +\n (options.kind === 'chargeback'\n ? ' — the shopper kept the goods unless they came back'\n : '') +\n (options.closedTheOrder ? '' : ' (partial reversal, at most)'),\n flaggedAtMs,\n ),\n })\n })\n } catch (error) {\n console.error('flagOrderRestock failed', error)\n }\n}\n\n/**\n * The order lines THIS reversal took back, or `null` for \"all of them\"\n * (AGL-2325).\n *\n * When AGL-2325 was filed a refund was an AMOUNT and nothing else — \"$17 of a\n * $62 order\" selected no line — so naming every tracked line and labelling the\n * total an upper bound was the only honest answer available. AGL-2454 ended\n * that: `refund.ts` records `refundedLineItemIds` in the same transaction that\n * moves the money, and it commits before this flag re-reads the order. A\n * one-line refund on a three-line order that still asks about all three is the\n * same over-statement this issue is about, arriving by a door that opened after\n * it was written.\n *\n * `null` — the whole order — for the three cases where a line list is not the\n * scope:\n *\n * - a **chargeback** reverses the entire payment, so an earlier line-scoped\n * partial must not narrow it to the one line that admin picked;\n * - a refund that **closed the order** took everything back, however the\n * individual attempts were requested;\n * - a refund by **amount alone**, which names no line. That is the pre-AGL-2454\n * shape and still the shape of one requested without a line selection, and\n * it degrades to the upper bound this has always carried rather than to\n * silence.\n */\nfunction reversalLineScope(\n order: CommerceModel.HostOrder,\n kind: 'refund' | 'chargeback',\n closedTheOrder: boolean,\n): Set<number> | null {\n if (kind === 'chargeback' || closedTheOrder) return null\n const withdrawn = new Set<number>()\n for (const value of order.refundedLineItemIds ?? []) {\n const index = Math.round(Number(value))\n if (Number.isFinite(index) && index >= 0) withdrawn.add(index)\n }\n return withdrawn.size > 0 ? withdrawn : null\n}\n\n/**\n * Which order lines an OPEN restock question already covers (AGL-2325), or\n * `null` when that cannot be told.\n *\n * `null` for a check written before `lineIndex` existed, and it is read as\n * \"covers the whole order\" by the one caller — suppressing, which is the\n * behaviour those checks were written under and the safe way to be wrong: a\n * duplicate prompt for units already asked about would teach a merchant to\n * ignore the ones that matter.\n */\nfunction openCheckCoverage(\n check: CommerceModel.OrderRestockCheck,\n): Set<number> | null {\n const covered = new Set<number>()\n for (const line of check.lines ?? []) {\n if (line.lineIndex == null) return null\n covered.add(line.lineIndex)\n }\n return covered\n}\n\n/** Does the open question already cover every line this reversal names? */\nfunction alreadyCovered(\n scope: ReadonlySet<number>,\n covered: ReadonlySet<number>,\n): boolean {\n for (const index of scope) if (!covered.has(index)) return false\n return true\n}\n\n/**\n * This order's sale-release caps, read outside any transaction (AGL-2325).\n *\n * SWALLOWS, like everything else on this path: the money has already moved,\n * and an empty map means \"cap nothing\", which leaves the prompt at the upper\n * bound it has always carried. One equality filter, so the automatic\n * single-field index answers it; the `reason` test runs here, over the handful\n * of rows one order has — the same query shape the cancellation writer runs\n * inside its transaction.\n */\nasync function readSaleReleaseCaps(\n hostRef: FirebaseFirestore.DocumentReference,\n orderId: string,\n): Promise<Map<string, number>> {\n try {\n const rows = await hostRef\n .collection('inventoryAdjustments')\n .where('orderId', '==', orderId)\n .get()\n return saleReleaseCaps(\n rows.docs.filter((row) => row.get('reason') === 'sale'),\n )\n } catch (error) {\n console.error('readSaleReleaseCaps failed', error)\n return new Map()\n }\n}\n\n/**\n * Key for one product+variant pair in a release-cap map.\n *\n * The separator is U+0000 written as an ESCAPE, not as a raw byte (AGL-2355).\n * Emitted literally it made `file(1)` call the source `data`, so grep skipped\n * the whole file as binary and an AGL-2320 sweep missed a call site in it.\n * Same character at runtime. NUL is still the right separator: it cannot occur\n * in a Firestore document id, so no pair can collide.\n */\nexport const restockCapKey = (productId: string, variantId: string): string =>\n `${productId}\\u0000${variantId}`\n\n/**\n * How many units of each product+variant a sale ACTUALLY took off the shelf,\n * read from that order's `reason: 'sale'` ledger rows (AGL-2149).\n *\n * The variant inventory writer floors at zero, and that floor silently absorbs\n * the overshoot on a BACKORDER product: stock 0, `canPurchase` admits the sale\n * because the policy says to, three units sell, `Math.max(0, 0 + -3)` leaves\n * the count at 0. The rows carry both numbers for exactly this — `delta` stays\n * the units SOLD, which is the merchant's history, and `appliedDelta` says how\n * many the count could give up.\n *\n * The writer is deliberately NOT named here: `stock-decrement-race.spec.ts`\n * sweeps every server source for that identifier to prove no file outside its\n * allowlist hand-rolls a stock write, and it matches the bare name, so even a\n * comment mentioning it would read as a call site. This module writes no stock\n * at all — it writes the question.\n *\n * Shared by the cancellation writer, which uses it to bound how much stock\n * goes back, and by the restock flag, which uses it to bound what it CLAIMS\n * went missing (AGL-2325). Extracted rather than copied: the two must not\n * drift, because a flag that names more units than the cancellation would\n * return is the double-count both of them exist to avoid.\n */\nexport function saleReleaseCaps(\n saleRows: readonly { get: (field: string) => unknown }[],\n): Map<string, number> {\n const caps = new Map<string, number>()\n for (const row of saleRows) {\n const productId = String(row.get('productId') ?? '')\n const variantId = String(row.get('variantId') ?? '')\n if (!productId || !variantId) continue\n const moved = Number(row.get('appliedDelta') ?? row.get('delta') ?? 0)\n // Sale rows are negative; the cap is how many units left the shelf.\n caps.set(\n restockCapKey(productId, variantId),\n (caps.get(restockCapKey(productId, variantId)) ?? 0) +\n Math.max(0, -moved),\n )\n }\n return caps\n}\n\n/**\n * Lines narrowed to what the count actually gave up, dropping any that gave up\n * nothing (AGL-2149, AGL-2325).\n *\n * ONLY PAIRS THE LEDGER DESCRIBES ARE CAPPED, and that asymmetry is the whole\n * safety argument. A pair with no row is left at the units it sold, which is\n * what keeps the pre-AGL-1807 draft links — decremented with no row at all —\n * and the buy-now path from being silently zeroed by a ledger that has holes\n * in it. So an absent ledger degrades to the old upper bound rather than to\n * saying nothing moved.\n *\n * The budget is consumed as it is spent, so two lines of the same\n * product+variant share one rather than each claiming it whole. Takes a COPY,\n * so a caller may reuse the map it passed.\n */\nexport function capRestockLines(\n lines: readonly CommerceModel.OrderRestockLine[],\n caps: ReadonlyMap<string, number>,\n): CommerceModel.OrderRestockLine[] {\n const budgets = new Map(caps)\n const capped: CommerceModel.OrderRestockLine[] = []\n for (const line of lines) {\n const key = restockCapKey(line.productId, line.variantId)\n const budget = budgets.get(key)\n const quantity =\n budget == null ? line.quantity : Math.min(line.quantity, budget)\n if (budget != null) budgets.set(key, budget - quantity)\n if (quantity <= 0) continue\n capped.push({ ...line, quantity })\n }\n return capped\n}\n\n/** What an order's tracked lines resolved to, with the products behind them. */\nexport interface TrackedRestockLines {\n lines: CommerceModel.OrderRestockLine[]\n /**\n * The product doc behind each line, by product id — read once each, and\n * handed back so a caller that WRITES stock (AGL-1808) does not read them a\n * second time. Only products that contributed a line appear.\n */\n products: Map<string, CommerceModel.HostProduct>\n}\n\n/**\n * The order's lines whose stock the sale actually took off the shelf.\n *\n * The test is the sale path's own — `variant.inventory != null` on the variant\n * that sold — read from the product as it stands NOW, because that is the\n * count a restock would be applied to. A product deleted since the sale, or a\n * variant removed from it, therefore drops out: there is no number left to put\n * anything back into.\n *\n * Products are read once each, so a three-line order of the same product costs\n * one read rather than three.\n *\n * `read` is the caller's, and the choice is not cosmetic (AGL-1808): the flag\n * passes a swallowing `get()` because a product it cannot read must not fail a\n * refund that already moved, while the cancellation writer passes its\n * TRANSACTION's `get` so every product it is about to increment is read under\n * the same transaction that writes it — and so a read failure aborts rather\n * than quietly shortening the list.\n */\nexport async function resolveTrackedRestockLines(\n hostRef: FirebaseFirestore.DocumentReference,\n order: CommerceModel.HostOrder,\n read: (\n ref: FirebaseFirestore.DocumentReference,\n ) => Promise<{ exists?: boolean; data?: () => unknown } | null>,\n): Promise<TrackedRestockLines> {\n const products = new Map<string, CommerceModel.HostProduct | null>()\n const lines: CommerceModel.OrderRestockLine[] = []\n const orderLines = order.lineItems ?? []\n for (let lineIndex = 0; lineIndex < orderLines.length; lineIndex += 1) {\n const line = orderLines[lineIndex]\n const productId = String(line?.productId ?? '')\n const quantity = Math.round(Number(line?.quantity ?? 0))\n if (!productId || !(quantity > 0)) continue\n // Firestore reserves `__…__` document ids and `.doc()` throws on one\n // SYNCHRONOUSLY, which no `.catch()` on the returned promise would see. A\n // single corrupt line item must not cost the whole flag.\n if (/^__.*__$/.test(productId)) continue\n if (!products.has(productId)) {\n const snapshot = await read(hostRef.collection('products').doc(productId))\n products.set(\n productId,\n snapshot?.exists\n ? CommerceModel.liftLegacyProduct((snapshot.data() ?? {}) as never)\n : null,\n )\n }\n const product = products.get(productId)\n if (!product) continue\n const variantId = line.variantId ?? product.variants?.[0]?.id\n const tracked = (product.variants ?? []).some(\n (variant) => variant.id === variantId && variant.inventory != null,\n )\n if (!variantId || !tracked) continue\n lines.push({\n productId,\n variantId,\n quantity,\n lineIndex,\n ...(line.name ? { name: line.name } : {}),\n ...(line.variantLabel ? { variantLabel: line.variantLabel } : {}),\n })\n }\n const resolved = new Map<string, CommerceModel.HostProduct>()\n for (const line of lines) {\n const product = products.get(line.productId)\n if (product) resolved.set(line.productId, product)\n }\n return { lines, products: resolved }\n}\n"],"names":["CommerceModel","firebaseAdmin","flagOrderRestock","options","snapshot","order","firestore","app","hostRef","collection","doc","hostId","orderRef","orderId","get","exists","liftLegacyOrder","data","open","restockCheck","resolution","covered","openCheckCoverage","timeline","some","event","scope","reversalLineScope","kind","closedTheOrder","extended","Set","lines","allLines","resolveTrackedRestockLines","ref","catch","filter","line","has","lineIndex","length","capped","capRestockLines","readSaleReleaseCaps","asking","map","alreadyCovered","units","reduce","sum","quantity","flaggedAtMs","Date","now","record","fullyReversed","runTransaction","transaction","fresh","current","currentOpen","currentCoverage","update","appendOrderEvent","error","console","withdrawn","value","refundedLineItemIds","index","Math","round","Number","isFinite","add","size","check","rows","where","saleReleaseCaps","docs","row","Map","restockCapKey","productId","variantId","saleRows","caps","String","moved","set","max","budgets","key","budget","min","push","read","products","orderLines","lineItems","product","test","liftLegacyProduct","variants","id","tracked","variant","inventory","name","variantLabel","resolved"],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
export interface PublicProductReview {
|
|
19
|
+
id: string;
|
|
20
|
+
rating: number;
|
|
21
|
+
body: string;
|
|
22
|
+
authorName: string;
|
|
23
|
+
verified: boolean;
|
|
24
|
+
reply: string | null;
|
|
25
|
+
createdAtMs: number;
|
|
26
|
+
}
|
|
27
|
+
export interface ProductReviewAggregate {
|
|
28
|
+
count: number;
|
|
29
|
+
/** Mean rating, one decimal. */
|
|
30
|
+
average: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Approved reviews for a product, newest first, plus their aggregate.
|
|
34
|
+
*
|
|
35
|
+
* Extracted from the handler (AGL-686) so the site-page resolver can nest
|
|
36
|
+
* `aggregateRating` inside the server-rendered `Product` node. Schema.org
|
|
37
|
+
* wants it as a PROPERTY of the product; the reviews block was emitting a
|
|
38
|
+
* free-standing `AggregateRating` node alongside it, which is orphaned and
|
|
39
|
+
* ignored. One reader means the rating in the structured data and the rating
|
|
40
|
+
* on the page cannot disagree.
|
|
41
|
+
*/
|
|
42
|
+
export declare function readProductReviews(hostId: string, productId: string): Promise<{
|
|
43
|
+
reviews: PublicProductReview[];
|
|
44
|
+
aggregate: ProductReviewAggregate;
|
|
45
|
+
}>;
|
|
46
|
+
/**
|
|
47
|
+
* Product reviews (AGL-324). GET returns approved reviews + aggregate;
|
|
48
|
+
* POST submits into the moderation queue, marking `verified` when the
|
|
49
|
+
* email has a paid order containing the product. Pro-plan gated.
|
|
50
|
+
*/
|
|
51
|
+
export declare const reviewsHandler: PluginApiHandler;
|