@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,91 @@
|
|
|
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 { firebaseAdmin } from "@aglyn/tenant-data-admin";
|
|
17
|
+
import { requireActiveMember } from "./membership.js";
|
|
18
|
+
/**
|
|
19
|
+
* Subscriber self-service (AGL-303): opens the Stripe Billing Portal for
|
|
20
|
+
* the signed-in member's storefront subscription (cancel, switch
|
|
21
|
+
* payment method). Member-gated; the portal customer is matched by the
|
|
22
|
+
* member's email through the recorded subscription doc.
|
|
23
|
+
*/ export const subscriptionPortalHandler = async (req, res)=>{
|
|
24
|
+
var _ref;
|
|
25
|
+
var _req_body;
|
|
26
|
+
if (req.method !== 'POST') {
|
|
27
|
+
return res.status(405).json({
|
|
28
|
+
error: 'Method not allowed'
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (!process.env.STRIPE_SECRET_KEY) {
|
|
32
|
+
return res.status(501).json({
|
|
33
|
+
error: 'Billing is not configured.'
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
const hostId = String((_ref = (_req_body = req.body) == null ? void 0 : _req_body.hostId) != null ? _ref : '');
|
|
37
|
+
if (!hostId) return res.status(400).json({
|
|
38
|
+
error: 'Missing hostId'
|
|
39
|
+
});
|
|
40
|
+
try {
|
|
41
|
+
var _auth_member_get, _req_headers_referer;
|
|
42
|
+
var _subscriptions_docs_;
|
|
43
|
+
// Suspension gate (AGL-550): suspended members (AGL-546) cannot
|
|
44
|
+
// open the billing portal — 403'd with the session cookie cleared.
|
|
45
|
+
const auth = await requireActiveMember(req, res, hostId);
|
|
46
|
+
if (!auth) return;
|
|
47
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
48
|
+
const hostRef = firestore.collection('hosts').doc(hostId);
|
|
49
|
+
const email = String((_auth_member_get = auth.member.get('email')) != null ? _auth_member_get : '');
|
|
50
|
+
if (!email) return res.status(401).json({
|
|
51
|
+
error: 'Not signed in'
|
|
52
|
+
});
|
|
53
|
+
const subscriptions = await hostRef.collection('subscriptions').where('customerEmail', '==', email).limit(1).get();
|
|
54
|
+
const customerId = (_subscriptions_docs_ = subscriptions.docs[0]) == null ? void 0 : _subscriptions_docs_.get('stripeCustomerId');
|
|
55
|
+
if (!customerId) {
|
|
56
|
+
return res.status(404).json({
|
|
57
|
+
error: 'No subscription found'
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const referer = String((_req_headers_referer = req.headers.referer) != null ? _req_headers_referer : '');
|
|
61
|
+
const origin = `https://${req.headers.host}`;
|
|
62
|
+
const response = await fetch('https://api.stripe.com/v1/billing_portal/sessions', {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: {
|
|
65
|
+
Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,
|
|
66
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
67
|
+
},
|
|
68
|
+
body: new URLSearchParams({
|
|
69
|
+
customer: String(customerId),
|
|
70
|
+
return_url: referer.startsWith('http') ? referer : origin
|
|
71
|
+
}).toString()
|
|
72
|
+
});
|
|
73
|
+
const session = await response.json();
|
|
74
|
+
if (!response.ok || !session.url) {
|
|
75
|
+
console.error('Portal error', session == null ? void 0 : session.error);
|
|
76
|
+
return res.status(502).json({
|
|
77
|
+
error: 'Portal unavailable'
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return res.status(200).json({
|
|
81
|
+
url: session.url
|
|
82
|
+
});
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error(error);
|
|
85
|
+
return res.status(500).json({
|
|
86
|
+
error: 'Portal unavailable'
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
//# sourceMappingURL=subscription-portal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/subscription-portal.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 { PluginApiHandler } from '@aglyn/aglyn/server'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { requireActiveMember } from './membership'\n\n/**\n * Subscriber self-service (AGL-303): opens the Stripe Billing Portal for\n * the signed-in member's storefront subscription (cancel, switch\n * payment method). Member-gated; the portal customer is matched by the\n * member's email through the recorded subscription doc.\n */\nexport const subscriptionPortalHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n if (!process.env.STRIPE_SECRET_KEY) {\n return res.status(501).json({ error: 'Billing is not configured.' })\n }\n const hostId = String(req.body?.hostId ?? '')\n if (!hostId) return res.status(400).json({ error: 'Missing hostId' })\n\n try {\n // Suspension gate (AGL-550): suspended members (AGL-546) cannot\n // open the billing portal — 403'd with the session cookie cleared.\n const auth = await requireActiveMember(req, res, hostId)\n if (!auth) return\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const email = String(auth.member.get('email') ?? '')\n if (!email) return res.status(401).json({ error: 'Not signed in' })\n const subscriptions = await hostRef\n .collection('subscriptions')\n .where('customerEmail', '==', email)\n .limit(1)\n .get()\n const customerId = subscriptions.docs[0]?.get('stripeCustomerId')\n if (!customerId) {\n return res.status(404).json({ error: 'No subscription found' })\n }\n const referer = String(req.headers.referer ?? '')\n const origin = `https://${req.headers.host}`\n const response = await fetch(\n 'https://api.stripe.com/v1/billing_portal/sessions',\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n customer: String(customerId),\n return_url: referer.startsWith('http') ? referer : origin,\n }).toString(),\n },\n )\n const session = await response.json()\n if (!response.ok || !session.url) {\n console.error('Portal error', session?.error)\n return res.status(502).json({ error: 'Portal unavailable' })\n }\n return res.status(200).json({ url: session.url })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Portal unavailable' })\n }\n}\n"],"names":["firebaseAdmin","requireActiveMember","subscriptionPortalHandler","req","res","method","status","json","error","process","env","STRIPE_SECRET_KEY","hostId","String","body","auth","subscriptions","firestore","app","hostRef","collection","doc","email","member","get","where","limit","customerId","docs","referer","headers","origin","host","response","fetch","Authorization","URLSearchParams","customer","return_url","startsWith","toString","session","ok","url","console"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SAASA,aAAa,QAAQ,2BAA0B;AACxD,SAASC,mBAAmB,QAAQ,kBAAc;AAElD;;;;;CAKC,GACD,OAAO,MAAMC,4BAA8C,OAAOC,KAAKC;;QAO/CD;IANtB,IAAIA,IAAIE,MAAM,KAAK,QAAQ;QACzB,OAAOD,IAAIE,MAAM,CAAC,KAAKC,IAAI,CAAC;YAAEC,OAAO;QAAqB;IAC5D;IACA,IAAI,CAACC,QAAQC,GAAG,CAACC,iBAAiB,EAAE;QAClC,OAAOP,IAAIE,MAAM,CAAC,KAAKC,IAAI,CAAC;YAAEC,OAAO;QAA6B;IACpE;IACA,MAAMI,SAASC,gBAAOV,YAAAA,IAAIW,IAAI,qBAARX,UAAUS,MAAM,mBAAI;IAC1C,IAAI,CAACA,QAAQ,OAAOR,IAAIE,MAAM,CAAC,KAAKC,IAAI,CAAC;QAAEC,OAAO;IAAiB;IAEnE,IAAI;YAOmBO,kBAWEZ;YAJJa;QAbnB,gEAAgE;QAChE,mEAAmE;QACnE,MAAMD,OAAO,MAAMd,oBAAoBE,KAAKC,KAAKQ;QACjD,IAAI,CAACG,MAAM;QACX,MAAME,YAAYjB,cAAckB,GAAG,GAAGD,SAAS;QAC/C,MAAME,UAAUF,UAAUG,UAAU,CAAC,SAASC,GAAG,CAACT;QAClD,MAAMU,QAAQT,QAAOE,mBAAAA,KAAKQ,MAAM,CAACC,GAAG,CAAC,oBAAhBT,mBAA4B;QACjD,IAAI,CAACO,OAAO,OAAOlB,IAAIE,MAAM,CAAC,KAAKC,IAAI,CAAC;YAAEC,OAAO;QAAgB;QACjE,MAAMQ,gBAAgB,MAAMG,QACzBC,UAAU,CAAC,iBACXK,KAAK,CAAC,iBAAiB,MAAMH,OAC7BI,KAAK,CAAC,GACNF,GAAG;QACN,MAAMG,cAAaX,uBAAAA,cAAcY,IAAI,CAAC,EAAE,qBAArBZ,qBAAuBQ,GAAG,CAAC;QAC9C,IAAI,CAACG,YAAY;YACf,OAAOvB,IAAIE,MAAM,CAAC,KAAKC,IAAI,CAAC;gBAAEC,OAAO;YAAwB;QAC/D;QACA,MAAMqB,UAAUhB,QAAOV,uBAAAA,IAAI2B,OAAO,CAACD,OAAO,YAAnB1B,uBAAuB;QAC9C,MAAM4B,SAAS,CAAC,QAAQ,EAAE5B,IAAI2B,OAAO,CAACE,IAAI,EAAE;QAC5C,MAAMC,WAAW,MAAMC,MACrB,qDACA;YACE7B,QAAQ;YACRyB,SAAS;gBACPK,eAAe,CAAC,OAAO,EAAE1B,QAAQC,GAAG,CAACC,iBAAiB,EAAE;gBACxD,gBAAgB;YAClB;YACAG,MAAM,IAAIsB,gBAAgB;gBACxBC,UAAUxB,OAAOc;gBACjBW,YAAYT,QAAQU,UAAU,CAAC,UAAUV,UAAUE;YACrD,GAAGS,QAAQ;QACb;QAEF,MAAMC,UAAU,MAAMR,SAAS1B,IAAI;QACnC,IAAI,CAAC0B,SAASS,EAAE,IAAI,CAACD,QAAQE,GAAG,EAAE;YAChCC,QAAQpC,KAAK,CAAC,gBAAgBiC,2BAAAA,QAASjC,KAAK;YAC5C,OAAOJ,IAAIE,MAAM,CAAC,KAAKC,IAAI,CAAC;gBAAEC,OAAO;YAAqB;QAC5D;QACA,OAAOJ,IAAIE,MAAM,CAAC,KAAKC,IAAI,CAAC;YAAEoC,KAAKF,QAAQE,GAAG;QAAC;IACjD,EAAE,OAAOnC,OAAO;QACdoC,QAAQpC,KAAK,CAACA;QACd,OAAOJ,IAAIE,MAAM,CAAC,KAAKC,IAAI,CAAC;YAAEC,OAAO;QAAqB;IAC5D;AACF,EAAC"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import { type PluginJobHostGate } from '@aglyn/aglyn/server';
|
|
18
|
+
/**
|
|
19
|
+
* The durable outbox for the dropship supplier notification (AGL-2473).
|
|
20
|
+
*
|
|
21
|
+
* ## What was wrong
|
|
22
|
+
*
|
|
23
|
+
* `billing-webhook.ts` routed a paid dropship order inside
|
|
24
|
+
* `void (async () => { … })()`, and the last thing that block did was
|
|
25
|
+
* `await fetch(supplier.webhookUrl, …)` — an endpoint the MERCHANT typed into
|
|
26
|
+
* a console field, on a host Aglyn neither runs nor monitors. Vercel freezes
|
|
27
|
+
* the container the instant the response is written, so a supplier that is
|
|
28
|
+
* slow, rate-limiting, or simply down is a supplier that is never told. The
|
|
29
|
+
* order is paid, the buyer is thanked, the merchant sees "Sent to supplier X"
|
|
30
|
+
* on the timeline, and NOTHING ANYWHERE records that the POST is still owed —
|
|
31
|
+
* so no later pass can find it and no report can list it. It surfaces when the
|
|
32
|
+
* customer asks where their parcel is.
|
|
33
|
+
*
|
|
34
|
+
* ## Why not just `await` it
|
|
35
|
+
*
|
|
36
|
+
* AGL-2161 fixed the other four fire-and-forget sites in that file and
|
|
37
|
+
* deliberately left this one, because awaiting it is worse rather than better:
|
|
38
|
+
* a supplier taking 30s pushes the handler past Stripe's delivery window,
|
|
39
|
+
* Stripe redelivers, and the redelivery re-enters a fan-out whose only
|
|
40
|
+
* idempotency is one `created` flag upstream. A silently dropped notification
|
|
41
|
+
* would have been traded for a DUPLICATED order. `refund.ts` awaits its own
|
|
42
|
+
* sibling and says so in a comment — correctly, because that sibling only
|
|
43
|
+
* touches our own Firestore.
|
|
44
|
+
*
|
|
45
|
+
* So the line this module draws is not "fast vs slow" but **ours vs theirs**.
|
|
46
|
+
* Everything the routing block does to Aglyn's own storage — the callback
|
|
47
|
+
* token, the timeline entry, the supplier email through our own provider — is
|
|
48
|
+
* now AWAITED on the response path, exactly like `refund.ts`. The one call to a
|
|
49
|
+
* stranger's server becomes a row here, and the response no longer waits on it.
|
|
50
|
+
*
|
|
51
|
+
* ## The mechanism
|
|
52
|
+
*
|
|
53
|
+
* A Firestore-backed outbox drained by the plugin-job beat that already runs
|
|
54
|
+
* the two commerce recovery passes (`server.ts`). No new infrastructure: the
|
|
55
|
+
* beat is a Cloud Scheduler function POSTing the tenant runner route, and it
|
|
56
|
+
* has carried bounded scans since AGL-2227.
|
|
57
|
+
*
|
|
58
|
+
* * **Idempotent enqueue.** The id is `{hostId}__{orderId}__{supplierId}` and
|
|
59
|
+
* the write is `create()`, which REFUSES an existing document, so a
|
|
60
|
+
* redelivery cannot reset a retry part-way through its backoff. The AGL-498
|
|
61
|
+
* existence guard upstream already returns before this fan-out, but
|
|
62
|
+
* `reconcile-stock.ts` documents at length why one flag over a dozen effects
|
|
63
|
+
* is not something to lean on.
|
|
64
|
+
* * **Backoff.** 1m, 5m, 15m, 1h, 6h — about seven and a half hours of cover,
|
|
65
|
+
* which is a supplier's overnight deploy but not their week off.
|
|
66
|
+
* * **Dead letter.** After {@link SUPPLIER_DELIVERY_MAX_ATTEMPTS} the row is
|
|
67
|
+
* left as `failed`, the ORDER's own timeline is stamped, and host managers
|
|
68
|
+
* get a notification. The merchant reading the order they were paid for can
|
|
69
|
+
* see that it never reached the supplier — the thing the old code could not
|
|
70
|
+
* tell anyone, because it wrote nothing down.
|
|
71
|
+
*
|
|
72
|
+
* ## Two placement decisions, both deliberate
|
|
73
|
+
*
|
|
74
|
+
* **TOP-LEVEL, not `hosts/{hostId}/supplierDeliveries`.** The host catch-all in
|
|
75
|
+
* the rules file is PERMISSIVE by default — a subcollection nobody names in its
|
|
76
|
+
* three exclusion lists is one every site editor can write from the browser —
|
|
77
|
+
* and the row carries the exact JSON body we sign and POST, so an editor-
|
|
78
|
+
* writable row is an editor-chosen payload delivered under the supplier's own
|
|
79
|
+
* shared secret. A top-level collection matches no rule in
|
|
80
|
+
* `cloud/firebase-firestore.rules` at all and is therefore denied to every
|
|
81
|
+
* client by default, the same footing as `platform/pluginJobs`. It also keeps
|
|
82
|
+
* the drain on an ordinary collection query: a collection-GROUP `where` would
|
|
83
|
+
* need a `COLLECTION_GROUP_ASC` fieldOverride, and AGL-1793 is the record of
|
|
84
|
+
* both commerce crons being silently dead in production for want of exactly
|
|
85
|
+
* that. Neither a rules deploy nor an index deploy is owed for this change.
|
|
86
|
+
*
|
|
87
|
+
* **A delivered row is DELETED, not stamped `delivered`.** The body holds the
|
|
88
|
+
* buyer's email and name, and a top-level document is outside the
|
|
89
|
+
* `hosts/{hostId}` recursive delete an erasure request runs. Retaining the
|
|
90
|
+
* happy path would accumulate third-party PII in a place erasure does not
|
|
91
|
+
* reach, to record something the order's own `routed` timeline entry already
|
|
92
|
+
* says. Dead-lettered rows survive, because those are the ones a human has to
|
|
93
|
+
* act on and the merchant has been told about them.
|
|
94
|
+
*/
|
|
95
|
+
/** Top-level collection. Matches no rule, so no client may read or write it. */
|
|
96
|
+
export declare const SUPPLIER_DELIVERY_COLLECTION = "supplierDeliveries";
|
|
97
|
+
/** Attempts before a delivery is dead-lettered. */
|
|
98
|
+
export declare const SUPPLIER_DELIVERY_MAX_ATTEMPTS = 6;
|
|
99
|
+
/**
|
|
100
|
+
* Delay before the next retry, indexed by attempts ALREADY made.
|
|
101
|
+
*
|
|
102
|
+
* The first entry is 60s and the job beat is also 60s, so a supplier that
|
|
103
|
+
* blipped is retried on the next tick rather than a quarter of an hour later —
|
|
104
|
+
* the difference between a parcel that ships today and one that does not.
|
|
105
|
+
*/
|
|
106
|
+
export declare const SUPPLIER_DELIVERY_BACKOFF_MS: readonly number[];
|
|
107
|
+
/**
|
|
108
|
+
* Per-attempt timeout. Nothing user-facing waits on this — it exists so one
|
|
109
|
+
* hung supplier cannot hold the shared job beat open for the whole scan.
|
|
110
|
+
*/
|
|
111
|
+
export declare const SUPPLIER_DELIVERY_TIMEOUT_MS = 10000;
|
|
112
|
+
/** Rows examined per drain. Bounded like every other scan on the beat. */
|
|
113
|
+
export declare const SUPPLIER_DELIVERY_SCAN_LIMIT = 100;
|
|
114
|
+
export type SupplierDeliveryStatus = 'pending' | 'failed';
|
|
115
|
+
export interface SupplierDeliveryRecord {
|
|
116
|
+
status: SupplierDeliveryStatus;
|
|
117
|
+
hostId: string;
|
|
118
|
+
orderId: string;
|
|
119
|
+
supplierId: string;
|
|
120
|
+
supplierName: string;
|
|
121
|
+
/** The endpoint as configured when the order was routed, for the record. */
|
|
122
|
+
url: string;
|
|
123
|
+
/** The exact JSON the supplier is sent; signed at delivery time. */
|
|
124
|
+
body: string;
|
|
125
|
+
attempts: number;
|
|
126
|
+
nextAttemptAtMs: number;
|
|
127
|
+
createdAtMs: number;
|
|
128
|
+
lastError?: string;
|
|
129
|
+
lastStatus?: number;
|
|
130
|
+
failedAtMs?: number;
|
|
131
|
+
}
|
|
132
|
+
export interface SupplierDeliveryScanResult {
|
|
133
|
+
scanned: number;
|
|
134
|
+
delivered: number;
|
|
135
|
+
retried: number;
|
|
136
|
+
deadLettered: number;
|
|
137
|
+
/** Retired without delivery because the merchant removed the endpoint. */
|
|
138
|
+
cancelled: number;
|
|
139
|
+
/** Left pending, with the retry budget intact, because the site is locked. */
|
|
140
|
+
skippedLocked: number;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The delay owed after `attempts` failures, clamped to the last step.
|
|
144
|
+
*
|
|
145
|
+
* Clamped rather than extrapolated: the tail entry is already six hours, and a
|
|
146
|
+
* dead letter is what comes after it, not a longer and longer silence.
|
|
147
|
+
*/
|
|
148
|
+
export declare function supplierDeliveryBackoffMs(attempts: number): number;
|
|
149
|
+
/**
|
|
150
|
+
* The outbox document id for one host/order/supplier triple.
|
|
151
|
+
*
|
|
152
|
+
* Deterministic, so `create()` is the idempotency. `__` rather than `:` or `/`:
|
|
153
|
+
* a slash would make `.doc()` build a PATH instead of an id (AGL-1771), and the
|
|
154
|
+
* separator has to survive a Stripe session id, which already contains `_`.
|
|
155
|
+
* Firestore reserves ids fully matching `__.*__`; this never is, because all
|
|
156
|
+
* three parts are non-empty and the id neither starts nor ends with `__`.
|
|
157
|
+
*/
|
|
158
|
+
export declare function supplierDeliveryId(hostId: string, orderId: string, supplierId: string): string;
|
|
159
|
+
/**
|
|
160
|
+
* Records that a supplier POST is owed. One Firestore write, awaited.
|
|
161
|
+
*
|
|
162
|
+
* Returns `'queued'`, `'exists'` when a row for this triple is already in
|
|
163
|
+
* flight, or `'skipped'`. NEVER THROWS: the caller is a paid order's fulfilment
|
|
164
|
+
* path, and a queue that cannot be written must not take the receipt down with
|
|
165
|
+
* it. The refusal is logged, which is strictly more than the old code managed
|
|
166
|
+
* on the same failure.
|
|
167
|
+
*/
|
|
168
|
+
export declare function enqueueSupplierDelivery(options: {
|
|
169
|
+
firestore: FirebaseFirestore.Firestore;
|
|
170
|
+
hostId: string;
|
|
171
|
+
orderId: string;
|
|
172
|
+
supplierId: string;
|
|
173
|
+
supplierName: string;
|
|
174
|
+
url: string;
|
|
175
|
+
body: string;
|
|
176
|
+
now?: number;
|
|
177
|
+
}): Promise<'queued' | 'exists' | 'skipped'>;
|
|
178
|
+
/**
|
|
179
|
+
* Drains the supplier outbox. Bounded, and safe to run twice.
|
|
180
|
+
*
|
|
181
|
+
* `nextAttemptAtMs` is filtered IN MEMORY rather than added to the query, which
|
|
182
|
+
* would need a composite index for one field's worth of selectivity. The cost
|
|
183
|
+
* is that a row still inside its backoff occupies one of the
|
|
184
|
+
* {@link SUPPLIER_DELIVERY_SCAN_LIMIT} slots; with at most
|
|
185
|
+
* {@link SUPPLIER_DELIVERY_MAX_ATTEMPTS} attempts per row the queue always
|
|
186
|
+
* drains rather than growing a permanent head.
|
|
187
|
+
*/
|
|
188
|
+
export declare function scanSupplierDeliveries(
|
|
189
|
+
/** The lockdown gate, injected by the caller (AGL-2495). Not optional. */
|
|
190
|
+
gate: PluginJobHostGate, now?: number): Promise<SupplierDeliveryScanResult>;
|
|
@@ -0,0 +1,342 @@
|
|
|
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 { isDocumentId } from "@aglyn/tenant-data-admin/server/document-id";
|
|
19
|
+
import { createHmac } from "crypto";
|
|
20
|
+
/**
|
|
21
|
+
* The durable outbox for the dropship supplier notification (AGL-2473).
|
|
22
|
+
*
|
|
23
|
+
* ## What was wrong
|
|
24
|
+
*
|
|
25
|
+
* `billing-webhook.ts` routed a paid dropship order inside
|
|
26
|
+
* `void (async () => { … })()`, and the last thing that block did was
|
|
27
|
+
* `await fetch(supplier.webhookUrl, …)` — an endpoint the MERCHANT typed into
|
|
28
|
+
* a console field, on a host Aglyn neither runs nor monitors. Vercel freezes
|
|
29
|
+
* the container the instant the response is written, so a supplier that is
|
|
30
|
+
* slow, rate-limiting, or simply down is a supplier that is never told. The
|
|
31
|
+
* order is paid, the buyer is thanked, the merchant sees "Sent to supplier X"
|
|
32
|
+
* on the timeline, and NOTHING ANYWHERE records that the POST is still owed —
|
|
33
|
+
* so no later pass can find it and no report can list it. It surfaces when the
|
|
34
|
+
* customer asks where their parcel is.
|
|
35
|
+
*
|
|
36
|
+
* ## Why not just `await` it
|
|
37
|
+
*
|
|
38
|
+
* AGL-2161 fixed the other four fire-and-forget sites in that file and
|
|
39
|
+
* deliberately left this one, because awaiting it is worse rather than better:
|
|
40
|
+
* a supplier taking 30s pushes the handler past Stripe's delivery window,
|
|
41
|
+
* Stripe redelivers, and the redelivery re-enters a fan-out whose only
|
|
42
|
+
* idempotency is one `created` flag upstream. A silently dropped notification
|
|
43
|
+
* would have been traded for a DUPLICATED order. `refund.ts` awaits its own
|
|
44
|
+
* sibling and says so in a comment — correctly, because that sibling only
|
|
45
|
+
* touches our own Firestore.
|
|
46
|
+
*
|
|
47
|
+
* So the line this module draws is not "fast vs slow" but **ours vs theirs**.
|
|
48
|
+
* Everything the routing block does to Aglyn's own storage — the callback
|
|
49
|
+
* token, the timeline entry, the supplier email through our own provider — is
|
|
50
|
+
* now AWAITED on the response path, exactly like `refund.ts`. The one call to a
|
|
51
|
+
* stranger's server becomes a row here, and the response no longer waits on it.
|
|
52
|
+
*
|
|
53
|
+
* ## The mechanism
|
|
54
|
+
*
|
|
55
|
+
* A Firestore-backed outbox drained by the plugin-job beat that already runs
|
|
56
|
+
* the two commerce recovery passes (`server.ts`). No new infrastructure: the
|
|
57
|
+
* beat is a Cloud Scheduler function POSTing the tenant runner route, and it
|
|
58
|
+
* has carried bounded scans since AGL-2227.
|
|
59
|
+
*
|
|
60
|
+
* * **Idempotent enqueue.** The id is `{hostId}__{orderId}__{supplierId}` and
|
|
61
|
+
* the write is `create()`, which REFUSES an existing document, so a
|
|
62
|
+
* redelivery cannot reset a retry part-way through its backoff. The AGL-498
|
|
63
|
+
* existence guard upstream already returns before this fan-out, but
|
|
64
|
+
* `reconcile-stock.ts` documents at length why one flag over a dozen effects
|
|
65
|
+
* is not something to lean on.
|
|
66
|
+
* * **Backoff.** 1m, 5m, 15m, 1h, 6h — about seven and a half hours of cover,
|
|
67
|
+
* which is a supplier's overnight deploy but not their week off.
|
|
68
|
+
* * **Dead letter.** After {@link SUPPLIER_DELIVERY_MAX_ATTEMPTS} the row is
|
|
69
|
+
* left as `failed`, the ORDER's own timeline is stamped, and host managers
|
|
70
|
+
* get a notification. The merchant reading the order they were paid for can
|
|
71
|
+
* see that it never reached the supplier — the thing the old code could not
|
|
72
|
+
* tell anyone, because it wrote nothing down.
|
|
73
|
+
*
|
|
74
|
+
* ## Two placement decisions, both deliberate
|
|
75
|
+
*
|
|
76
|
+
* **TOP-LEVEL, not `hosts/{hostId}/supplierDeliveries`.** The host catch-all in
|
|
77
|
+
* the rules file is PERMISSIVE by default — a subcollection nobody names in its
|
|
78
|
+
* three exclusion lists is one every site editor can write from the browser —
|
|
79
|
+
* and the row carries the exact JSON body we sign and POST, so an editor-
|
|
80
|
+
* writable row is an editor-chosen payload delivered under the supplier's own
|
|
81
|
+
* shared secret. A top-level collection matches no rule in
|
|
82
|
+
* `cloud/firebase-firestore.rules` at all and is therefore denied to every
|
|
83
|
+
* client by default, the same footing as `platform/pluginJobs`. It also keeps
|
|
84
|
+
* the drain on an ordinary collection query: a collection-GROUP `where` would
|
|
85
|
+
* need a `COLLECTION_GROUP_ASC` fieldOverride, and AGL-1793 is the record of
|
|
86
|
+
* both commerce crons being silently dead in production for want of exactly
|
|
87
|
+
* that. Neither a rules deploy nor an index deploy is owed for this change.
|
|
88
|
+
*
|
|
89
|
+
* **A delivered row is DELETED, not stamped `delivered`.** The body holds the
|
|
90
|
+
* buyer's email and name, and a top-level document is outside the
|
|
91
|
+
* `hosts/{hostId}` recursive delete an erasure request runs. Retaining the
|
|
92
|
+
* happy path would accumulate third-party PII in a place erasure does not
|
|
93
|
+
* reach, to record something the order's own `routed` timeline entry already
|
|
94
|
+
* says. Dead-lettered rows survive, because those are the ones a human has to
|
|
95
|
+
* act on and the merchant has been told about them.
|
|
96
|
+
*/ /** Top-level collection. Matches no rule, so no client may read or write it. */ export const SUPPLIER_DELIVERY_COLLECTION = 'supplierDeliveries';
|
|
97
|
+
/** Attempts before a delivery is dead-lettered. */ export const SUPPLIER_DELIVERY_MAX_ATTEMPTS = 6;
|
|
98
|
+
/**
|
|
99
|
+
* Delay before the next retry, indexed by attempts ALREADY made.
|
|
100
|
+
*
|
|
101
|
+
* The first entry is 60s and the job beat is also 60s, so a supplier that
|
|
102
|
+
* blipped is retried on the next tick rather than a quarter of an hour later —
|
|
103
|
+
* the difference between a parcel that ships today and one that does not.
|
|
104
|
+
*/ export const SUPPLIER_DELIVERY_BACKOFF_MS = [
|
|
105
|
+
60000,
|
|
106
|
+
300000,
|
|
107
|
+
900000,
|
|
108
|
+
3600000,
|
|
109
|
+
21600000
|
|
110
|
+
];
|
|
111
|
+
/**
|
|
112
|
+
* Per-attempt timeout. Nothing user-facing waits on this — it exists so one
|
|
113
|
+
* hung supplier cannot hold the shared job beat open for the whole scan.
|
|
114
|
+
*/ export const SUPPLIER_DELIVERY_TIMEOUT_MS = 10000;
|
|
115
|
+
/** Rows examined per drain. Bounded like every other scan on the beat. */ export const SUPPLIER_DELIVERY_SCAN_LIMIT = 100;
|
|
116
|
+
/** gRPC `Status.ALREADY_EXISTS` — what `create()` on a live path carries. */ const GRPC_ALREADY_EXISTS = 6;
|
|
117
|
+
/**
|
|
118
|
+
* The delay owed after `attempts` failures, clamped to the last step.
|
|
119
|
+
*
|
|
120
|
+
* Clamped rather than extrapolated: the tail entry is already six hours, and a
|
|
121
|
+
* dead letter is what comes after it, not a longer and longer silence.
|
|
122
|
+
*/ export function supplierDeliveryBackoffMs(attempts) {
|
|
123
|
+
const last = SUPPLIER_DELIVERY_BACKOFF_MS.length - 1;
|
|
124
|
+
return SUPPLIER_DELIVERY_BACKOFF_MS[Math.max(0, Math.min(attempts - 1, last))];
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The outbox document id for one host/order/supplier triple.
|
|
128
|
+
*
|
|
129
|
+
* Deterministic, so `create()` is the idempotency. `__` rather than `:` or `/`:
|
|
130
|
+
* a slash would make `.doc()` build a PATH instead of an id (AGL-1771), and the
|
|
131
|
+
* separator has to survive a Stripe session id, which already contains `_`.
|
|
132
|
+
* Firestore reserves ids fully matching `__.*__`; this never is, because all
|
|
133
|
+
* three parts are non-empty and the id neither starts nor ends with `__`.
|
|
134
|
+
*/ export function supplierDeliveryId(hostId, orderId, supplierId) {
|
|
135
|
+
return `${hostId}__${orderId}__${supplierId}`;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Records that a supplier POST is owed. One Firestore write, awaited.
|
|
139
|
+
*
|
|
140
|
+
* Returns `'queued'`, `'exists'` when a row for this triple is already in
|
|
141
|
+
* flight, or `'skipped'`. NEVER THROWS: the caller is a paid order's fulfilment
|
|
142
|
+
* path, and a queue that cannot be written must not take the receipt down with
|
|
143
|
+
* it. The refusal is logged, which is strictly more than the old code managed
|
|
144
|
+
* on the same failure.
|
|
145
|
+
*/ export async function enqueueSupplierDelivery(options) {
|
|
146
|
+
var _options_now;
|
|
147
|
+
const { firestore, hostId, orderId, supplierId, supplierName, url, body } = options;
|
|
148
|
+
const now = (_options_now = options.now) != null ? _options_now : Date.now();
|
|
149
|
+
const id = supplierDeliveryId(hostId, orderId, supplierId);
|
|
150
|
+
if (!isDocumentId(id)) {
|
|
151
|
+
console.error('Supplier delivery not queued: unusable id', id);
|
|
152
|
+
return 'skipped';
|
|
153
|
+
}
|
|
154
|
+
const record = {
|
|
155
|
+
status: 'pending',
|
|
156
|
+
hostId,
|
|
157
|
+
orderId,
|
|
158
|
+
supplierId,
|
|
159
|
+
supplierName,
|
|
160
|
+
url,
|
|
161
|
+
body,
|
|
162
|
+
attempts: 0,
|
|
163
|
+
// Due immediately: the next beat picks it up, at most a minute out.
|
|
164
|
+
nextAttemptAtMs: now,
|
|
165
|
+
createdAtMs: now
|
|
166
|
+
};
|
|
167
|
+
try {
|
|
168
|
+
await firestore.collection(SUPPLIER_DELIVERY_COLLECTION).doc(id).create(record);
|
|
169
|
+
return 'queued';
|
|
170
|
+
} catch (error) {
|
|
171
|
+
if ((error == null ? void 0 : error.code) === GRPC_ALREADY_EXISTS) {
|
|
172
|
+
return 'exists';
|
|
173
|
+
}
|
|
174
|
+
console.error('Supplier delivery not queued', hostId, orderId, error);
|
|
175
|
+
return 'skipped';
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Stamps a routing failure on the order the merchant actually reads.
|
|
180
|
+
*
|
|
181
|
+
* `update()`, not a merge-set, for `recordRedemptionOrphan`'s reason: the order
|
|
182
|
+
* exists — the delivery was queued from its own fulfilment — and a merge would
|
|
183
|
+
* mint an order stub on the one path where it does not.
|
|
184
|
+
*/ async function stampOrderRoutingFailure(firestore, hostId, orderId, detail) {
|
|
185
|
+
if (!isDocumentId(hostId) || !isDocumentId(orderId)) return;
|
|
186
|
+
await firestore.collection('hosts').doc(hostId).collection('orders').doc(orderId).update({
|
|
187
|
+
timeline: firebaseAdmin.firestore.FieldValue.arrayUnion({
|
|
188
|
+
atMs: Date.now(),
|
|
189
|
+
event: 'routing-failed',
|
|
190
|
+
detail
|
|
191
|
+
})
|
|
192
|
+
}).catch(()=>undefined);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* One delivery attempt.
|
|
196
|
+
*
|
|
197
|
+
* Never throws: the drain is a shared beat, and one merchant's supplier must
|
|
198
|
+
* not abort every other merchant's queue — the `process-restock.ts` lesson,
|
|
199
|
+
* where a single poisoned row killed back-in-stock email platform-wide.
|
|
200
|
+
*/ async function attemptSupplierDelivery(firestore, snapshot, now) {
|
|
201
|
+
var _data_hostId, _data_attempts, _data_supplierId, _supplierSnapshot_get, _supplierSnapshot_get1, _data_body;
|
|
202
|
+
const data = snapshot.data();
|
|
203
|
+
const hostId = String((_data_hostId = data.hostId) != null ? _data_hostId : '');
|
|
204
|
+
const attempts = Number((_data_attempts = data.attempts) != null ? _data_attempts : 0) + 1;
|
|
205
|
+
const retire = async ()=>{
|
|
206
|
+
await snapshot.ref.delete().catch(()=>undefined);
|
|
207
|
+
return 'cancelled';
|
|
208
|
+
};
|
|
209
|
+
if (!isDocumentId(hostId) || !isDocumentId(String((_data_supplierId = data.supplierId) != null ? _data_supplierId : ''))) {
|
|
210
|
+
return retire();
|
|
211
|
+
}
|
|
212
|
+
// The endpoint and the secret are read FRESH rather than frozen into the row:
|
|
213
|
+
// a merchant correcting a typo'd URL corrects what is already queued, and the
|
|
214
|
+
// supplier's shared secret is never copied into a second document.
|
|
215
|
+
const supplierSnapshot = await firestore.collection('hosts').doc(hostId).collection('suppliers').doc(String(data.supplierId)).get().catch(()=>null);
|
|
216
|
+
// A read that FAILED is not a supplier that is gone. Retrying beats retiring
|
|
217
|
+
// a real delivery on a transient Firestore error — the `reconcile-stock.ts`
|
|
218
|
+
// rule that a failed read is not an absent marker.
|
|
219
|
+
if (!supplierSnapshot) {
|
|
220
|
+
return recordFailure(firestore, snapshot, data, attempts, now, 'could not read the supplier record', undefined);
|
|
221
|
+
}
|
|
222
|
+
const url = String((_supplierSnapshot_get = supplierSnapshot.get('webhookUrl')) != null ? _supplierSnapshot_get : '');
|
|
223
|
+
if (!url) {
|
|
224
|
+
// Deliberately NOT a dead letter: the merchant removed the endpoint (or the
|
|
225
|
+
// supplier), which is a decision rather than an outage. Alarming them about
|
|
226
|
+
// a delivery they cancelled is how an alert stops being read.
|
|
227
|
+
return retire();
|
|
228
|
+
}
|
|
229
|
+
const secret = String((_supplierSnapshot_get1 = supplierSnapshot.get('webhookSecret')) != null ? _supplierSnapshot_get1 : '');
|
|
230
|
+
const body = String((_data_body = data.body) != null ? _data_body : '');
|
|
231
|
+
let status;
|
|
232
|
+
try {
|
|
233
|
+
var _ref;
|
|
234
|
+
const response = await fetch(url, {
|
|
235
|
+
method: 'POST',
|
|
236
|
+
headers: _extends({
|
|
237
|
+
'Content-Type': 'application/json'
|
|
238
|
+
}, secret ? {
|
|
239
|
+
'x-aglyn-signature': createHmac('sha256', secret).update(body).digest('hex')
|
|
240
|
+
} : {}),
|
|
241
|
+
body,
|
|
242
|
+
signal: AbortSignal.timeout(SUPPLIER_DELIVERY_TIMEOUT_MS)
|
|
243
|
+
});
|
|
244
|
+
status = Number((_ref = response == null ? void 0 : response.status) != null ? _ref : 0);
|
|
245
|
+
if (!(response == null ? void 0 : response.ok)) {
|
|
246
|
+
return recordFailure(firestore, snapshot, data, attempts, now, `supplier answered HTTP ${status}`, status);
|
|
247
|
+
}
|
|
248
|
+
} catch (error) {
|
|
249
|
+
var _ref1;
|
|
250
|
+
return recordFailure(firestore, snapshot, data, attempts, now, String((_ref1 = error == null ? void 0 : error.message) != null ? _ref1 : error), undefined);
|
|
251
|
+
}
|
|
252
|
+
// Delivered rows are DELETED rather than stamped — see the module note: the
|
|
253
|
+
// body holds the buyer's email and name, and a top-level document is outside
|
|
254
|
+
// the recursive delete an erasure request runs over `hosts/{hostId}`.
|
|
255
|
+
await snapshot.ref.delete().catch(()=>undefined);
|
|
256
|
+
return 'delivered';
|
|
257
|
+
}
|
|
258
|
+
/** Books one failure: another retry, or the dead letter and its two alarms. */ async function recordFailure(firestore, snapshot, data, attempts, now, reason, status) {
|
|
259
|
+
var _data_hostId, _data_orderId, _data_supplierName;
|
|
260
|
+
const exhausted = attempts >= SUPPLIER_DELIVERY_MAX_ATTEMPTS;
|
|
261
|
+
await snapshot.ref.update(_extends({
|
|
262
|
+
status: exhausted ? 'failed' : 'pending',
|
|
263
|
+
attempts,
|
|
264
|
+
lastError: reason
|
|
265
|
+
}, status ? {
|
|
266
|
+
lastStatus: status
|
|
267
|
+
} : {}, exhausted ? {
|
|
268
|
+
failedAtMs: now
|
|
269
|
+
} : {
|
|
270
|
+
nextAttemptAtMs: now + supplierDeliveryBackoffMs(attempts)
|
|
271
|
+
})).catch(()=>undefined);
|
|
272
|
+
if (!exhausted) return 'retried';
|
|
273
|
+
const hostId = String((_data_hostId = data.hostId) != null ? _data_hostId : '');
|
|
274
|
+
const orderId = String((_data_orderId = data.orderId) != null ? _data_orderId : '');
|
|
275
|
+
const supplierName = String((_data_supplierName = data.supplierName) != null ? _data_supplierName : '') || 'the supplier';
|
|
276
|
+
await stampOrderRoutingFailure(firestore, hostId, orderId, `Could not reach ${supplierName} after ${attempts} attempts ` + `(${reason}). This order was never routed.`);
|
|
277
|
+
// The bell as well as the timeline: nobody opens an order they have no
|
|
278
|
+
// reason to suspect.
|
|
279
|
+
await notifyHostManagers(hostId, {
|
|
280
|
+
type: 'content.order',
|
|
281
|
+
title: `Order not routed to ${supplierName}`,
|
|
282
|
+
body: `Order ${orderId} could not be sent after ${attempts} attempts.`,
|
|
283
|
+
link: `/${hostId}/products`
|
|
284
|
+
}).catch(()=>undefined);
|
|
285
|
+
return 'dead-lettered';
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Drains the supplier outbox. Bounded, and safe to run twice.
|
|
289
|
+
*
|
|
290
|
+
* `nextAttemptAtMs` is filtered IN MEMORY rather than added to the query, which
|
|
291
|
+
* would need a composite index for one field's worth of selectivity. The cost
|
|
292
|
+
* is that a row still inside its backoff occupies one of the
|
|
293
|
+
* {@link SUPPLIER_DELIVERY_SCAN_LIMIT} slots; with at most
|
|
294
|
+
* {@link SUPPLIER_DELIVERY_MAX_ATTEMPTS} attempts per row the queue always
|
|
295
|
+
* drains rather than growing a permanent head.
|
|
296
|
+
*/ export async function scanSupplierDeliveries(/** The lockdown gate, injected by the caller (AGL-2495). Not optional. */ gate, now = Date.now()) {
|
|
297
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
298
|
+
const pending = await firestore.collection(SUPPLIER_DELIVERY_COLLECTION).where('status', '==', 'pending').limit(SUPPLIER_DELIVERY_SCAN_LIMIT).get();
|
|
299
|
+
const result = {
|
|
300
|
+
scanned: pending.size,
|
|
301
|
+
delivered: 0,
|
|
302
|
+
retried: 0,
|
|
303
|
+
deadLettered: 0,
|
|
304
|
+
cancelled: 0,
|
|
305
|
+
skippedLocked: 0
|
|
306
|
+
};
|
|
307
|
+
for (const snapshot of pending.docs){
|
|
308
|
+
var _ref, _ref1;
|
|
309
|
+
const data = snapshot.data();
|
|
310
|
+
// LOCKDOWN (AGL-2495). The row is a TOP-LEVEL document carrying its own
|
|
311
|
+
// `hostId` rather than living under `hosts/{hostId}`, so the host is read
|
|
312
|
+
// from the record instead of the parent chain — the one job of the six
|
|
313
|
+
// whose subject is not in its path.
|
|
314
|
+
//
|
|
315
|
+
// An outbound POST to the merchant's supplier is this platform acting in
|
|
316
|
+
// a suspended merchant's name against a third party, which is precisely
|
|
317
|
+
// what a takedown is for. The attempt also mutates the row (`attempts`,
|
|
318
|
+
// `nextAttemptAtMs`, or the dead-letter) and can notify the host's
|
|
319
|
+
// managers.
|
|
320
|
+
//
|
|
321
|
+
// SKIPPED, NOT DROPPED, and the strongest case of the six: the row stays
|
|
322
|
+
// `pending` with its `attempts` count untouched, so a long lock cannot
|
|
323
|
+
// burn a paid order's retry budget and dead-letter it for being locked.
|
|
324
|
+
// The queue drains from where it stood the minute the lock lifts.
|
|
325
|
+
if (await gate.isLocked(String((_ref = data == null ? void 0 : data.hostId) != null ? _ref : ''))) {
|
|
326
|
+
result.skippedLocked += 1;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (Number((_ref1 = data == null ? void 0 : data.nextAttemptAtMs) != null ? _ref1 : 0) > now) continue;
|
|
330
|
+
const outcome = await attemptSupplierDelivery(firestore, snapshot, now).catch((error)=>{
|
|
331
|
+
console.error('Supplier delivery attempt failed', snapshot.ref.path, error);
|
|
332
|
+
return null;
|
|
333
|
+
});
|
|
334
|
+
if (outcome === 'delivered') result.delivered += 1;
|
|
335
|
+
else if (outcome === 'retried') result.retried += 1;
|
|
336
|
+
else if (outcome === 'dead-lettered') result.deadLettered += 1;
|
|
337
|
+
else if (outcome === 'cancelled') result.cancelled += 1;
|
|
338
|
+
}
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
//# sourceMappingURL=supplier-outbox.js.map
|