@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,219 @@
|
|
|
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 Aglyn from "@aglyn/aglyn/server";
|
|
18
|
+
import * as CommerceModel from "../model/index.js";
|
|
19
|
+
import { firebaseAdmin, getOrgForHost, hostSendingIdentity, meterHostEmail } from "@aglyn/tenant-data-admin";
|
|
20
|
+
import { isDocumentId } from "@aglyn/tenant-data-admin/server/document-id";
|
|
21
|
+
import { isDeferrableSendResult, isEmailConfigured, loadHostEmail, renderLoadedHostEmail, sendEmail } from "@aglyn/shared-util-email";
|
|
22
|
+
import { pluginJobHostGate } from "@aglyn/aglyn/server";
|
|
23
|
+
/**
|
|
24
|
+
* The back-in-stock pass itself, separated from its HTTP door (AGL-2227).
|
|
25
|
+
*
|
|
26
|
+
* Same finding as `process-abandoned.ts` beside it: `x-cron-secret`-gated and
|
|
27
|
+
* **never invoked by anything**. `commerce/notify-restock` accepts the
|
|
28
|
+
* shopper's "tell me when it's back" from the storefront and writes
|
|
29
|
+
* `hosts/{hostId}/restockAlerts`; this is the only thing that would ever mail
|
|
30
|
+
* them. Shoppers have been entering an address into a queue with no drain.
|
|
31
|
+
*
|
|
32
|
+
* Exported so `server.ts` can put it on the platform job beat.
|
|
33
|
+
*/ export async function scanRestockAlerts(/** The lockdown gate, injected by the caller (AGL-2495). Not optional. */ gate) {
|
|
34
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
35
|
+
const alerts = await firestore.collectionGroup('restockAlerts').where('notifiedAtMs', '==', null).limit(200).get();
|
|
36
|
+
let sent = 0;
|
|
37
|
+
const productCache = new Map();
|
|
38
|
+
// Resolve each host's designed template once per run (AGL-770).
|
|
39
|
+
const templateCache = new Map();
|
|
40
|
+
// White-label brand per host (White-Label Phase 3): resolved once per host
|
|
41
|
+
// from the owning org doc through the one shared resolver.
|
|
42
|
+
const brandingByHost = new Map();
|
|
43
|
+
/*
|
|
44
|
+
* One identity resolution per SITE, not per message.
|
|
45
|
+
*
|
|
46
|
+
* The same reason the branding map above exists: this sweep runs across
|
|
47
|
+
* every site, and an uncached resolution here would be two Firestore reads
|
|
48
|
+
* per recipient rather than per host. Scoped to the run, so a domain
|
|
49
|
+
* un-verified by the re-check sweep is picked up on the next one.
|
|
50
|
+
*/ const identityByHost = new Map();
|
|
51
|
+
/** Each site's public origin, for the unsubscribe link on the alert. */ const siteBaseByHost = new Map();
|
|
52
|
+
let skippedLocked = 0;
|
|
53
|
+
for (const docSnapshot of alerts.docs){
|
|
54
|
+
var _product_name, _ref, _siteBaseByHost_get;
|
|
55
|
+
var _brandingByHost_get;
|
|
56
|
+
const hostRef = docSnapshot.ref.parent.parent;
|
|
57
|
+
if (!hostRef) continue;
|
|
58
|
+
// LOCKDOWN (AGL-2495), before anything in this body writes. The poisoned-
|
|
59
|
+
// row retirement below stamps `notifiedAtMs`, which is a write, and the
|
|
60
|
+
// email is a message sent in a suspended merchant's name — both wait.
|
|
61
|
+
//
|
|
62
|
+
// SKIPPED, NOT DROPPED: an alert that is not stamped is re-scanned on the
|
|
63
|
+
// next beat, which is exactly the behaviour the `notifiedAtMs == null`
|
|
64
|
+
// query already relies on.
|
|
65
|
+
if (await gate.isLocked(hostRef.id)) {
|
|
66
|
+
skippedLocked += 1;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const data = docSnapshot.data();
|
|
70
|
+
// AGL-1774: `productId` is a STORED FIELD written by an unauthenticated
|
|
71
|
+
// POST (`notify-restock.ts`), and this line is where it becomes a path
|
|
72
|
+
// component. `.doc()` appends a slash-separated path and throws
|
|
73
|
+
// SYNCHRONOUSLY on an even component count — a throw that landed here,
|
|
74
|
+
// outside the loop body's own error handling, so it aborted the entire
|
|
75
|
+
// run. The scan is a `collectionGroup`, so that run is platform-wide;
|
|
76
|
+
// and because the offending alert was never stamped `notifiedAtMs` it
|
|
77
|
+
// returned on every subsequent run, taking every alert ordered after it
|
|
78
|
+
// down too. One anonymous request stopped back-in-stock email for every
|
|
79
|
+
// merchant, permanently.
|
|
80
|
+
//
|
|
81
|
+
// `notify-restock.ts` now refuses such a value at the door, but this
|
|
82
|
+
// guard is not redundant with that one: rows written BEFORE the fix are
|
|
83
|
+
// already in the database, and a create-time check does nothing about
|
|
84
|
+
// them. Treated as "no product" rather than as an error, which is the
|
|
85
|
+
// correct refusal by AGL-1760's test — there is no product, so there is
|
|
86
|
+
// no email owed and no work to discard — and it stamps the alert below
|
|
87
|
+
// so a poisoned row is retired instead of re-scanned forever.
|
|
88
|
+
const productId = isDocumentId(data.productId) ? data.productId : '';
|
|
89
|
+
const cacheKey = `${hostRef.id}:${productId}`;
|
|
90
|
+
if (productId && !productCache.has(cacheKey)) {
|
|
91
|
+
const productSnapshot = await hostRef.collection('products').doc(productId).get();
|
|
92
|
+
productCache.set(cacheKey, productSnapshot.exists ? CommerceModel.liftLegacyProduct(productSnapshot.data()) : null);
|
|
93
|
+
}
|
|
94
|
+
const product = productId ? productCache.get(cacheKey) : null;
|
|
95
|
+
if (!product) {
|
|
96
|
+
await docSnapshot.ref.set({
|
|
97
|
+
notifiedAtMs: Date.now(),
|
|
98
|
+
skipped: true
|
|
99
|
+
}, {
|
|
100
|
+
merge: true
|
|
101
|
+
}).catch(()=>undefined);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const total = CommerceModel.productInventory(product);
|
|
105
|
+
if (total != null && total <= 0) continue;
|
|
106
|
+
const productUrl = `/products/${product.slug}`;
|
|
107
|
+
let loaded = templateCache.get(hostRef.id);
|
|
108
|
+
if (loaded === undefined) {
|
|
109
|
+
var _Aglyn_hostPublicOrigin;
|
|
110
|
+
var _this;
|
|
111
|
+
loaded = await loadHostEmail(firestore, hostRef.id, 'back-in-stock');
|
|
112
|
+
templateCache.set(hostRef.id, loaded);
|
|
113
|
+
brandingByHost.set(hostRef.id, Aglyn.resolveBrandingProfile((_this = await getOrgForHost(hostRef.id).catch(()=>null)) == null ? void 0 : _this.org));
|
|
114
|
+
// The site's own origin, for the unsubscribe link. Resolved once per
|
|
115
|
+
// host beside the branding, because this sweep is a `collectionGroup`
|
|
116
|
+
// over every site's alerts and a read per alert would be a read per
|
|
117
|
+
// shopper.
|
|
118
|
+
const hostSnapshot = await hostRef.get().catch(()=>null);
|
|
119
|
+
siteBaseByHost.set(hostRef.id, (_Aglyn_hostPublicOrigin = Aglyn.hostPublicOrigin({
|
|
120
|
+
cname: hostSnapshot == null ? void 0 : hostSnapshot.get('cname'),
|
|
121
|
+
subdomain: hostSnapshot == null ? void 0 : hostSnapshot.get('subdomain')
|
|
122
|
+
})) != null ? _Aglyn_hostPublicOrigin : '');
|
|
123
|
+
}
|
|
124
|
+
const designed = loaded ? renderLoadedHostEmail(loaded, {
|
|
125
|
+
'product.name': String((_product_name = product.name) != null ? _product_name : ''),
|
|
126
|
+
'product.url': productUrl
|
|
127
|
+
}, Aglyn.sanitizeAuthorHtml) : null;
|
|
128
|
+
/*
|
|
129
|
+
* MARKETING, and `'bulk'` priority, which this sweep is entitled to
|
|
130
|
+
* because it is resumable: an alert left unstamped is re-scanned by the
|
|
131
|
+
* `notifiedAtMs == null` query on the next beat, so a refusal means "not
|
|
132
|
+
* this hour" rather than a shopper who asked to be told and never was.
|
|
133
|
+
*
|
|
134
|
+
* The gate adds the unsubscribe header pair and a visible link, checks
|
|
135
|
+
* both suppression lists, and counts this against how much mail the
|
|
136
|
+
* shopper has had from this site today.
|
|
137
|
+
*/ const result = await sendEmail(_extends({
|
|
138
|
+
to: String(data.email),
|
|
139
|
+
subject: (_ref = designed == null ? void 0 : designed.subject) != null ? _ref : `Back in stock: ${product.name}`,
|
|
140
|
+
text: (designed == null ? void 0 : designed.text) || `${product.name} is available again — grab it before it sells ` + `out:\n\n${productUrl}`
|
|
141
|
+
}, (designed == null ? void 0 : designed.html) ? {
|
|
142
|
+
html: designed.html
|
|
143
|
+
} : {}, {
|
|
144
|
+
fromName: (_brandingByHost_get = brandingByHost.get(hostRef.id)) == null ? void 0 : _brandingByHost_get.fromName,
|
|
145
|
+
sendingIdentity: await hostSendingIdentity(hostRef.id, identityByHost),
|
|
146
|
+
audience: 'tenant',
|
|
147
|
+
context: 'restock alert',
|
|
148
|
+
priority: 'bulk',
|
|
149
|
+
/*
|
|
150
|
+
* A restock alert IS the "Product updates" stream — the catalog entry
|
|
151
|
+
* names restocks in its own description. Declared so a shopper who
|
|
152
|
+
* unticked that stream on the preference page is not mailed it: the
|
|
153
|
+
* gate asks the topic only for callers that name one, so a sender that
|
|
154
|
+
* left this off would keep mailing them.
|
|
155
|
+
*/ marketing: {
|
|
156
|
+
hostId: hostRef.id,
|
|
157
|
+
siteBase: (_siteBaseByHost_get = siteBaseByHost.get(hostRef.id)) != null ? _siteBaseByHost_get : '',
|
|
158
|
+
topicId: Aglyn.EMAIL_TOPIC_PRODUCT_UPDATES
|
|
159
|
+
}
|
|
160
|
+
}));
|
|
161
|
+
/*
|
|
162
|
+
* SKIPPED, NOT DROPPED — the lockdown rule above, applied to the two
|
|
163
|
+
* refusals a later beat can pass.
|
|
164
|
+
*
|
|
165
|
+
* `notifiedAtMs` is what retires an alert from this sweep. Stamping it
|
|
166
|
+
* after the hourly ceiling or the frequency cap refused would drop a
|
|
167
|
+
* request from a shopper who asked to be told; NOT stamping it after a
|
|
168
|
+
* suppression or a rejection would re-read the same doomed row forever,
|
|
169
|
+
* inside a window of two hundred, starving every alert behind it.
|
|
170
|
+
*/ if (isDeferrableSendResult(result)) continue;
|
|
171
|
+
// Cost meter (AGL-1438). One alert per shopper who asked to be told,
|
|
172
|
+
// like the abandoned-checkout reminder beside it, and on the DELIVERED
|
|
173
|
+
// message only — a suppressed recipient produced no message and no cost.
|
|
174
|
+
if (result.sent) await meterHostEmail(hostRef.id);
|
|
175
|
+
await docSnapshot.ref.set({
|
|
176
|
+
notifiedAtMs: Date.now()
|
|
177
|
+
}, {
|
|
178
|
+
merge: true
|
|
179
|
+
}).catch(()=>undefined);
|
|
180
|
+
if (result.sent) sent += 1;
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
scanned: alerts.size,
|
|
184
|
+
sent,
|
|
185
|
+
skippedLocked
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* HTTP door for the pass above (AGL-326), kept for manual/ops invocation and
|
|
190
|
+
* for the console's "send now" control.
|
|
191
|
+
*/ export const processRestockHandler = async (req, res)=>{
|
|
192
|
+
if (req.method !== 'POST') {
|
|
193
|
+
return res.status(405).json({
|
|
194
|
+
error: 'Method not allowed'
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
const cronSecret = process.env.CRON_SECRET;
|
|
198
|
+
if (!cronSecret || req.headers['x-cron-secret'] !== cronSecret) {
|
|
199
|
+
return res.status(401).json({
|
|
200
|
+
error: 'Unauthorized'
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
if (!isEmailConfigured()) {
|
|
204
|
+
return res.status(501).json({
|
|
205
|
+
error: 'Email is not configured.'
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
// The manual door asks the same question the beat does (AGL-2495).
|
|
210
|
+
return res.status(200).json(await scanRestockAlerts(pluginJobHostGate()));
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error(error);
|
|
213
|
+
return res.status(500).json({
|
|
214
|
+
error: 'Processing failed'
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
//# sourceMappingURL=process-restock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/process-restock.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 Aglyn from '@aglyn/aglyn/server'\nimport * as CommerceModel from '../model'\nimport {\n firebaseAdmin,\n getOrgForHost,\n hostSendingIdentity,\n meterHostEmail,\n} from '@aglyn/tenant-data-admin'\nimport { isDocumentId } from '@aglyn/tenant-data-admin/server/document-id'\nimport {\n isDeferrableSendResult,\n isEmailConfigured,\n loadHostEmail,\n renderLoadedHostEmail,\n sendEmail,\n type LoadedHostEmail,\n} from '@aglyn/shared-util-email'\nimport {\n type PluginApiHandler,\n type PluginJobHostGate,\n pluginJobHostGate,\n} from '@aglyn/aglyn/server'\n\nexport interface RestockScanResult {\n scanned: number\n sent: number\n /** Alerts left unstamped because their site is locked (AGL-2495). */\n skippedLocked: number\n}\n\n/**\n * The back-in-stock pass itself, separated from its HTTP door (AGL-2227).\n *\n * Same finding as `process-abandoned.ts` beside it: `x-cron-secret`-gated and\n * **never invoked by anything**. `commerce/notify-restock` accepts the\n * shopper's \"tell me when it's back\" from the storefront and writes\n * `hosts/{hostId}/restockAlerts`; this is the only thing that would ever mail\n * them. Shoppers have been entering an address into a queue with no drain.\n *\n * Exported so `server.ts` can put it on the platform job beat.\n */\nexport async function scanRestockAlerts(\n /** The lockdown gate, injected by the caller (AGL-2495). Not optional. */\n gate: PluginJobHostGate,\n): Promise<RestockScanResult> {\n const firestore = firebaseAdmin.app().firestore()\n const alerts = await firestore\n .collectionGroup('restockAlerts')\n .where('notifiedAtMs', '==', null)\n .limit(200)\n .get()\n let sent = 0\n const productCache = new Map<string, CommerceModel.HostProduct | null>()\n // Resolve each host's designed template once per run (AGL-770).\n const templateCache = new Map<string, LoadedHostEmail | null>()\n // White-label brand per host (White-Label Phase 3): resolved once per host\n // from the owning org doc through the one shared resolver.\n const brandingByHost = new Map<string, Aglyn.ResolvedBrandingProfile>()\n /*\n * One identity resolution per SITE, not per message.\n *\n * The same reason the branding map above exists: this sweep runs across\n * every site, and an uncached resolution here would be two Firestore reads\n * per recipient rather than per host. Scoped to the run, so a domain\n * un-verified by the re-check sweep is picked up on the next one.\n */\n const identityByHost = new Map<\n string,\n Awaited<ReturnType<typeof hostSendingIdentity>>\n >()\n /** Each site's public origin, for the unsubscribe link on the alert. */\n const siteBaseByHost = new Map<string, string>()\n let skippedLocked = 0\n for (const docSnapshot of alerts.docs) {\n const hostRef = docSnapshot.ref.parent.parent\n if (!hostRef) continue\n // LOCKDOWN (AGL-2495), before anything in this body writes. The poisoned-\n // row retirement below stamps `notifiedAtMs`, which is a write, and the\n // email is a message sent in a suspended merchant's name — both wait.\n //\n // SKIPPED, NOT DROPPED: an alert that is not stamped is re-scanned on the\n // next beat, which is exactly the behaviour the `notifiedAtMs == null`\n // query already relies on.\n if (await gate.isLocked(hostRef.id)) {\n skippedLocked += 1\n continue\n }\n const data = docSnapshot.data() as any\n // AGL-1774: `productId` is a STORED FIELD written by an unauthenticated\n // POST (`notify-restock.ts`), and this line is where it becomes a path\n // component. `.doc()` appends a slash-separated path and throws\n // SYNCHRONOUSLY on an even component count — a throw that landed here,\n // outside the loop body's own error handling, so it aborted the entire\n // run. The scan is a `collectionGroup`, so that run is platform-wide;\n // and because the offending alert was never stamped `notifiedAtMs` it\n // returned on every subsequent run, taking every alert ordered after it\n // down too. One anonymous request stopped back-in-stock email for every\n // merchant, permanently.\n //\n // `notify-restock.ts` now refuses such a value at the door, but this\n // guard is not redundant with that one: rows written BEFORE the fix are\n // already in the database, and a create-time check does nothing about\n // them. Treated as \"no product\" rather than as an error, which is the\n // correct refusal by AGL-1760's test — there is no product, so there is\n // no email owed and no work to discard — and it stamps the alert below\n // so a poisoned row is retired instead of re-scanned forever.\n const productId = isDocumentId(data.productId) ? data.productId : ''\n const cacheKey = `${hostRef.id}:${productId}`\n if (productId && !productCache.has(cacheKey)) {\n const productSnapshot = await hostRef\n .collection('products')\n .doc(productId)\n .get()\n productCache.set(\n cacheKey,\n productSnapshot.exists\n ? CommerceModel.liftLegacyProduct(productSnapshot.data() as any)\n : null,\n )\n }\n const product = productId ? productCache.get(cacheKey) : null\n if (!product) {\n await docSnapshot.ref\n .set({ notifiedAtMs: Date.now(), skipped: true }, { merge: true })\n .catch(() => undefined)\n continue\n }\n const total = CommerceModel.productInventory(product)\n if (total != null && total <= 0) continue\n const productUrl = `/products/${product.slug}`\n let loaded = templateCache.get(hostRef.id)\n if (loaded === undefined) {\n loaded = await loadHostEmail(firestore, hostRef.id, 'back-in-stock')\n templateCache.set(hostRef.id, loaded)\n brandingByHost.set(\n hostRef.id,\n Aglyn.resolveBrandingProfile(\n (await getOrgForHost(hostRef.id).catch(() => null))?.org as never,\n ),\n )\n // The site's own origin, for the unsubscribe link. Resolved once per\n // host beside the branding, because this sweep is a `collectionGroup`\n // over every site's alerts and a read per alert would be a read per\n // shopper.\n const hostSnapshot = await hostRef.get().catch(() => null)\n siteBaseByHost.set(\n hostRef.id,\n Aglyn.hostPublicOrigin({\n cname: hostSnapshot?.get('cname'),\n subdomain: hostSnapshot?.get('subdomain'),\n }) ?? '',\n )\n }\n const designed = loaded\n ? renderLoadedHostEmail(\n loaded,\n {\n 'product.name': String(product.name ?? ''),\n 'product.url': productUrl,\n },\n Aglyn.sanitizeAuthorHtml,\n )\n : null\n /*\n * MARKETING, and `'bulk'` priority, which this sweep is entitled to\n * because it is resumable: an alert left unstamped is re-scanned by the\n * `notifiedAtMs == null` query on the next beat, so a refusal means \"not\n * this hour\" rather than a shopper who asked to be told and never was.\n *\n * The gate adds the unsubscribe header pair and a visible link, checks\n * both suppression lists, and counts this against how much mail the\n * shopper has had from this site today.\n */\n const result = await sendEmail({\n to: String(data.email),\n subject: designed?.subject ?? `Back in stock: ${product.name}`,\n text:\n designed?.text ||\n `${product.name} is available again — grab it before it sells ` +\n `out:\\n\\n${productUrl}`,\n ...(designed?.html ? { html: designed.html } : {}),\n fromName: brandingByHost.get(hostRef.id)?.fromName,\n sendingIdentity: await hostSendingIdentity(hostRef.id, identityByHost),\n audience: 'tenant',\n context: 'restock alert',\n priority: 'bulk',\n /*\n * A restock alert IS the \"Product updates\" stream — the catalog entry\n * names restocks in its own description. Declared so a shopper who\n * unticked that stream on the preference page is not mailed it: the\n * gate asks the topic only for callers that name one, so a sender that\n * left this off would keep mailing them.\n */\n marketing: {\n hostId: hostRef.id,\n siteBase: siteBaseByHost.get(hostRef.id) ?? '',\n topicId: Aglyn.EMAIL_TOPIC_PRODUCT_UPDATES,\n },\n })\n /*\n * SKIPPED, NOT DROPPED — the lockdown rule above, applied to the two\n * refusals a later beat can pass.\n *\n * `notifiedAtMs` is what retires an alert from this sweep. Stamping it\n * after the hourly ceiling or the frequency cap refused would drop a\n * request from a shopper who asked to be told; NOT stamping it after a\n * suppression or a rejection would re-read the same doomed row forever,\n * inside a window of two hundred, starving every alert behind it.\n */\n if (isDeferrableSendResult(result)) continue\n // Cost meter (AGL-1438). One alert per shopper who asked to be told,\n // like the abandoned-checkout reminder beside it, and on the DELIVERED\n // message only — a suppressed recipient produced no message and no cost.\n if (result.sent) await meterHostEmail(hostRef.id)\n await docSnapshot.ref\n .set({ notifiedAtMs: Date.now() }, { merge: true })\n .catch(() => undefined)\n if (result.sent) sent += 1\n }\n return { scanned: alerts.size, sent, skippedLocked }\n}\n\n/**\n * HTTP door for the pass above (AGL-326), kept for manual/ops invocation and\n * for the console's \"send now\" control.\n */\nexport const processRestockHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const cronSecret = process.env.CRON_SECRET\n if (!cronSecret || req.headers['x-cron-secret'] !== cronSecret) {\n return res.status(401).json({ error: 'Unauthorized' })\n }\n if (!isEmailConfigured()) {\n return res.status(501).json({ error: 'Email is not configured.' })\n }\n try {\n // The manual door asks the same question the beat does (AGL-2495).\n return res.status(200).json(await scanRestockAlerts(pluginJobHostGate()))\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Processing failed' })\n }\n}\n"],"names":["Aglyn","CommerceModel","firebaseAdmin","getOrgForHost","hostSendingIdentity","meterHostEmail","isDocumentId","isDeferrableSendResult","isEmailConfigured","loadHostEmail","renderLoadedHostEmail","sendEmail","pluginJobHostGate","scanRestockAlerts","gate","firestore","app","alerts","collectionGroup","where","limit","get","sent","productCache","Map","templateCache","brandingByHost","identityByHost","siteBaseByHost","skippedLocked","docSnapshot","docs","product","hostRef","ref","parent","isLocked","id","data","productId","cacheKey","has","productSnapshot","collection","doc","set","exists","liftLegacyProduct","notifiedAtMs","Date","now","skipped","merge","catch","undefined","total","productInventory","productUrl","slug","loaded","resolveBrandingProfile","org","hostSnapshot","hostPublicOrigin","cname","subdomain","designed","String","name","sanitizeAuthorHtml","result","to","email","subject","text","html","fromName","sendingIdentity","audience","context","priority","marketing","hostId","siteBase","topicId","EMAIL_TOPIC_PRODUCT_UPDATES","scanned","size","processRestockHandler","req","res","method","status","json","error","cronSecret","process","env","CRON_SECRET","headers","console"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,YAAYA,WAAW,sBAAqB;AAC5C,YAAYC,mBAAmB,oBAAU;AACzC,SACEC,aAAa,EACbC,aAAa,EACbC,mBAAmB,EACnBC,cAAc,QACT,2BAA0B;AACjC,SAASC,YAAY,QAAQ,8CAA6C;AAC1E,SACEC,sBAAsB,EACtBC,iBAAiB,EACjBC,aAAa,EACbC,qBAAqB,EACrBC,SAAS,QAEJ,2BAA0B;AACjC,SAGEC,iBAAiB,QACZ,sBAAqB;AAS5B;;;;;;;;;;CAUC,GACD,OAAO,eAAeC,kBACpB,wEAAwE,GACxEC,IAAuB;IAEvB,MAAMC,YAAYb,cAAcc,GAAG,GAAGD,SAAS;IAC/C,MAAME,SAAS,MAAMF,UAClBG,eAAe,CAAC,iBAChBC,KAAK,CAAC,gBAAgB,MAAM,MAC5BC,KAAK,CAAC,KACNC,GAAG;IACN,IAAIC,OAAO;IACX,MAAMC,eAAe,IAAIC;IACzB,gEAAgE;IAChE,MAAMC,gBAAgB,IAAID;IAC1B,2EAA2E;IAC3E,2DAA2D;IAC3D,MAAME,iBAAiB,IAAIF;IAC3B;;;;;;;GAOC,GACD,MAAMG,iBAAiB,IAAIH;IAI3B,sEAAsE,GACtE,MAAMI,iBAAiB,IAAIJ;IAC3B,IAAIK,gBAAgB;IACpB,KAAK,MAAMC,eAAeb,OAAOc,IAAI,CAAE;YAoFNC,qBAsCjBJ;YAdFF;QA3GZ,MAAMO,UAAUH,YAAYI,GAAG,CAACC,MAAM,CAACA,MAAM;QAC7C,IAAI,CAACF,SAAS;QACd,0EAA0E;QAC1E,wEAAwE;QACxE,sEAAsE;QACtE,EAAE;QACF,0EAA0E;QAC1E,uEAAuE;QACvE,2BAA2B;QAC3B,IAAI,MAAMnB,KAAKsB,QAAQ,CAACH,QAAQI,EAAE,GAAG;YACnCR,iBAAiB;YACjB;QACF;QACA,MAAMS,OAAOR,YAAYQ,IAAI;QAC7B,wEAAwE;QACxE,uEAAuE;QACvE,gEAAgE;QAChE,uEAAuE;QACvE,uEAAuE;QACvE,sEAAsE;QACtE,sEAAsE;QACtE,wEAAwE;QACxE,wEAAwE;QACxE,yBAAyB;QACzB,EAAE;QACF,qEAAqE;QACrE,wEAAwE;QACxE,sEAAsE;QACtE,sEAAsE;QACtE,wEAAwE;QACxE,uEAAuE;QACvE,8DAA8D;QAC9D,MAAMC,YAAYjC,aAAagC,KAAKC,SAAS,IAAID,KAAKC,SAAS,GAAG;QAClE,MAAMC,WAAW,GAAGP,QAAQI,EAAE,CAAC,CAAC,EAAEE,WAAW;QAC7C,IAAIA,aAAa,CAAChB,aAAakB,GAAG,CAACD,WAAW;YAC5C,MAAME,kBAAkB,MAAMT,QAC3BU,UAAU,CAAC,YACXC,GAAG,CAACL,WACJlB,GAAG;YACNE,aAAasB,GAAG,CACdL,UACAE,gBAAgBI,MAAM,GAClB7C,cAAc8C,iBAAiB,CAACL,gBAAgBJ,IAAI,MACpD;QAER;QACA,MAAMN,UAAUO,YAAYhB,aAAaF,GAAG,CAACmB,YAAY;QACzD,IAAI,CAACR,SAAS;YACZ,MAAMF,YAAYI,GAAG,CAClBW,GAAG,CAAC;gBAAEG,cAAcC,KAAKC,GAAG;gBAAIC,SAAS;YAAK,GAAG;gBAAEC,OAAO;YAAK,GAC/DC,KAAK,CAAC,IAAMC;YACf;QACF;QACA,MAAMC,QAAQtD,cAAcuD,gBAAgB,CAACxB;QAC7C,IAAIuB,SAAS,QAAQA,SAAS,GAAG;QACjC,MAAME,aAAa,CAAC,UAAU,EAAEzB,QAAQ0B,IAAI,EAAE;QAC9C,IAAIC,SAASlC,cAAcJ,GAAG,CAACY,QAAQI,EAAE;QACzC,IAAIsB,WAAWL,WAAW;gBAgBtBtD;gBAVG;YALL2D,SAAS,MAAMlD,cAAcM,WAAWkB,QAAQI,EAAE,EAAE;YACpDZ,cAAcoB,GAAG,CAACZ,QAAQI,EAAE,EAAEsB;YAC9BjC,eAAemB,GAAG,CAChBZ,QAAQI,EAAE,EACVrC,MAAM4D,sBAAsB,EACzB,QAAA,MAAMzD,cAAc8B,QAAQI,EAAE,EAAEgB,KAAK,CAAC,IAAM,0BAA7C,AAAC,MAAoDQ,GAAG;YAG5D,qEAAqE;YACrE,sEAAsE;YACtE,oEAAoE;YACpE,WAAW;YACX,MAAMC,eAAe,MAAM7B,QAAQZ,GAAG,GAAGgC,KAAK,CAAC,IAAM;YACrDzB,eAAeiB,GAAG,CAChBZ,QAAQI,EAAE,GACVrC,0BAAAA,MAAM+D,gBAAgB,CAAC;gBACrBC,KAAK,EAAEF,gCAAAA,aAAczC,GAAG,CAAC;gBACzB4C,SAAS,EAAEH,gCAAAA,aAAczC,GAAG,CAAC;YAC/B,cAHArB,0BAGM;QAEV;QACA,MAAMkE,WAAWP,SACbjD,sBACEiD,QACA;YACE,gBAAgBQ,QAAOnC,gBAAAA,QAAQoC,IAAI,YAAZpC,gBAAgB;YACvC,eAAeyB;QACjB,GACAzD,MAAMqE,kBAAkB,IAE1B;QACJ;;;;;;;;;KASC,GACD,MAAMC,SAAS,MAAM3D,UAAU;YAC7B4D,IAAIJ,OAAO7B,KAAKkC,KAAK;YACrBC,OAAO,UAAEP,4BAAAA,SAAUO,OAAO,mBAAI,CAAC,eAAe,EAAEzC,QAAQoC,IAAI,EAAE;YAC9DM,MACER,CAAAA,4BAAAA,SAAUQ,IAAI,KACd,GAAG1C,QAAQoC,IAAI,CAAC,8CAA8C,CAAC,GAC7D,CAAC,QAAQ,EAAEX,YAAY;WACvBS,CAAAA,4BAAAA,SAAUS,IAAI,IAAG;YAAEA,MAAMT,SAASS,IAAI;QAAC,IAAI,CAAC;YAChDC,QAAQ,GAAElD,sBAAAA,eAAeL,GAAG,CAACY,QAAQI,EAAE,sBAA7BX,oBAAgCkD,QAAQ;YAClDC,iBAAiB,MAAMzE,oBAAoB6B,QAAQI,EAAE,EAAEV;YACvDmD,UAAU;YACVC,SAAS;YACTC,UAAU;YACV;;;;;;OAMC,GACDC,WAAW;gBACTC,QAAQjD,QAAQI,EAAE;gBAClB8C,QAAQ,GAAEvD,sBAAAA,eAAeP,GAAG,CAACY,QAAQI,EAAE,aAA7BT,sBAAkC;gBAC5CwD,SAASpF,MAAMqF,2BAA2B;YAC5C;;QAEF;;;;;;;;;KASC,GACD,IAAI9E,uBAAuB+D,SAAS;QACpC,qEAAqE;QACrE,uEAAuE;QACvE,yEAAyE;QACzE,IAAIA,OAAOhD,IAAI,EAAE,MAAMjB,eAAe4B,QAAQI,EAAE;QAChD,MAAMP,YAAYI,GAAG,CAClBW,GAAG,CAAC;YAAEG,cAAcC,KAAKC,GAAG;QAAG,GAAG;YAAEE,OAAO;QAAK,GAChDC,KAAK,CAAC,IAAMC;QACf,IAAIgB,OAAOhD,IAAI,EAAEA,QAAQ;IAC3B;IACA,OAAO;QAAEgE,SAASrE,OAAOsE,IAAI;QAAEjE;QAAMO;IAAc;AACrD;AAEA;;;CAGC,GACD,OAAO,MAAM2D,wBAA0C,OAAOC,KAAKC;IACjE,IAAID,IAAIE,MAAM,KAAK,QAAQ;QACzB,OAAOD,IAAIE,MAAM,CAAC,KAAKC,IAAI,CAAC;YAAEC,OAAO;QAAqB;IAC5D;IACA,MAAMC,aAAaC,QAAQC,GAAG,CAACC,WAAW;IAC1C,IAAI,CAACH,cAAcN,IAAIU,OAAO,CAAC,gBAAgB,KAAKJ,YAAY;QAC9D,OAAOL,IAAIE,MAAM,CAAC,KAAKC,IAAI,CAAC;YAAEC,OAAO;QAAe;IACtD;IACA,IAAI,CAACtF,qBAAqB;QACxB,OAAOkF,IAAIE,MAAM,CAAC,KAAKC,IAAI,CAAC;YAAEC,OAAO;QAA2B;IAClE;IACA,IAAI;QACF,mEAAmE;QACnE,OAAOJ,IAAIE,MAAM,CAAC,KAAKC,IAAI,CAAC,MAAMhF,kBAAkBD;IACtD,EAAE,OAAOkF,OAAO;QACdM,QAAQN,KAAK,CAACA;QACd,OAAOJ,IAAIE,MAAM,CAAC,KAAKC,IAAI,CAAC;YAAEC,OAAO;QAAoB;IAC3D;AACF,EAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
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 PluginRecordCard } from '@aglyn/aglyn/plugin-manager/plugin-record-cards';
|
|
18
|
+
/**
|
|
19
|
+
* What a product looks like in one line and one image, for a surface that is
|
|
20
|
+
* not this plugin's own — a designed email that features it, today.
|
|
21
|
+
*
|
|
22
|
+
* The rule that matters is the caption: a product's "from" price is the lowest
|
|
23
|
+
* variant price, and {@link productPriceRange} is the one place that is worked
|
|
24
|
+
* out. A caller that read the product document itself would have to import it
|
|
25
|
+
* to agree, which is how the marketing plugin came to depend on this one.
|
|
26
|
+
*/
|
|
27
|
+
export declare function productCard(productId: string, data: Record<string, any>): PluginRecordCard;
|
|
28
|
+
/** Registered from both server registrars, so either app's handlers can ask. */
|
|
29
|
+
export declare function registerProductCardReader(): void;
|
|
@@ -0,0 +1,54 @@
|
|
|
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 { registerPluginRecordCardReader } from "@aglyn/aglyn/plugin-manager/plugin-record-cards";
|
|
18
|
+
import { firebaseAdmin } from "@aglyn/tenant-data-admin";
|
|
19
|
+
import { BUNDLE_ID } from "../constants/bundle-common.js";
|
|
20
|
+
import { productPriceRange } from "../model/index.js";
|
|
21
|
+
/**
|
|
22
|
+
* What a product looks like in one line and one image, for a surface that is
|
|
23
|
+
* not this plugin's own — a designed email that features it, today.
|
|
24
|
+
*
|
|
25
|
+
* The rule that matters is the caption: a product's "from" price is the lowest
|
|
26
|
+
* variant price, and {@link productPriceRange} is the one place that is worked
|
|
27
|
+
* out. A caller that read the product document itself would have to import it
|
|
28
|
+
* to agree, which is how the marketing plugin came to depend on this one.
|
|
29
|
+
*/ export function productCard(productId, data) {
|
|
30
|
+
var _data_name, _data_imageUrl, _data_imageUrl1;
|
|
31
|
+
var _data_mediaUrls, _data_mediaUrls1;
|
|
32
|
+
const [minPrice] = productPriceRange(data);
|
|
33
|
+
return _extends({
|
|
34
|
+
title: String((_data_name = data['name']) != null ? _data_name : productId)
|
|
35
|
+
}, minPrice ? {
|
|
36
|
+
caption: `$${minPrice}`
|
|
37
|
+
} : {}, ((_data_imageUrl = data['imageUrl']) != null ? _data_imageUrl : (_data_mediaUrls = data['mediaUrls']) == null ? void 0 : _data_mediaUrls[0]) ? {
|
|
38
|
+
imageUrl: (_data_imageUrl1 = data['imageUrl']) != null ? _data_imageUrl1 : (_data_mediaUrls1 = data['mediaUrls']) == null ? void 0 : _data_mediaUrls1[0]
|
|
39
|
+
} : {}, data['slug'] ? {
|
|
40
|
+
path: `/products/${data['slug']}`
|
|
41
|
+
} : {});
|
|
42
|
+
}
|
|
43
|
+
/** Registered from both server registrars, so either app's handlers can ask. */ export function registerProductCardReader() {
|
|
44
|
+
registerPluginRecordCardReader('product', {
|
|
45
|
+
async read ({ hostId, id }) {
|
|
46
|
+
const snapshot = await firebaseAdmin.app().firestore().collection('hosts').doc(hostId).collection('products').doc(id).get();
|
|
47
|
+
return snapshot.exists ? productCard(id, snapshot.data()) : null;
|
|
48
|
+
}
|
|
49
|
+
}, {
|
|
50
|
+
pluginId: BUNDLE_ID
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//# sourceMappingURL=product-card.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/product-card.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 {\n registerPluginRecordCardReader,\n type PluginRecordCard,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-cards'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { productPriceRange } from '../model'\n\n/**\n * What a product looks like in one line and one image, for a surface that is\n * not this plugin's own — a designed email that features it, today.\n *\n * The rule that matters is the caption: a product's \"from\" price is the lowest\n * variant price, and {@link productPriceRange} is the one place that is worked\n * out. A caller that read the product document itself would have to import it\n * to agree, which is how the marketing plugin came to depend on this one.\n */\nexport function productCard(\n productId: string,\n data: Record<string, any>,\n): PluginRecordCard {\n const [minPrice] = productPriceRange(data as Parameters<typeof productPriceRange>[0])\n return {\n title: String(data['name'] ?? productId),\n ...(minPrice ? { caption: `$${minPrice}` } : {}),\n ...(data['imageUrl'] ?? data['mediaUrls']?.[0]\n ? { imageUrl: data['imageUrl'] ?? data['mediaUrls']?.[0] }\n : {}),\n ...(data['slug'] ? { path: `/products/${data['slug']}` } : {}),\n }\n}\n\n/** Registered from both server registrars, so either app's handlers can ask. */\nexport function registerProductCardReader(): void {\n registerPluginRecordCardReader(\n 'product',\n {\n async read({ hostId, id }) {\n const snapshot = await firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(hostId)\n .collection('products')\n .doc(id)\n .get()\n return snapshot.exists\n ? productCard(id, snapshot.data() as Record<string, any>)\n : null\n },\n },\n { pluginId: BUNDLE_ID },\n )\n}\n"],"names":["registerPluginRecordCardReader","firebaseAdmin","BUNDLE_ID","productPriceRange","productCard","productId","data","minPrice","title","String","caption","imageUrl","path","registerProductCardReader","read","hostId","id","snapshot","app","firestore","collection","doc","get","exists","pluginId"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,8BAA8B,QAEzB,kDAAiD;AACxD,SAASC,aAAa,QAAQ,2BAA0B;AACxD,SAASC,SAAS,QAAQ,gCAA4B;AACtD,SAASC,iBAAiB,QAAQ,oBAAU;AAE5C;;;;;;;;CAQC,GACD,OAAO,SAASC,YACdC,SAAiB,EACjBC,IAAyB;QAITA,YAEVA,gBACYA;QADQA,iBACYA;IALtC,MAAM,CAACC,SAAS,GAAGJ,kBAAkBG;IACrC,OAAO;QACLE,OAAOC,QAAOH,aAAAA,IAAI,CAAC,OAAO,YAAZA,aAAgBD;OAC1BE,WAAW;QAAEG,SAAS,CAAC,CAAC,EAAEH,UAAU;IAAC,IAAI,CAAC,GAC1CD,EAAAA,iBAAAA,IAAI,CAAC,WAAW,YAAhBA,kBAAoBA,kBAAAA,IAAI,CAAC,YAAY,qBAAjBA,eAAmB,CAAC,EAAE,IAC1C;QAAEK,QAAQ,GAAEL,kBAAAA,IAAI,CAAC,WAAW,YAAhBA,mBAAoBA,mBAAAA,IAAI,CAAC,YAAY,qBAAjBA,gBAAmB,CAAC,EAAE;IAAC,IACvD,CAAC,GACDA,IAAI,CAAC,OAAO,GAAG;QAAEM,MAAM,CAAC,UAAU,EAAEN,IAAI,CAAC,OAAO,EAAE;IAAC,IAAI,CAAC;AAEhE;AAEA,8EAA8E,GAC9E,OAAO,SAASO;IACdb,+BACE,WACA;QACE,MAAMc,MAAK,EAAEC,MAAM,EAAEC,EAAE,EAAE;YACvB,MAAMC,WAAW,MAAMhB,cACpBiB,GAAG,GACHC,SAAS,GACTC,UAAU,CAAC,SACXC,GAAG,CAACN,QACJK,UAAU,CAAC,YACXC,GAAG,CAACL,IACJM,GAAG;YACN,OAAOL,SAASM,MAAM,GAClBnB,YAAYY,IAAIC,SAASX,IAAI,MAC7B;QACN;IACF,GACA;QAAEkB,UAAUtB;IAAU;AAE1B"}
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
import * as CommerceModel from '../model';
|
|
19
|
+
export interface PublicProductDetail {
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
slug: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
type: CommerceModel.ProductType;
|
|
25
|
+
mediaUrls: string[];
|
|
26
|
+
options: CommerceModel.ProductOption[];
|
|
27
|
+
variants: Array<{
|
|
28
|
+
id: string;
|
|
29
|
+
options?: Record<string, string>;
|
|
30
|
+
priceUsd: number;
|
|
31
|
+
compareAtPriceUsd?: number;
|
|
32
|
+
soldOut: boolean;
|
|
33
|
+
imageUrl?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Merchant's stock-keeping unit (AGL-686). Carried into the public DTO
|
|
36
|
+
* so the server can put `sku` on the Product structured data — Google
|
|
37
|
+
* Merchant treats it as required for a product offer, and without it a
|
|
38
|
+
* listing is simply ineligible for rich results rather than warned about.
|
|
39
|
+
* It is already public information: it prints on receipts and invoices.
|
|
40
|
+
*/
|
|
41
|
+
sku?: string;
|
|
42
|
+
}>;
|
|
43
|
+
tags?: string[];
|
|
44
|
+
/** Recurring billing (AGL-303) so the PDP frames the price. */
|
|
45
|
+
subscription?: {
|
|
46
|
+
interval: 'month' | 'year';
|
|
47
|
+
trialDays?: number;
|
|
48
|
+
};
|
|
49
|
+
/** Buyer may choose one-time OR subscribe (AGL-545). */
|
|
50
|
+
subscriptionOptional?: boolean;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Map a lifted product onto the public PDP DTO.
|
|
54
|
+
*
|
|
55
|
+
* Extracted (AGL-659) so the site-page resolver can put the SAME payload
|
|
56
|
+
* into page props as the API returns — the PDP block used to fetch this in
|
|
57
|
+
* an effect, which meant the server rendered a skeleton and crawlers saw a
|
|
58
|
+
* product page with no product in it. One definition keeps the server-
|
|
59
|
+
* rendered and client-fetched shapes from drifting.
|
|
60
|
+
*
|
|
61
|
+
* Display-only by design: stock is a `soldOut` boolean rather than a raw
|
|
62
|
+
* count, and the charge still prices server-side.
|
|
63
|
+
*/
|
|
64
|
+
export declare function toPublicProductDetail(id: string, product: ReturnType<typeof CommerceModel.liftLegacyProduct>): PublicProductDetail;
|
|
65
|
+
/**
|
|
66
|
+
* Public product detail by slug (AGL-292): everything the PDP block
|
|
67
|
+
* needs — variants with per-option prices and stock booleans, never raw
|
|
68
|
+
* counts. Display-only; the charge still prices server-side.
|
|
69
|
+
*/
|
|
70
|
+
export declare const productHandler: PluginApiHandler;
|
|
@@ -0,0 +1,105 @@
|
|
|
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 * as CommerceModel from "../model/index.js";
|
|
18
|
+
import { firebaseAdmin } from "@aglyn/tenant-data-admin";
|
|
19
|
+
/**
|
|
20
|
+
* Map a lifted product onto the public PDP DTO.
|
|
21
|
+
*
|
|
22
|
+
* Extracted (AGL-659) so the site-page resolver can put the SAME payload
|
|
23
|
+
* into page props as the API returns — the PDP block used to fetch this in
|
|
24
|
+
* an effect, which meant the server rendered a skeleton and crawlers saw a
|
|
25
|
+
* product page with no product in it. One definition keeps the server-
|
|
26
|
+
* rendered and client-fetched shapes from drifting.
|
|
27
|
+
*
|
|
28
|
+
* Display-only by design: stock is a `soldOut` boolean rather than a raw
|
|
29
|
+
* count, and the charge still prices server-side.
|
|
30
|
+
*/ export function toPublicProductDetail(id, product) {
|
|
31
|
+
var _product_mediaUrls, _product_options;
|
|
32
|
+
var _product_tags;
|
|
33
|
+
return _extends({
|
|
34
|
+
id,
|
|
35
|
+
name: product.name,
|
|
36
|
+
slug: product.slug
|
|
37
|
+
}, product.description ? {
|
|
38
|
+
description: product.description
|
|
39
|
+
} : {}, {
|
|
40
|
+
type: product.type,
|
|
41
|
+
mediaUrls: (_product_mediaUrls = product.mediaUrls) != null ? _product_mediaUrls : product.imageUrl ? [
|
|
42
|
+
product.imageUrl
|
|
43
|
+
] : [],
|
|
44
|
+
options: (_product_options = product.options) != null ? _product_options : [],
|
|
45
|
+
variants: product.variants.map((variant)=>_extends({
|
|
46
|
+
id: variant.id
|
|
47
|
+
}, variant.options ? {
|
|
48
|
+
options: variant.options
|
|
49
|
+
} : {}, {
|
|
50
|
+
priceUsd: variant.priceUsd
|
|
51
|
+
}, variant.compareAtPriceUsd ? {
|
|
52
|
+
compareAtPriceUsd: variant.compareAtPriceUsd
|
|
53
|
+
} : {}, {
|
|
54
|
+
soldOut: variant.inventory != null && Number(variant.inventory) <= 0 && product.oversellPolicy !== 'backorder'
|
|
55
|
+
}, variant.imageUrl ? {
|
|
56
|
+
imageUrl: variant.imageUrl
|
|
57
|
+
} : {}, variant.sku ? {
|
|
58
|
+
sku: variant.sku
|
|
59
|
+
} : {}))
|
|
60
|
+
}, ((_product_tags = product.tags) == null ? void 0 : _product_tags.length) ? {
|
|
61
|
+
tags: product.tags
|
|
62
|
+
} : {}, product.subscription ? {
|
|
63
|
+
subscription: product.subscription
|
|
64
|
+
} : {}, product.subscription && product.subscriptionOptional ? {
|
|
65
|
+
subscriptionOptional: true
|
|
66
|
+
} : {});
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Public product detail by slug (AGL-292): everything the PDP block
|
|
70
|
+
* needs — variants with per-option prices and stock booleans, never raw
|
|
71
|
+
* counts. Display-only; the charge still prices server-side.
|
|
72
|
+
*/ export const productHandler = async (req, res)=>{
|
|
73
|
+
var _req_query_hostId, _req_query_slug;
|
|
74
|
+
const hostId = String((_req_query_hostId = req.query.hostId) != null ? _req_query_hostId : '');
|
|
75
|
+
const slug = String((_req_query_slug = req.query.slug) != null ? _req_query_slug : '');
|
|
76
|
+
if (!hostId || !slug) {
|
|
77
|
+
return res.status(400).json({
|
|
78
|
+
error: 'Missing hostId or slug'
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
83
|
+
const snapshot = await firestore.collection('hosts').doc(hostId).collection('products').where('slug', '==', slug).limit(1).get();
|
|
84
|
+
const docSnapshot = snapshot.docs[0];
|
|
85
|
+
const raw = docSnapshot == null ? void 0 : docSnapshot.data();
|
|
86
|
+
if (!raw || raw.deletedAt || raw.status !== 'active') {
|
|
87
|
+
return res.status(404).json({
|
|
88
|
+
error: 'Unknown product'
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const product = CommerceModel.liftLegacyProduct(raw);
|
|
92
|
+
const detail = toPublicProductDetail(docSnapshot.id, product);
|
|
93
|
+
res.setHeader('Cache-Control', 'public, s-maxage=60, stale-while-revalidate=300');
|
|
94
|
+
return res.status(200).json({
|
|
95
|
+
product: detail
|
|
96
|
+
});
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error(error);
|
|
99
|
+
return res.status(500).json({
|
|
100
|
+
error: 'Product unavailable'
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
//# sourceMappingURL=product.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/product.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 * as Aglyn from '@aglyn/aglyn/server'\nimport * as CommerceModel from '../model'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\n\nexport interface PublicProductDetail {\n id: string\n name: string\n slug: string\n description?: string\n type: CommerceModel.ProductType\n mediaUrls: string[]\n options: CommerceModel.ProductOption[]\n variants: Array<{\n id: string\n options?: Record<string, string>\n priceUsd: number\n compareAtPriceUsd?: number\n soldOut: boolean\n imageUrl?: string\n /**\n * Merchant's stock-keeping unit (AGL-686). Carried into the public DTO\n * so the server can put `sku` on the Product structured data — Google\n * Merchant treats it as required for a product offer, and without it a\n * listing is simply ineligible for rich results rather than warned about.\n * It is already public information: it prints on receipts and invoices.\n */\n sku?: string\n }>\n tags?: string[]\n /** Recurring billing (AGL-303) so the PDP frames the price. */\n subscription?: { interval: 'month' | 'year'; trialDays?: number }\n /** Buyer may choose one-time OR subscribe (AGL-545). */\n subscriptionOptional?: boolean\n}\n\n/**\n * Map a lifted product onto the public PDP DTO.\n *\n * Extracted (AGL-659) so the site-page resolver can put the SAME payload\n * into page props as the API returns — the PDP block used to fetch this in\n * an effect, which meant the server rendered a skeleton and crawlers saw a\n * product page with no product in it. One definition keeps the server-\n * rendered and client-fetched shapes from drifting.\n *\n * Display-only by design: stock is a `soldOut` boolean rather than a raw\n * count, and the charge still prices server-side.\n */\nexport function toPublicProductDetail(\n id: string,\n product: ReturnType<typeof CommerceModel.liftLegacyProduct>,\n): PublicProductDetail {\n return {\n id,\n name: product.name,\n slug: product.slug,\n ...(product.description ? { description: product.description } : {}),\n type: product.type,\n mediaUrls:\n product.mediaUrls ?? (product.imageUrl ? [product.imageUrl] : []),\n options: product.options ?? [],\n variants: product.variants.map((variant) => ({\n id: variant.id,\n ...(variant.options ? { options: variant.options } : {}),\n priceUsd: variant.priceUsd,\n ...(variant.compareAtPriceUsd\n ? { compareAtPriceUsd: variant.compareAtPriceUsd }\n : {}),\n soldOut:\n variant.inventory != null &&\n Number(variant.inventory) <= 0 &&\n product.oversellPolicy !== 'backorder',\n ...(variant.imageUrl ? { imageUrl: variant.imageUrl } : {}),\n ...(variant.sku ? { sku: variant.sku } : {}),\n })),\n ...(product.tags?.length ? { tags: product.tags } : {}),\n ...(product.subscription ? { subscription: product.subscription } : {}),\n ...(product.subscription && product.subscriptionOptional\n ? { subscriptionOptional: true }\n : {}),\n }\n}\n\n/**\n * Public product detail by slug (AGL-292): everything the PDP block\n * needs — variants with per-option prices and stock booleans, never raw\n * counts. Display-only; the charge still prices server-side.\n */\nexport const productHandler: PluginApiHandler = async (req, res) => {\n const hostId = String(req.query.hostId ?? '')\n const slug = String(req.query.slug ?? '')\n if (!hostId || !slug) {\n return res.status(400).json({ error: 'Missing hostId or slug' })\n }\n try {\n const firestore = firebaseAdmin.app().firestore()\n const snapshot = await firestore\n .collection('hosts')\n .doc(hostId)\n .collection('products')\n .where('slug', '==', slug)\n .limit(1)\n .get()\n const docSnapshot = snapshot.docs[0]\n const raw = docSnapshot?.data() as any\n if (!raw || raw.deletedAt || raw.status !== 'active') {\n return res.status(404).json({ error: 'Unknown product' })\n }\n const product = CommerceModel.liftLegacyProduct(raw)\n const detail = toPublicProductDetail(docSnapshot.id, product)\n res.setHeader(\n 'Cache-Control',\n 'public, s-maxage=60, stale-while-revalidate=300',\n )\n return res.status(200).json({ product: detail })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Product unavailable' })\n }\n}\n"],"names":["CommerceModel","firebaseAdmin","toPublicProductDetail","id","product","name","slug","description","type","mediaUrls","imageUrl","options","variants","map","variant","priceUsd","compareAtPriceUsd","soldOut","inventory","Number","oversellPolicy","sku","tags","length","subscription","subscriptionOptional","productHandler","req","res","hostId","String","query","status","json","error","firestore","app","snapshot","collection","doc","where","limit","get","docSnapshot","docs","raw","data","deletedAt","liftLegacyProduct","detail","setHeader","console"],"mappings":"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"}
|