@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,245 @@
|
|
|
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
|
+
* What a storefront sale's TAX decomposes to (AGL-1904) — the record that
|
|
18
|
+
* makes tax collected on a merchant's sale visible to Aglyn's own return.
|
|
19
|
+
*
|
|
20
|
+
* ## The observation this module exists to record
|
|
21
|
+
*
|
|
22
|
+
* Every storefront checkout — cart, buy-now, subscription — creates its
|
|
23
|
+
* Checkout Session on the PLATFORM account: platform secret key, no
|
|
24
|
+
* `Stripe-Account` header, no `on_behalf_of`, with
|
|
25
|
+
* `payment_intent_data[transfer_data][destination]` pointing at the merchant's
|
|
26
|
+
* connected account. `cart-checkout.ts` and `checkout.ts` then set
|
|
27
|
+
* `automatic_tax[enabled]` when the store's tax mode is `stripe`.
|
|
28
|
+
*
|
|
29
|
+
* That was long suspected to mean Stripe Tax computes the shopper's tax
|
|
30
|
+
* against AGLYN's registrations rather than the merchant's. It was MEASURED on
|
|
31
|
+
* 2026-08-18, in test mode, as a two-arm experiment on exactly that session
|
|
32
|
+
* shape (a $100 line, a Texas shopper address, `transfer_data[destination]`
|
|
33
|
+
* set):
|
|
34
|
+
*
|
|
35
|
+
* - **Platform unregistered, destination connected account registered in
|
|
36
|
+
* TX** → `amount_tax: 0`, one breakdown line with
|
|
37
|
+
* `taxability_reason: "not_collecting"`, `taxable_amount: 0`.
|
|
38
|
+
* - **Platform registered in TX, everything else byte-identical** →
|
|
39
|
+
* `amount_tax: 825`, `taxability_reason: "standard_rated"`,
|
|
40
|
+
* `taxable_amount: 10000`, rate 8.25% "Texas".
|
|
41
|
+
*
|
|
42
|
+
* Both sessions reported `automatic_tax.liability: { type: "self" }` — Stripe's
|
|
43
|
+
* own word for "the account this session was created on is the one liable".
|
|
44
|
+
* `transfer_data[destination]` had no effect on tax in either arm.
|
|
45
|
+
*
|
|
46
|
+
* So on a `mode: 'stripe'` store the tax charged to the shopper is computed on
|
|
47
|
+
* Aglyn's registration footprint, lands in Aglyn's platform balance, and is
|
|
48
|
+
* Aglyn's to remit. The live platform account has held an ACTIVE Texas state
|
|
49
|
+
* sales tax registration since 2026-08-17, so this is not hypothetical.
|
|
50
|
+
*
|
|
51
|
+
* **This module describes that mechanism and records the money.** Aglyn IS a
|
|
52
|
+
* marketplace facilitator for storefront sales, and the published Terms say so
|
|
53
|
+
* at §10.7 (AGL-1956). Facilitator status still attaches by operation of
|
|
54
|
+
* law rather than by our say-so, and §10.7 is deliberately narrow — Aglyn
|
|
55
|
+
* collects and remits **where applicable law gives it an obligation**, not
|
|
56
|
+
* universally — so this module still records the mechanism per sale and does
|
|
57
|
+
* not stamp a per-merchant or per-jurisdiction verdict on any row.
|
|
58
|
+
*
|
|
59
|
+
* ## The two store modes are DIFFERENT FACTS and must never be summed
|
|
60
|
+
*
|
|
61
|
+
* `taxMode` is the whole point of this record, and it is derived from
|
|
62
|
+
* `automatic_tax.enabled` — **never** from whether tax lines are present:
|
|
63
|
+
*
|
|
64
|
+
* - **`stripe-automatic`** — Stripe Tax computed it, against whichever
|
|
65
|
+
* account `automatic_tax.liability` names. When that is `platform`, the
|
|
66
|
+
* money is held under Aglyn's registration.
|
|
67
|
+
* - **`manual`** — the merchant's own configured rate
|
|
68
|
+
* (`checkout.ts` / `pos-order.ts`), applied to the merchant's own declared
|
|
69
|
+
* origin. Aglyn's registrations never enter into it and the amount is not
|
|
70
|
+
* Aglyn's to remit.
|
|
71
|
+
*
|
|
72
|
+
* The trap that makes the `automatic_tax.enabled` discriminator load-bearing:
|
|
73
|
+
* a MANUAL-mode subscription renewal carries a real Stripe Tax Rate (AGL-1751
|
|
74
|
+
* attaches one so the tax recurs), so its invoice arrives with a populated
|
|
75
|
+
* `total_taxes[]` — indistinguishable from a Stripe Tax invoice if you look at
|
|
76
|
+
* the tax lines. Summing tax lines would silently book merchant-configured tax
|
|
77
|
+
* as Aglyn-collected. **Read the flag, not the lines.**
|
|
78
|
+
*
|
|
79
|
+
* ## The taxable base is kept, never inferred
|
|
80
|
+
*
|
|
81
|
+
* `taxable_amount` is what a return reports as "taxable sales". Deriving it as
|
|
82
|
+
* `amount ÷ rate` needs a live read to resolve the rate id and rounds wrong at
|
|
83
|
+
* the edges, so it is stored when Stripe states it and left `null` when it
|
|
84
|
+
* does not — and a row whose base cannot be read is COUNTED OUT LOUD by the
|
|
85
|
+
* return rather than treated as a zero-base sale.
|
|
86
|
+
*
|
|
87
|
+
* A Checkout Session's `total_details.breakdown` is not in the webhook payload
|
|
88
|
+
* and must be re-read with `expand[]=total_details.breakdown`; an invoice
|
|
89
|
+
* carries `total_taxes[]` inline. Both land in the same `taxLines` shape.
|
|
90
|
+
*
|
|
91
|
+
* Pure and total: unreadable input answers `null`, never throws — a recording
|
|
92
|
+
* failure must never 500 a webhook into redelivery.
|
|
93
|
+
*/ import { storefrontTaxMode } from "../model/commerce-tax-decision.js";
|
|
94
|
+
function asCents(value) {
|
|
95
|
+
const cents = Number(value != null ? value : 0);
|
|
96
|
+
return Number.isFinite(cents) ? Math.round(cents) : 0;
|
|
97
|
+
}
|
|
98
|
+
function asStringOrNull(value) {
|
|
99
|
+
return typeof value === 'string' && value ? value : null;
|
|
100
|
+
}
|
|
101
|
+
function asNumberOrNull(value) {
|
|
102
|
+
const parsed = Number(value != null ? value : NaN);
|
|
103
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
104
|
+
}
|
|
105
|
+
/** Seconds-since-epoch to Date, or null for anything unreadable. */ function asDate(value) {
|
|
106
|
+
const seconds = Number(value != null ? value : NaN);
|
|
107
|
+
return Number.isFinite(seconds) && seconds > 0 ? new Date(seconds * 1000) : null;
|
|
108
|
+
}
|
|
109
|
+
function address(source) {
|
|
110
|
+
if (!source || typeof source !== 'object') return null;
|
|
111
|
+
return {
|
|
112
|
+
country: asStringOrNull(source.country),
|
|
113
|
+
state: asStringOrNull(source.state),
|
|
114
|
+
city: asStringOrNull(source.city),
|
|
115
|
+
postalCode: asStringOrNull(source.postal_code)
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The breakdown entries, from either shape.
|
|
120
|
+
*
|
|
121
|
+
* A Checkout Session expands them under `total_details.breakdown.taxes[]`
|
|
122
|
+
* with the rate object inline; an invoice carries `total_taxes[]` (rate under
|
|
123
|
+
* `tax_rate_details.tax_rate`) or the older `total_tax_amounts[]` (`tax_rate`,
|
|
124
|
+
* an id or an expanded object). All three map onto one stored line.
|
|
125
|
+
*/ function taxLines(source) {
|
|
126
|
+
var _source_total_details_breakdown, _source_total_details;
|
|
127
|
+
const entries = Array.isArray(source == null ? void 0 : (_source_total_details = source.total_details) == null ? void 0 : (_source_total_details_breakdown = _source_total_details.breakdown) == null ? void 0 : _source_total_details_breakdown.taxes) ? source.total_details.breakdown.taxes : Array.isArray(source == null ? void 0 : source.total_taxes) ? source.total_taxes : Array.isArray(source == null ? void 0 : source.total_tax_amounts) ? source.total_tax_amounts : [];
|
|
128
|
+
return entries.map((entry)=>{
|
|
129
|
+
var _ref, _ref1, _ref2, _asStringOrNull, _asNumberOrNull;
|
|
130
|
+
var _entry_tax_rate_details;
|
|
131
|
+
const rate = (_ref = (_ref1 = entry == null ? void 0 : entry.rate) != null ? _ref1 : entry == null ? void 0 : (_entry_tax_rate_details = entry.tax_rate_details) == null ? void 0 : _entry_tax_rate_details.tax_rate) != null ? _ref : entry == null ? void 0 : entry.tax_rate;
|
|
132
|
+
const rateObject = rate && typeof rate === 'object' ? rate : null;
|
|
133
|
+
// Distinguish an absent `taxable_amount` from a genuine zero: a zero base
|
|
134
|
+
// is a real fact (`not_collecting`), an absent one means the base is
|
|
135
|
+
// UNKNOWN, and the return has to be able to tell those apart.
|
|
136
|
+
const taxableAmount = Number((_ref2 = entry == null ? void 0 : entry.taxable_amount) != null ? _ref2 : NaN);
|
|
137
|
+
return {
|
|
138
|
+
amountCents: asCents(entry == null ? void 0 : entry.amount),
|
|
139
|
+
taxabilityReason: asStringOrNull(entry == null ? void 0 : entry.taxability_reason),
|
|
140
|
+
taxRateId: (_asStringOrNull = asStringOrNull(rate)) != null ? _asStringOrNull : asStringOrNull(rateObject == null ? void 0 : rateObject.id),
|
|
141
|
+
taxableAmountCents: Number.isFinite(taxableAmount) ? Math.round(taxableAmount) : null,
|
|
142
|
+
jurisdiction: asStringOrNull(rateObject == null ? void 0 : rateObject.jurisdiction),
|
|
143
|
+
rateState: asStringOrNull(rateObject == null ? void 0 : rateObject.state),
|
|
144
|
+
percentage: (_asNumberOrNull = asNumberOrNull(rateObject == null ? void 0 : rateObject.effective_percentage)) != null ? _asNumberOrNull : asNumberOrNull(rateObject == null ? void 0 : rateObject.percentage)
|
|
145
|
+
};
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/** Sum of the stored lines' amounts. */ function sumLines(lines) {
|
|
149
|
+
return lines.reduce((sum, line)=>sum + line.amountCents, 0);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Reads the tax off one paid session or invoice — the SINGLE derivation, and
|
|
153
|
+
* the reason it is its own exported function (AGL-2451).
|
|
154
|
+
*
|
|
155
|
+
* `storefrontTaxRow` builds the `storefrontTaxCollected` record from it, and
|
|
156
|
+
* the four order-writing doors in `billing-webhook.ts` stamp `HostOrder.taxMode`
|
|
157
|
+
* from it, so an order and the row filed for the same Stripe id cannot state
|
|
158
|
+
* different regimes. Re-deriving the mode beside a second reading of the same
|
|
159
|
+
* object is exactly how the two would drift.
|
|
160
|
+
*
|
|
161
|
+
* Pure and total: unreadable input answers `taxMode: 'none'`, never throws.
|
|
162
|
+
*/ export function resolveStorefrontTax(object, manualTaxCents = 0) {
|
|
163
|
+
var _ref, _ref1;
|
|
164
|
+
var _source_automatic_tax, _source_total_details, _source_total_details1;
|
|
165
|
+
const source = object;
|
|
166
|
+
const automatic = (source == null ? void 0 : (_source_automatic_tax = source.automatic_tax) == null ? void 0 : _source_automatic_tax.enabled) === true;
|
|
167
|
+
const lines = automatic ? taxLines(source) : [];
|
|
168
|
+
const manualCents = Math.max(0, asCents(manualTaxCents));
|
|
169
|
+
// The flag, never the lines — see the module note. A manual-mode
|
|
170
|
+
// subscription renewal carries real Stripe tax rates in `total_taxes[]`,
|
|
171
|
+
// and booking those as Aglyn-collected would be the whole defect restated.
|
|
172
|
+
//
|
|
173
|
+
// Read on SESSIONS as well as invoices since AGL-1953: the cart and
|
|
174
|
+
// draft-order paths now carry their manual tax as a real Stripe Tax Rate on
|
|
175
|
+
// the line (the construction that survives a session-level coupon), so a
|
|
176
|
+
// manual session reports a genuine `total_details.amount_tax`. The scalar is
|
|
177
|
+
// the fallback because `total_details.breakdown` is expandable and absent
|
|
178
|
+
// unless the recorder re-read it. Buy-now's `line_items[1]` construction
|
|
179
|
+
// reports 0 in both shapes and still answers from `manualTaxCents` below.
|
|
180
|
+
const manualLineCents = !automatic ? sumLines(taxLines(source)) || asCents((_ref = source == null ? void 0 : (_source_total_details = source.total_details) == null ? void 0 : _source_total_details.amount_tax) != null ? _ref : source == null ? void 0 : source.tax) : 0;
|
|
181
|
+
const taxCents = automatic ? // invoice. Prefer the summed lines when they exist (a computed invoice
|
|
182
|
+
// can send a 0/null scalar beside a populated array — AGL-1811's
|
|
183
|
+
// measurement), and fall back to the scalar shapes otherwise.
|
|
184
|
+
lines.length > 0 ? sumLines(lines) : asCents((_ref1 = source == null ? void 0 : (_source_total_details1 = source.total_details) == null ? void 0 : _source_total_details1.amount_tax) != null ? _ref1 : source == null ? void 0 : source.tax) : Math.max(manualCents, manualLineCents);
|
|
185
|
+
return {
|
|
186
|
+
automatic,
|
|
187
|
+
lines,
|
|
188
|
+
taxCents,
|
|
189
|
+
taxMode: storefrontTaxMode({
|
|
190
|
+
automatic,
|
|
191
|
+
taxCents
|
|
192
|
+
})
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Just the regime, for the order-writing doors (AGL-2451). Delegates, so this
|
|
197
|
+
* is the same answer the filed tax record gives for the same object.
|
|
198
|
+
*/ export function storefrontTaxModeOf(object, manualTaxCents = 0) {
|
|
199
|
+
return resolveStorefrontTax(object, manualTaxCents).taxMode;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Decomposes a paid storefront session or invoice into the stored row, or
|
|
203
|
+
* `null` when there is no host to attribute it to.
|
|
204
|
+
*
|
|
205
|
+
* The caller decides WHICH objects reach this — the commerce webhook
|
|
206
|
+
* self-selects on `metadata.type`, exactly as every other section does.
|
|
207
|
+
*/ export function storefrontTaxRow(object, context) {
|
|
208
|
+
var _ref, _ref1, _ref2, _asStringOrNull, _asStringOrNull1, _asStringOrNull2, _address, _asDate;
|
|
209
|
+
var _source_automatic_tax_liability, _source_automatic_tax, _source_payment_intent_data_transfer_data, _source_payment_intent_data, _source_transfer_data, _source_metadata, _source_subscription_details_metadata, _source_subscription_details, _source_parent_subscription_details_metadata, _source_parent_subscription_details, _source_parent, _source_customer_details, _source_status_transitions;
|
|
210
|
+
const source = object;
|
|
211
|
+
const hostId = String((_ref = context == null ? void 0 : context.hostId) != null ? _ref : '');
|
|
212
|
+
if (!hostId) return null;
|
|
213
|
+
const { automatic, lines, taxCents, taxMode } = resolveStorefrontTax(source, context == null ? void 0 : context.manualTaxCents);
|
|
214
|
+
const liabilityType = asStringOrNull(source == null ? void 0 : (_source_automatic_tax = source.automatic_tax) == null ? void 0 : (_source_automatic_tax_liability = _source_automatic_tax.liability) == null ? void 0 : _source_automatic_tax_liability.type);
|
|
215
|
+
const taxLiability = !automatic ? null : liabilityType === 'account' ? 'connected-account' : // older API versions, where the only account a session could be
|
|
216
|
+
// liable under is the one it was created on — the platform.
|
|
217
|
+
'platform';
|
|
218
|
+
const grossCents = asCents(context.kind === 'invoice' ? source == null ? void 0 : source.amount_paid : source == null ? void 0 : source.amount_total);
|
|
219
|
+
const transferDestination = (_ref1 = source == null ? void 0 : (_source_payment_intent_data = source.payment_intent_data) == null ? void 0 : (_source_payment_intent_data_transfer_data = _source_payment_intent_data.transfer_data) == null ? void 0 : _source_payment_intent_data_transfer_data.destination) != null ? _ref1 : source == null ? void 0 : (_source_transfer_data = source.transfer_data) == null ? void 0 : _source_transfer_data.destination;
|
|
220
|
+
return {
|
|
221
|
+
kind: context.kind,
|
|
222
|
+
hostId,
|
|
223
|
+
metadataType: (_ref2 = (_asStringOrNull = asStringOrNull(source == null ? void 0 : (_source_metadata = source.metadata) == null ? void 0 : _source_metadata.type)) != null ? _asStringOrNull : asStringOrNull(source == null ? void 0 : (_source_subscription_details = source.subscription_details) == null ? void 0 : (_source_subscription_details_metadata = _source_subscription_details.metadata) == null ? void 0 : _source_subscription_details_metadata.type)) != null ? _ref2 : asStringOrNull(source == null ? void 0 : (_source_parent = source.parent) == null ? void 0 : (_source_parent_subscription_details = _source_parent.subscription_details) == null ? void 0 : (_source_parent_subscription_details_metadata = _source_parent_subscription_details.metadata) == null ? void 0 : _source_parent_subscription_details_metadata.type),
|
|
224
|
+
taxMode,
|
|
225
|
+
taxLiability,
|
|
226
|
+
connectedAccountId: (_asStringOrNull1 = asStringOrNull(transferDestination)) != null ? _asStringOrNull1 : asStringOrNull(transferDestination == null ? void 0 : transferDestination.id),
|
|
227
|
+
grossCents,
|
|
228
|
+
taxCents,
|
|
229
|
+
netCents: grossCents - taxCents,
|
|
230
|
+
currency: (_asStringOrNull2 = asStringOrNull(source == null ? void 0 : source.currency)) != null ? _asStringOrNull2 : 'usd',
|
|
231
|
+
customerAddress: (_address = address(source == null ? void 0 : (_source_customer_details = source.customer_details) == null ? void 0 : _source_customer_details.address)) != null ? _address : address(source == null ? void 0 : source.customer_address),
|
|
232
|
+
// Stripe's OWN breakdown, whenever Stripe stated one — never an invented
|
|
233
|
+
// base. Buy-now's manual construction states none (Stripe was never told
|
|
234
|
+
// the line was tax) and answers `[]`, which is the honest answer. The
|
|
235
|
+
// cart, draft-order and subscription-renewal manual paths carry a real
|
|
236
|
+
// Tax Rate, so Stripe DOES state a `taxable_amount` for them, and keeping
|
|
237
|
+
// it gives `merchantManual` a real taxable-sales figure instead of a
|
|
238
|
+
// permanent zero (AGL-1953). It never reaches the Aglyn-liable bucket:
|
|
239
|
+
// the classification above is the flag, not these lines.
|
|
240
|
+
taxLines: automatic ? lines : taxLines(source),
|
|
241
|
+
paidAt: (_asDate = asDate(source == null ? void 0 : (_source_status_transitions = source.status_transitions) == null ? void 0 : _source_status_transitions.paid_at)) != null ? _asDate : asDate(source == null ? void 0 : source.created)
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
//# sourceMappingURL=storefront-tax.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/storefront-tax.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\n/**\n * What a storefront sale's TAX decomposes to (AGL-1904) — the record that\n * makes tax collected on a merchant's sale visible to Aglyn's own return.\n *\n * ## The observation this module exists to record\n *\n * Every storefront checkout — cart, buy-now, subscription — creates its\n * Checkout Session on the PLATFORM account: platform secret key, no\n * `Stripe-Account` header, no `on_behalf_of`, with\n * `payment_intent_data[transfer_data][destination]` pointing at the merchant's\n * connected account. `cart-checkout.ts` and `checkout.ts` then set\n * `automatic_tax[enabled]` when the store's tax mode is `stripe`.\n *\n * That was long suspected to mean Stripe Tax computes the shopper's tax\n * against AGLYN's registrations rather than the merchant's. It was MEASURED on\n * 2026-08-18, in test mode, as a two-arm experiment on exactly that session\n * shape (a $100 line, a Texas shopper address, `transfer_data[destination]`\n * set):\n *\n * - **Platform unregistered, destination connected account registered in\n * TX** → `amount_tax: 0`, one breakdown line with\n * `taxability_reason: \"not_collecting\"`, `taxable_amount: 0`.\n * - **Platform registered in TX, everything else byte-identical** →\n * `amount_tax: 825`, `taxability_reason: \"standard_rated\"`,\n * `taxable_amount: 10000`, rate 8.25% \"Texas\".\n *\n * Both sessions reported `automatic_tax.liability: { type: \"self\" }` — Stripe's\n * own word for \"the account this session was created on is the one liable\".\n * `transfer_data[destination]` had no effect on tax in either arm.\n *\n * So on a `mode: 'stripe'` store the tax charged to the shopper is computed on\n * Aglyn's registration footprint, lands in Aglyn's platform balance, and is\n * Aglyn's to remit. The live platform account has held an ACTIVE Texas state\n * sales tax registration since 2026-08-17, so this is not hypothetical.\n *\n * **This module describes that mechanism and records the money.** Aglyn IS a\n * marketplace facilitator for storefront sales, and the published Terms say so\n * at §10.7 (AGL-1956). Facilitator status still attaches by operation of\n * law rather than by our say-so, and §10.7 is deliberately narrow — Aglyn\n * collects and remits **where applicable law gives it an obligation**, not\n * universally — so this module still records the mechanism per sale and does\n * not stamp a per-merchant or per-jurisdiction verdict on any row.\n *\n * ## The two store modes are DIFFERENT FACTS and must never be summed\n *\n * `taxMode` is the whole point of this record, and it is derived from\n * `automatic_tax.enabled` — **never** from whether tax lines are present:\n *\n * - **`stripe-automatic`** — Stripe Tax computed it, against whichever\n * account `automatic_tax.liability` names. When that is `platform`, the\n * money is held under Aglyn's registration.\n * - **`manual`** — the merchant's own configured rate\n * (`checkout.ts` / `pos-order.ts`), applied to the merchant's own declared\n * origin. Aglyn's registrations never enter into it and the amount is not\n * Aglyn's to remit.\n *\n * The trap that makes the `automatic_tax.enabled` discriminator load-bearing:\n * a MANUAL-mode subscription renewal carries a real Stripe Tax Rate (AGL-1751\n * attaches one so the tax recurs), so its invoice arrives with a populated\n * `total_taxes[]` — indistinguishable from a Stripe Tax invoice if you look at\n * the tax lines. Summing tax lines would silently book merchant-configured tax\n * as Aglyn-collected. **Read the flag, not the lines.**\n *\n * ## The taxable base is kept, never inferred\n *\n * `taxable_amount` is what a return reports as \"taxable sales\". Deriving it as\n * `amount ÷ rate` needs a live read to resolve the rate id and rounds wrong at\n * the edges, so it is stored when Stripe states it and left `null` when it\n * does not — and a row whose base cannot be read is COUNTED OUT LOUD by the\n * return rather than treated as a zero-base sale.\n *\n * A Checkout Session's `total_details.breakdown` is not in the webhook payload\n * and must be re-read with `expand[]=total_details.breakdown`; an invoice\n * carries `total_taxes[]` inline. Both land in the same `taxLines` shape.\n *\n * Pure and total: unreadable input answers `null`, never throws — a recording\n * failure must never 500 a webhook into redelivery.\n */\n\nimport {\n storefrontTaxMode,\n type StorefrontTaxMode,\n} from '../model/commerce-tax-decision'\n\n/**\n * Which machinery produced the tax on this sale. See the module note.\n *\n * The union and its derivation live in the MODEL since AGL-2451, because the\n * merchant's order document now carries the same fact (`HostOrder.taxMode`)\n * and two copies of a rule that must agree are one copy too many. Re-exported\n * here so this module stays the place the mechanism is documented.\n */\nexport type { StorefrontTaxMode }\n\n/**\n * Whose registrations Stripe Tax computed against, from\n * `automatic_tax.liability.type`: `self` (the account the object lives on,\n * i.e. Aglyn's platform) or `account` (a named connected account).\n * Null whenever `taxMode` is not `stripe-automatic`.\n */\nexport type StorefrontTaxLiability = 'platform' | 'connected-account'\n\n/** One entry of the stored tax breakdown. */\nexport interface StorefrontTaxLine {\n amountCents: number\n /** Stripe's stated reason, e.g. `standard_rated`, `not_collecting`. */\n taxabilityReason: string | null\n taxRateId: string | null\n /** Stripe's `taxable_amount`; null when the shape did not carry it. */\n taxableAmountCents: number | null\n /** e.g. `Texas` — from the expanded rate, for the working papers. */\n jurisdiction: string | null\n /** The rate's own state code, which is not the shopper's address. */\n rateState: string | null\n percentage: number | null\n}\n\n/** The per-transaction record `storefrontTaxCollected/{stripeId}` stores. */\nexport interface StorefrontTaxRow {\n /** `session` for one-time sales, `invoice` for subscription cycles. */\n kind: 'session' | 'invoice'\n hostId: string\n /** The commerce path — `commerce-cart`, `commerce-order`, … */\n metadataType: string | null\n taxMode: StorefrontTaxMode\n taxLiability: StorefrontTaxLiability | null\n /** The merchant's connected account, when the object names one. */\n connectedAccountId: string | null\n /** What the shopper actually paid, tax included. */\n grossCents: number\n taxCents: number\n /** Gross minus tax. NOT Aglyn's revenue — most of it is the merchant's. */\n netCents: number\n currency: string\n /** The jurisdiction input — the shopper's address on the Stripe object. */\n customerAddress: {\n country: string | null\n state: string | null\n city: string | null\n postalCode: string | null\n } | null\n taxLines: StorefrontTaxLine[]\n paidAt: Date | null\n}\n\nfunction asCents(value: unknown): number {\n const cents = Number(value ?? 0)\n return Number.isFinite(cents) ? Math.round(cents) : 0\n}\n\nfunction asStringOrNull(value: unknown): string | null {\n return typeof value === 'string' && value ? value : null\n}\n\nfunction asNumberOrNull(value: unknown): number | null {\n const parsed = Number(value ?? NaN)\n return Number.isFinite(parsed) ? parsed : null\n}\n\n/** Seconds-since-epoch to Date, or null for anything unreadable. */\nfunction asDate(value: unknown): Date | null {\n const seconds = Number(value ?? NaN)\n return Number.isFinite(seconds) && seconds > 0\n ? new Date(seconds * 1000)\n : null\n}\n\nfunction address(source: any): StorefrontTaxRow['customerAddress'] {\n if (!source || typeof source !== 'object') return null\n return {\n country: asStringOrNull(source.country),\n state: asStringOrNull(source.state),\n city: asStringOrNull(source.city),\n postalCode: asStringOrNull(source.postal_code),\n }\n}\n\n/**\n * The breakdown entries, from either shape.\n *\n * A Checkout Session expands them under `total_details.breakdown.taxes[]`\n * with the rate object inline; an invoice carries `total_taxes[]` (rate under\n * `tax_rate_details.tax_rate`) or the older `total_tax_amounts[]` (`tax_rate`,\n * an id or an expanded object). All three map onto one stored line.\n */\nfunction taxLines(source: any): StorefrontTaxLine[] {\n const entries: any[] = Array.isArray(source?.total_details?.breakdown?.taxes)\n ? source.total_details.breakdown.taxes\n : Array.isArray(source?.total_taxes)\n ? source.total_taxes\n : Array.isArray(source?.total_tax_amounts)\n ? source.total_tax_amounts\n : []\n return entries.map((entry: any) => {\n const rate = entry?.rate ?? entry?.tax_rate_details?.tax_rate ?? entry?.tax_rate\n const rateObject = rate && typeof rate === 'object' ? rate : null\n // Distinguish an absent `taxable_amount` from a genuine zero: a zero base\n // is a real fact (`not_collecting`), an absent one means the base is\n // UNKNOWN, and the return has to be able to tell those apart.\n const taxableAmount = Number(entry?.taxable_amount ?? NaN)\n return {\n amountCents: asCents(entry?.amount),\n taxabilityReason: asStringOrNull(entry?.taxability_reason),\n taxRateId: asStringOrNull(rate) ?? asStringOrNull(rateObject?.id),\n taxableAmountCents: Number.isFinite(taxableAmount)\n ? Math.round(taxableAmount)\n : null,\n jurisdiction: asStringOrNull(rateObject?.jurisdiction),\n rateState: asStringOrNull(rateObject?.state),\n percentage:\n asNumberOrNull(rateObject?.effective_percentage) ??\n asNumberOrNull(rateObject?.percentage),\n }\n })\n}\n\n/** Sum of the stored lines' amounts. */\nfunction sumLines(lines: readonly StorefrontTaxLine[]): number {\n return lines.reduce((sum, line) => sum + line.amountCents, 0)\n}\n\nexport interface StorefrontTaxContext {\n kind: 'session' | 'invoice'\n hostId: string\n /**\n * The manual-mode tax `checkout.ts` puts over as an ordinary line item.\n * Stripe is never told it is tax, so no Stripe field carries it and the\n * session's own `metadata[taxCents]` is the only witness (AGL-1711).\n */\n manualTaxCents?: number\n}\n\n/** What one Stripe object says about the tax it carried. */\nexport interface ResolvedStorefrontTax {\n /** `automatic_tax.enabled` — the discriminator, never the tax lines. */\n automatic: boolean\n /** Stripe's own breakdown, present only on a `stripe-automatic` sale. */\n lines: StorefrontTaxLine[]\n taxCents: number\n taxMode: StorefrontTaxMode\n}\n\n/**\n * Reads the tax off one paid session or invoice — the SINGLE derivation, and\n * the reason it is its own exported function (AGL-2451).\n *\n * `storefrontTaxRow` builds the `storefrontTaxCollected` record from it, and\n * the four order-writing doors in `billing-webhook.ts` stamp `HostOrder.taxMode`\n * from it, so an order and the row filed for the same Stripe id cannot state\n * different regimes. Re-deriving the mode beside a second reading of the same\n * object is exactly how the two would drift.\n *\n * Pure and total: unreadable input answers `taxMode: 'none'`, never throws.\n */\nexport function resolveStorefrontTax(\n object: unknown,\n manualTaxCents = 0,\n): ResolvedStorefrontTax {\n const source = object as any\n const automatic = source?.automatic_tax?.enabled === true\n const lines = automatic ? taxLines(source) : []\n const manualCents = Math.max(0, asCents(manualTaxCents))\n // The flag, never the lines — see the module note. A manual-mode\n // subscription renewal carries real Stripe tax rates in `total_taxes[]`,\n // and booking those as Aglyn-collected would be the whole defect restated.\n //\n // Read on SESSIONS as well as invoices since AGL-1953: the cart and\n // draft-order paths now carry their manual tax as a real Stripe Tax Rate on\n // the line (the construction that survives a session-level coupon), so a\n // manual session reports a genuine `total_details.amount_tax`. The scalar is\n // the fallback because `total_details.breakdown` is expandable and absent\n // unless the recorder re-read it. Buy-now's `line_items[1]` construction\n // reports 0 in both shapes and still answers from `manualTaxCents` below.\n const manualLineCents = !automatic\n ? sumLines(taxLines(source)) ||\n asCents(source?.total_details?.amount_tax ?? source?.tax)\n : 0\n const taxCents = automatic\n ? // `total_details.amount_tax` on a session, `total_taxes[]` on an\n // invoice. Prefer the summed lines when they exist (a computed invoice\n // can send a 0/null scalar beside a populated array — AGL-1811's\n // measurement), and fall back to the scalar shapes otherwise.\n lines.length > 0\n ? sumLines(lines)\n : asCents(source?.total_details?.amount_tax ?? source?.tax)\n : Math.max(manualCents, manualLineCents)\n return {\n automatic,\n lines,\n taxCents,\n taxMode: storefrontTaxMode({ automatic, taxCents }),\n }\n}\n\n/**\n * Just the regime, for the order-writing doors (AGL-2451). Delegates, so this\n * is the same answer the filed tax record gives for the same object.\n */\nexport function storefrontTaxModeOf(\n object: unknown,\n manualTaxCents = 0,\n): StorefrontTaxMode {\n return resolveStorefrontTax(object, manualTaxCents).taxMode\n}\n\n/**\n * Decomposes a paid storefront session or invoice into the stored row, or\n * `null` when there is no host to attribute it to.\n *\n * The caller decides WHICH objects reach this — the commerce webhook\n * self-selects on `metadata.type`, exactly as every other section does.\n */\nexport function storefrontTaxRow(\n object: unknown,\n context: StorefrontTaxContext,\n): StorefrontTaxRow | null {\n const source = object as any\n const hostId = String(context?.hostId ?? '')\n if (!hostId) return null\n\n const { automatic, lines, taxCents, taxMode } = resolveStorefrontTax(\n source,\n context?.manualTaxCents,\n )\n\n const liabilityType = asStringOrNull(source?.automatic_tax?.liability?.type)\n const taxLiability: StorefrontTaxLiability | null = !automatic\n ? null\n : liabilityType === 'account'\n ? 'connected-account'\n : // `self`, and also an absent liability: Stripe omits the field on\n // older API versions, where the only account a session could be\n // liable under is the one it was created on — the platform.\n 'platform'\n\n const grossCents = asCents(\n context.kind === 'invoice' ? source?.amount_paid : source?.amount_total,\n )\n const transferDestination =\n source?.payment_intent_data?.transfer_data?.destination ??\n source?.transfer_data?.destination\n return {\n kind: context.kind,\n hostId,\n metadataType:\n asStringOrNull(source?.metadata?.type) ??\n asStringOrNull(source?.subscription_details?.metadata?.type) ??\n asStringOrNull(source?.parent?.subscription_details?.metadata?.type),\n taxMode,\n taxLiability,\n connectedAccountId:\n asStringOrNull(transferDestination) ??\n asStringOrNull((transferDestination as any)?.id),\n grossCents,\n taxCents,\n netCents: grossCents - taxCents,\n currency: asStringOrNull(source?.currency) ?? 'usd',\n customerAddress:\n address(source?.customer_details?.address) ??\n address(source?.customer_address),\n // Stripe's OWN breakdown, whenever Stripe stated one — never an invented\n // base. Buy-now's manual construction states none (Stripe was never told\n // the line was tax) and answers `[]`, which is the honest answer. The\n // cart, draft-order and subscription-renewal manual paths carry a real\n // Tax Rate, so Stripe DOES state a `taxable_amount` for them, and keeping\n // it gives `merchantManual` a real taxable-sales figure instead of a\n // permanent zero (AGL-1953). It never reaches the Aglyn-liable bucket:\n // the classification above is the flag, not these lines.\n taxLines: automatic ? lines : taxLines(source),\n paidAt:\n asDate(source?.status_transitions?.paid_at) ?? asDate(source?.created),\n }\n}\n"],"names":["storefrontTaxMode","asCents","value","cents","Number","isFinite","Math","round","asStringOrNull","asNumberOrNull","parsed","NaN","asDate","seconds","Date","address","source","country","state","city","postalCode","postal_code","taxLines","entries","Array","isArray","total_details","breakdown","taxes","total_taxes","total_tax_amounts","map","entry","rate","tax_rate_details","tax_rate","rateObject","taxableAmount","taxable_amount","amountCents","amount","taxabilityReason","taxability_reason","taxRateId","id","taxableAmountCents","jurisdiction","rateState","percentage","effective_percentage","sumLines","lines","reduce","sum","line","resolveStorefrontTax","object","manualTaxCents","automatic","automatic_tax","enabled","manualCents","max","manualLineCents","amount_tax","tax","taxCents","length","taxMode","storefrontTaxModeOf","storefrontTaxRow","context","hostId","String","liabilityType","liability","type","taxLiability","grossCents","kind","amount_paid","amount_total","transferDestination","payment_intent_data","transfer_data","destination","metadataType","metadata","subscription_details","parent","connectedAccountId","netCents","currency","customerAddress","customer_details","customer_address","paidAt","status_transitions","paid_at","created"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6EC,GAED,SACEA,iBAAiB,QAEZ,oCAAgC;AA+DvC,SAASC,QAAQC,KAAc;IAC7B,MAAMC,QAAQC,OAAOF,gBAAAA,QAAS;IAC9B,OAAOE,OAAOC,QAAQ,CAACF,SAASG,KAAKC,KAAK,CAACJ,SAAS;AACtD;AAEA,SAASK,eAAeN,KAAc;IACpC,OAAO,OAAOA,UAAU,YAAYA,QAAQA,QAAQ;AACtD;AAEA,SAASO,eAAeP,KAAc;IACpC,MAAMQ,SAASN,OAAOF,gBAAAA,QAASS;IAC/B,OAAOP,OAAOC,QAAQ,CAACK,UAAUA,SAAS;AAC5C;AAEA,kEAAkE,GAClE,SAASE,OAAOV,KAAc;IAC5B,MAAMW,UAAUT,OAAOF,gBAAAA,QAASS;IAChC,OAAOP,OAAOC,QAAQ,CAACQ,YAAYA,UAAU,IACzC,IAAIC,KAAKD,UAAU,QACnB;AACN;AAEA,SAASE,QAAQC,MAAW;IAC1B,IAAI,CAACA,UAAU,OAAOA,WAAW,UAAU,OAAO;IAClD,OAAO;QACLC,SAAST,eAAeQ,OAAOC,OAAO;QACtCC,OAAOV,eAAeQ,OAAOE,KAAK;QAClCC,MAAMX,eAAeQ,OAAOG,IAAI;QAChCC,YAAYZ,eAAeQ,OAAOK,WAAW;IAC/C;AACF;AAEA;;;;;;;CAOC,GACD,SAASC,SAASN,MAAW;QACUA,iCAAAA;IAArC,MAAMO,UAAiBC,MAAMC,OAAO,CAACT,2BAAAA,wBAAAA,OAAQU,aAAa,sBAArBV,kCAAAA,sBAAuBW,SAAS,qBAAhCX,gCAAkCY,KAAK,IACxEZ,OAAOU,aAAa,CAACC,SAAS,CAACC,KAAK,GACpCJ,MAAMC,OAAO,CAACT,0BAAAA,OAAQa,WAAW,IAC/Bb,OAAOa,WAAW,GAClBL,MAAMC,OAAO,CAACT,0BAAAA,OAAQc,iBAAiB,IACrCd,OAAOc,iBAAiB,GACxB,EAAE;IACV,OAAOP,QAAQQ,GAAG,CAAC,CAACC;YACLA,oBASAxB,iBAOTC;YAhBwBuB;QAA5B,MAAMC,QAAOD,gBAAAA,yBAAAA,MAAOC,IAAI,oBAAID,0BAAAA,0BAAAA,MAAOE,gBAAgB,qBAAvBF,wBAAyBG,QAAQ,YAAhDH,OAAoDA,yBAAAA,MAAOG,QAAQ;QAChF,MAAMC,aAAaH,QAAQ,OAAOA,SAAS,WAAWA,OAAO;QAC7D,0EAA0E;QAC1E,qEAAqE;QACrE,8DAA8D;QAC9D,MAAMI,gBAAgBjC,gBAAO4B,yBAAAA,MAAOM,cAAc,oBAAI3B;QACtD,OAAO;YACL4B,aAAatC,QAAQ+B,yBAAAA,MAAOQ,MAAM;YAClCC,kBAAkBjC,eAAewB,yBAAAA,MAAOU,iBAAiB;YACzDC,SAAS,GAAEnC,kBAAAA,eAAeyB,iBAAfzB,kBAAwBA,eAAe4B,8BAAAA,WAAYQ,EAAE;YAChEC,oBAAoBzC,OAAOC,QAAQ,CAACgC,iBAChC/B,KAAKC,KAAK,CAAC8B,iBACX;YACJS,cAActC,eAAe4B,8BAAAA,WAAYU,YAAY;YACrDC,WAAWvC,eAAe4B,8BAAAA,WAAYlB,KAAK;YAC3C8B,UAAU,GACRvC,kBAAAA,eAAe2B,8BAAAA,WAAYa,oBAAoB,aAA/CxC,kBACAA,eAAe2B,8BAAAA,WAAYY,UAAU;QACzC;IACF;AACF;AAEA,sCAAsC,GACtC,SAASE,SAASC,KAAmC;IACnD,OAAOA,MAAMC,MAAM,CAAC,CAACC,KAAKC,OAASD,MAAMC,KAAKf,WAAW,EAAE;AAC7D;AAuBA;;;;;;;;;;;CAWC,GACD,OAAO,SAASgB,qBACdC,MAAe,EACfC,iBAAiB,CAAC;;QAGAzC,uBAgBNA,uBASEA;IA1Bd,MAAMA,SAASwC;IACf,MAAME,YAAY1C,CAAAA,2BAAAA,wBAAAA,OAAQ2C,aAAa,qBAArB3C,sBAAuB4C,OAAO,MAAK;IACrD,MAAMT,QAAQO,YAAYpC,SAASN,UAAU,EAAE;IAC/C,MAAM6C,cAAcvD,KAAKwD,GAAG,CAAC,GAAG7D,QAAQwD;IACxC,iEAAiE;IACjE,yEAAyE;IACzE,2EAA2E;IAC3E,EAAE;IACF,oEAAoE;IACpE,4EAA4E;IAC5E,yEAAyE;IACzE,6EAA6E;IAC7E,0EAA0E;IAC1E,yEAAyE;IACzE,0EAA0E;IAC1E,MAAMM,kBAAkB,CAACL,YACrBR,SAAS5B,SAASN,YAClBf,gBAAQe,2BAAAA,wBAAAA,OAAQU,aAAa,qBAArBV,sBAAuBgD,UAAU,mBAAIhD,0BAAAA,OAAQiD,GAAG,IACxD;IACJ,MAAMC,WAAWR,YAEb,uEAAuE;IACvE,iEAAiE;IACjE,8DAA8D;IAC9DP,MAAMgB,MAAM,GAAG,IACbjB,SAASC,SACTlD,iBAAQe,2BAAAA,yBAAAA,OAAQU,aAAa,qBAArBV,uBAAuBgD,UAAU,oBAAIhD,0BAAAA,OAAQiD,GAAG,IAC1D3D,KAAKwD,GAAG,CAACD,aAAaE;IAC1B,OAAO;QACLL;QACAP;QACAe;QACAE,SAASpE,kBAAkB;YAAE0D;YAAWQ;QAAS;IACnD;AACF;AAEA;;;CAGC,GACD,OAAO,SAASG,oBACdb,MAAe,EACfC,iBAAiB,CAAC;IAElB,OAAOF,qBAAqBC,QAAQC,gBAAgBW,OAAO;AAC7D;AAEA;;;;;;CAMC,GACD,OAAO,SAASE,iBACdd,MAAe,EACfe,OAA6B;qBA+BzB/D,OAAAA,iBAMAA,kBAKQA,kBAERO,UAYAH;QA7CiCI,iCAAAA,uBAcnCA,2CAAAA,6BACAA,uBAKiBA,kBACAA,uCAAAA,8BACAA,8CAAAA,qCAAAA,gBAWPA,0BAYDA;IAtDX,MAAMA,SAASwC;IACf,MAAMgB,SAASC,eAAOF,2BAAAA,QAASC,MAAM,mBAAI;IACzC,IAAI,CAACA,QAAQ,OAAO;IAEpB,MAAM,EAAEd,SAAS,EAAEP,KAAK,EAAEe,QAAQ,EAAEE,OAAO,EAAE,GAAGb,qBAC9CvC,QACAuD,2BAAAA,QAASd,cAAc;IAGzB,MAAMiB,gBAAgBlE,eAAeQ,2BAAAA,wBAAAA,OAAQ2C,aAAa,sBAArB3C,kCAAAA,sBAAuB2D,SAAS,qBAAhC3D,gCAAkC4D,IAAI;IAC3E,MAAMC,eAA8C,CAACnB,YACjD,OACAgB,kBAAkB,YAChB,sBAEA,gEAAgE;IAChE,4DAA4D;IAC5D;IAEN,MAAMI,aAAa7E,QACjBsE,QAAQQ,IAAI,KAAK,YAAY/D,0BAAAA,OAAQgE,WAAW,GAAGhE,0BAAAA,OAAQiE,YAAY;IAEzE,MAAMC,+BACJlE,2BAAAA,8BAAAA,OAAQmE,mBAAmB,sBAA3BnE,4CAAAA,4BAA6BoE,aAAa,qBAA1CpE,0CAA4CqE,WAAW,oBACvDrE,2BAAAA,wBAAAA,OAAQoE,aAAa,qBAArBpE,sBAAuBqE,WAAW;IACpC,OAAO;QACLN,MAAMR,QAAQQ,IAAI;QAClBP;QACAc,YAAY,GACV9E,SAAAA,kBAAAA,eAAeQ,2BAAAA,mBAAAA,OAAQuE,QAAQ,qBAAhBvE,iBAAkB4D,IAAI,aAArCpE,kBACAA,eAAeQ,2BAAAA,+BAAAA,OAAQwE,oBAAoB,sBAA5BxE,wCAAAA,6BAA8BuE,QAAQ,qBAAtCvE,sCAAwC4D,IAAI,aAD3DpE,QAEAA,eAAeQ,2BAAAA,iBAAAA,OAAQyE,MAAM,sBAAdzE,sCAAAA,eAAgBwE,oBAAoB,sBAApCxE,+CAAAA,oCAAsCuE,QAAQ,qBAA9CvE,6CAAgD4D,IAAI;QACrER;QACAS;QACAa,kBAAkB,GAChBlF,mBAAAA,eAAe0E,gCAAf1E,mBACAA,eAAgB0E,uCAAD,AAACA,oBAA6BtC,EAAE;QACjDkC;QACAZ;QACAyB,UAAUb,aAAaZ;QACvB0B,QAAQ,GAAEpF,mBAAAA,eAAeQ,0BAAAA,OAAQ4E,QAAQ,aAA/BpF,mBAAoC;QAC9CqF,eAAe,GACb9E,WAAAA,QAAQC,2BAAAA,2BAAAA,OAAQ8E,gBAAgB,qBAAxB9E,yBAA0BD,OAAO,aAAzCA,WACAA,QAAQC,0BAAAA,OAAQ+E,gBAAgB;QAClC,yEAAyE;QACzE,yEAAyE;QACzE,sEAAsE;QACtE,uEAAuE;QACvE,0EAA0E;QAC1E,qEAAqE;QACrE,uEAAuE;QACvE,yDAAyD;QACzDzE,UAAUoC,YAAYP,QAAQ7B,SAASN;QACvCgF,MAAM,GACJpF,UAAAA,OAAOI,2BAAAA,6BAAAA,OAAQiF,kBAAkB,qBAA1BjF,2BAA4BkF,OAAO,aAA1CtF,UAA+CA,OAAOI,0BAAAA,OAAQmF,OAAO;IACzE;AACF"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import { type PluginApiHandler } from '@aglyn/aglyn/server';
|
|
18
|
+
/**
|
|
19
|
+
* Gated video streaming (AGL-315). POST (member session) checks the
|
|
20
|
+
* entitlement and mints a short-TTL signed URL; GET with a valid signature
|
|
21
|
+
* redirects to a media URL that itself expires (AGL-2814). The mint step is
|
|
22
|
+
* the server-enforced gate, a shared stream link dies within 15 minutes, and
|
|
23
|
+
* the media URL behind it dies with the viewing session.
|
|
24
|
+
*/
|
|
25
|
+
export declare const streamHandler: PluginApiHandler;
|
|
@@ -0,0 +1,191 @@
|
|
|
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 { MEDIA_CDN_RENDITION_AUTO, MEDIA_CDN_RENDITION_PARAM } from "@aglyn/aglyn/server";
|
|
17
|
+
import * as CommerceModel from "../model/index.js";
|
|
18
|
+
import { firebaseAdmin, GATED_VIDEO_SESSION_TTL_MS } from "@aglyn/tenant-data-admin";
|
|
19
|
+
import { createPaidMediaDeliveryIo, resolvePaidMediaDelivery } from "./paid-media-delivery.js";
|
|
20
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
21
|
+
import { requireActiveMember } from "./membership.js";
|
|
22
|
+
import { checkMemberEntitlement } from "./gate.js";
|
|
23
|
+
import { tokenSigningSecret } from "./download.js";
|
|
24
|
+
const TTL_MS = 15 * 60 * 1000;
|
|
25
|
+
/**
|
|
26
|
+
* Signed with the dedicated, fail-closed `TOKEN_SIGNING_SECRET` (AGL-509).
|
|
27
|
+
* AGL-509 converted the download and supplier tokens but missed this call
|
|
28
|
+
* site (AGL-689): the old `STRIPE_SECRET_KEY ?? 'aglyn'` key left the whole
|
|
29
|
+
* payload — `stream:${hostId}:${productId}:${video}:${exp}`, all public
|
|
30
|
+
* identifiers — forgeable on any deploy without the Stripe key, and the
|
|
31
|
+
* short TTL bought nothing because the forger chooses `exp`.
|
|
32
|
+
*/ function sign(hostId, productId, video, exp) {
|
|
33
|
+
return createHmac('sha256', tokenSigningSecret()).update(`stream:${hostId}:${productId}:${video}:${exp}`).digest('hex').slice(0, 32);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Constant-time signature compare (AGL-512, closed here by AGL-1881).
|
|
37
|
+
*
|
|
38
|
+
* The presented signature was compared with `!==`, which short-circuits on
|
|
39
|
+
* the first differing byte — the exact defect AGL-512 closed in `download.ts`
|
|
40
|
+
* and `supplier-update.ts` and missed here, one directory over. It matters
|
|
41
|
+
* more on this route than it reads: the signature is a truncated HMAC over
|
|
42
|
+
* values the caller already knows (`hostId`, `productId`, `video`, `exp`), so
|
|
43
|
+
* a byte-at-a-time oracle recovers a token for a video the caller was never
|
|
44
|
+
* entitled to, and the 15-minute TTL does not bound the attempt rate.
|
|
45
|
+
*
|
|
46
|
+
* Length is checked first because `timingSafeEqual` THROWS on a length
|
|
47
|
+
* mismatch rather than returning false, and the expected length here is a
|
|
48
|
+
* public constant (32 hex chars) — leaking it costs nothing.
|
|
49
|
+
*/ function signatureMatches(presented, expected) {
|
|
50
|
+
const a = Buffer.from(String(presented != null ? presented : ''), 'utf8');
|
|
51
|
+
const b = Buffer.from(String(expected != null ? expected : ''), 'utf8');
|
|
52
|
+
if (a.length !== b.length) return false;
|
|
53
|
+
return timingSafeEqual(new Uint8Array(a), new Uint8Array(b));
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Gated video streaming (AGL-315). POST (member session) checks the
|
|
57
|
+
* entitlement and mints a short-TTL signed URL; GET with a valid signature
|
|
58
|
+
* redirects to a media URL that itself expires (AGL-2814). The mint step is
|
|
59
|
+
* the server-enforced gate, a shared stream link dies within 15 minutes, and
|
|
60
|
+
* the media URL behind it dies with the viewing session.
|
|
61
|
+
*/ export const streamHandler = async (req, res)=>{
|
|
62
|
+
var _ref, _ref1, _ref2;
|
|
63
|
+
var _req_body, _req_body1, _req_body2;
|
|
64
|
+
const hostId = String((_ref = req.method === 'POST' ? (_req_body = req.body) == null ? void 0 : _req_body.hostId : req.query.hostId) != null ? _ref : '');
|
|
65
|
+
const productId = String((_ref1 = req.method === 'POST' ? (_req_body1 = req.body) == null ? void 0 : _req_body1.productId : req.query.productId) != null ? _ref1 : '');
|
|
66
|
+
const video = Math.max(0, Number((_ref2 = req.method === 'POST' ? (_req_body2 = req.body) == null ? void 0 : _req_body2.video : req.query.video) != null ? _ref2 : 0));
|
|
67
|
+
if (!hostId || !productId) {
|
|
68
|
+
return res.status(400).json({
|
|
69
|
+
error: 'Missing hostId or productId'
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
var _req_query_exp, _req_query_sig, _productSnapshot_data;
|
|
74
|
+
var _product_gatedVideos, _req_headers;
|
|
75
|
+
if (req.method === 'POST') {
|
|
76
|
+
var _auth_member_get;
|
|
77
|
+
// Suspension gate (AGL-550): suspended members (AGL-546) cannot
|
|
78
|
+
// mint stream URLs — 403'd with the session cookie cleared.
|
|
79
|
+
const auth = await requireActiveMember(req, res, hostId, 'Sign in first');
|
|
80
|
+
if (!auth) return;
|
|
81
|
+
const email = String((_auth_member_get = auth.member.get('email')) != null ? _auth_member_get : '');
|
|
82
|
+
const entitled = email && await checkMemberEntitlement(hostId, email, productId);
|
|
83
|
+
if (!entitled) {
|
|
84
|
+
return res.status(403).json({
|
|
85
|
+
error: 'Not entitled'
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const exp = Date.now() + TTL_MS;
|
|
89
|
+
const url = `/api/commerce/stream?hostId=${encodeURIComponent(hostId)}` + `&productId=${encodeURIComponent(productId)}&video=${video}` + `&exp=${exp}&sig=${sign(hostId, productId, video, exp)}`;
|
|
90
|
+
res.setHeader('Cache-Control', 'private, no-store');
|
|
91
|
+
return res.status(200).json({
|
|
92
|
+
url,
|
|
93
|
+
expiresAtMs: exp
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
// GET: signature + expiry gate, then redirect to the media.
|
|
97
|
+
const exp = Number((_req_query_exp = req.query.exp) != null ? _req_query_exp : 0);
|
|
98
|
+
const sig = String((_req_query_sig = req.query.sig) != null ? _req_query_sig : '');
|
|
99
|
+
if (!exp || exp < Date.now() || !signatureMatches(sig, sign(hostId, productId, video, exp))) {
|
|
100
|
+
return res.status(403).send('Link expired — reload the page');
|
|
101
|
+
}
|
|
102
|
+
const app = firebaseAdmin.app();
|
|
103
|
+
const productSnapshot = await app.firestore().collection('hosts').doc(hostId).collection('products').doc(productId).get();
|
|
104
|
+
const product = CommerceModel.liftLegacyProduct((_productSnapshot_data = productSnapshot.data()) != null ? _productSnapshot_data : {});
|
|
105
|
+
const file = (_product_gatedVideos = product.gatedVideos) == null ? void 0 : _product_gatedVideos[video];
|
|
106
|
+
if (!(file == null ? void 0 : file.url)) return res.status(404).send('No video');
|
|
107
|
+
/**
|
|
108
|
+
* The redirect lands on a link that expires (AGL-2814).
|
|
109
|
+
*
|
|
110
|
+
* Everything above only guards the 302. What the 302 points at is what
|
|
111
|
+
* the buyer, and anyone who copies the `Location` out of the buyer's
|
|
112
|
+
* network panel, keeps. So `resolvePaidMediaDelivery` hands back one of
|
|
113
|
+
* three things and never a permanent URL:
|
|
114
|
+
*
|
|
115
|
+
* - a CDN URL signed for {@link GATED_VIDEO_SESSION_TTL_MS}, when the video
|
|
116
|
+
* is a PRIVATE asset in this site's library or its org's. The CDN refuses
|
|
117
|
+
* a private asset on every path without an unexpired signature.
|
|
118
|
+
* - a V4 signed Storage read with the same lifetime, for an object in those
|
|
119
|
+
* libraries that no media document owns. A raw download URL's token lives
|
|
120
|
+
* as long as the object, so it is never the redirect.
|
|
121
|
+
* - an author-typed hotlink, unchanged, because nothing of ours runs there.
|
|
122
|
+
*
|
|
123
|
+
* A video whose asset is still PUBLIC is refused rather than delivered:
|
|
124
|
+
* its URL already works for anyone, so redirecting a buyer to it is the
|
|
125
|
+
* leak. The 409 is what the gated player reports to the viewer, and the
|
|
126
|
+
* log line is what tells the operator which product needs its video made
|
|
127
|
+
* private.
|
|
128
|
+
*
|
|
129
|
+
* `?r=auto` rides inside the signed URL (AGL-2766). The signature covers
|
|
130
|
+
* `(scope, mediaId, exp)` and not the parameter, so it asks the CDN for the
|
|
131
|
+
* best delivery copy of the one asset the signature names and can reach no
|
|
132
|
+
* other. Nothing the caller sends reaches this `Location`: the GET half
|
|
133
|
+
* reads `hostId`, `productId`, `video`, `exp` and `sig`, and builds the
|
|
134
|
+
* target from the product document.
|
|
135
|
+
*
|
|
136
|
+
* With a delivery provider configured and its release flag on for the
|
|
137
|
+
* owning org, a video with a current copy there is redirected straight to
|
|
138
|
+
* the provider's short-lived URL instead (AGL-2824), for the same session
|
|
139
|
+
* and the same rendition the CDN would pick. `Accept` is the one request
|
|
140
|
+
* header that reaches that choice, and only to pick among the asset's own
|
|
141
|
+
* encodings.
|
|
142
|
+
*/ const delivery = await resolvePaidMediaDelivery({
|
|
143
|
+
stored: file.url,
|
|
144
|
+
hostId,
|
|
145
|
+
ttlMs: GATED_VIDEO_SESSION_TTL_MS,
|
|
146
|
+
cdnParams: [
|
|
147
|
+
[
|
|
148
|
+
MEDIA_CDN_RENDITION_PARAM,
|
|
149
|
+
MEDIA_CDN_RENDITION_AUTO
|
|
150
|
+
]
|
|
151
|
+
],
|
|
152
|
+
delivery: {
|
|
153
|
+
accept: (_req_headers = req.headers) == null ? void 0 : _req_headers['accept']
|
|
154
|
+
},
|
|
155
|
+
io: createPaidMediaDeliveryIo({
|
|
156
|
+
firestore: app.firestore(),
|
|
157
|
+
bucket: app.storage().bucket(process.env['NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET'])
|
|
158
|
+
})
|
|
159
|
+
});
|
|
160
|
+
// Per-member and time-boxed, whichever way it goes: no cache may keep the
|
|
161
|
+
// redirect past the session it opens, nor a refusal past the fix.
|
|
162
|
+
res.setHeader('Cache-Control', 'private, no-store');
|
|
163
|
+
if (delivery.ok === false) {
|
|
164
|
+
console.error('[commerce/stream] gated video not delivered', JSON.stringify({
|
|
165
|
+
hostId,
|
|
166
|
+
productId,
|
|
167
|
+
video,
|
|
168
|
+
refusal: delivery.refusal
|
|
169
|
+
}));
|
|
170
|
+
return delivery.refusal === 'not-private' ? res.status(409).send('This video is not ready to play yet') : res.status(404).send('No video');
|
|
171
|
+
}
|
|
172
|
+
/*
|
|
173
|
+
* `Vary: Accept` only when this response chose the rendition (AGL-2824).
|
|
174
|
+
*
|
|
175
|
+
* A signed CDN URL is the same for every client: the `Location` is a
|
|
176
|
+
* function of the product document and the clock, not of the request's
|
|
177
|
+
* `Accept`, and the negotiation happens on the SECOND response, where
|
|
178
|
+
* `serveMediaCdn` declares `Vary` (AGL-2766). A delivery provider's URL is
|
|
179
|
+
* minted after the rendition is picked, so there the `Accept` shaped this
|
|
180
|
+
* `Location`, and saying so is correct even under `no-store`.
|
|
181
|
+
*/ if (delivery.via === 'delivery') res.setHeader('Vary', 'Accept');
|
|
182
|
+
return res.redirect(302, delivery.location);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.error(error);
|
|
185
|
+
return res.status(500).json({
|
|
186
|
+
error: 'Stream unavailable'
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
//# sourceMappingURL=stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/stream.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 MEDIA_CDN_RENDITION_AUTO,\n MEDIA_CDN_RENDITION_PARAM,\n type PluginApiHandler,\n} from '@aglyn/aglyn/server'\nimport * as CommerceModel from '../model'\nimport {\n firebaseAdmin,\n GATED_VIDEO_SESSION_TTL_MS,\n} from '@aglyn/tenant-data-admin'\nimport {\n createPaidMediaDeliveryIo,\n resolvePaidMediaDelivery,\n} from './paid-media-delivery'\nimport { createHmac, timingSafeEqual } from 'crypto'\nimport { requireActiveMember } from './membership'\nimport { checkMemberEntitlement } from './gate'\nimport { tokenSigningSecret } from './download'\n\nconst TTL_MS = 15 * 60 * 1000\n\n/**\n * Signed with the dedicated, fail-closed `TOKEN_SIGNING_SECRET` (AGL-509).\n * AGL-509 converted the download and supplier tokens but missed this call\n * site (AGL-689): the old `STRIPE_SECRET_KEY ?? 'aglyn'` key left the whole\n * payload — `stream:${hostId}:${productId}:${video}:${exp}`, all public\n * identifiers — forgeable on any deploy without the Stripe key, and the\n * short TTL bought nothing because the forger chooses `exp`.\n */\nfunction sign(hostId: string, productId: string, video: number, exp: number) {\n return createHmac('sha256', tokenSigningSecret())\n .update(`stream:${hostId}:${productId}:${video}:${exp}`)\n .digest('hex')\n .slice(0, 32)\n}\n\n/**\n * Constant-time signature compare (AGL-512, closed here by AGL-1881).\n *\n * The presented signature was compared with `!==`, which short-circuits on\n * the first differing byte — the exact defect AGL-512 closed in `download.ts`\n * and `supplier-update.ts` and missed here, one directory over. It matters\n * more on this route than it reads: the signature is a truncated HMAC over\n * values the caller already knows (`hostId`, `productId`, `video`, `exp`), so\n * a byte-at-a-time oracle recovers a token for a video the caller was never\n * entitled to, and the 15-minute TTL does not bound the attempt rate.\n *\n * Length is checked first because `timingSafeEqual` THROWS on a length\n * mismatch rather than returning false, and the expected length here is a\n * public constant (32 hex chars) — leaking it costs nothing.\n */\nfunction signatureMatches(presented: string, expected: string): boolean {\n const a = Buffer.from(String(presented ?? ''), 'utf8')\n const b = Buffer.from(String(expected ?? ''), 'utf8')\n if (a.length !== b.length) return false\n return timingSafeEqual(new Uint8Array(a), new Uint8Array(b))\n}\n\n/**\n * Gated video streaming (AGL-315). POST (member session) checks the\n * entitlement and mints a short-TTL signed URL; GET with a valid signature\n * redirects to a media URL that itself expires (AGL-2814). The mint step is\n * the server-enforced gate, a shared stream link dies within 15 minutes, and\n * the media URL behind it dies with the viewing session.\n */\nexport const streamHandler: PluginApiHandler = async (req, res) => {\n const hostId = String(\n (req.method === 'POST' ? req.body?.hostId : req.query.hostId) ?? '',\n )\n const productId = String(\n (req.method === 'POST' ? req.body?.productId : req.query.productId) ?? '',\n )\n const video = Math.max(\n 0,\n Number(\n (req.method === 'POST' ? req.body?.video : req.query.video) ?? 0,\n ),\n )\n if (!hostId || !productId) {\n return res.status(400).json({ error: 'Missing hostId or productId' })\n }\n\n try {\n if (req.method === 'POST') {\n // Suspension gate (AGL-550): suspended members (AGL-546) cannot\n // mint stream URLs — 403'd with the session cookie cleared.\n const auth = await requireActiveMember(req, res, hostId, 'Sign in first')\n if (!auth) return\n const email = String(auth.member.get('email') ?? '')\n const entitled =\n email && (await checkMemberEntitlement(hostId, email, productId))\n if (!entitled) {\n return res.status(403).json({ error: 'Not entitled' })\n }\n const exp = Date.now() + TTL_MS\n const url =\n `/api/commerce/stream?hostId=${encodeURIComponent(hostId)}` +\n `&productId=${encodeURIComponent(productId)}&video=${video}` +\n `&exp=${exp}&sig=${sign(hostId, productId, video, exp)}`\n res.setHeader('Cache-Control', 'private, no-store')\n return res.status(200).json({ url, expiresAtMs: exp })\n }\n\n // GET: signature + expiry gate, then redirect to the media.\n const exp = Number(req.query.exp ?? 0)\n const sig = String(req.query.sig ?? '')\n if (\n !exp ||\n exp < Date.now() ||\n !signatureMatches(sig, sign(hostId, productId, video, exp))\n ) {\n return res.status(403).send('Link expired — reload the page')\n }\n const app = firebaseAdmin.app()\n const productSnapshot = await app\n .firestore()\n .collection('hosts')\n .doc(hostId)\n .collection('products')\n .doc(productId)\n .get()\n const product = CommerceModel.liftLegacyProduct(\n (productSnapshot.data() as any) ?? {},\n )\n const file = product.gatedVideos?.[video]\n if (!file?.url) return res.status(404).send('No video')\n /**\n * The redirect lands on a link that expires (AGL-2814).\n *\n * Everything above only guards the 302. What the 302 points at is what\n * the buyer, and anyone who copies the `Location` out of the buyer's\n * network panel, keeps. So `resolvePaidMediaDelivery` hands back one of\n * three things and never a permanent URL:\n *\n * - a CDN URL signed for {@link GATED_VIDEO_SESSION_TTL_MS}, when the video\n * is a PRIVATE asset in this site's library or its org's. The CDN refuses\n * a private asset on every path without an unexpired signature.\n * - a V4 signed Storage read with the same lifetime, for an object in those\n * libraries that no media document owns. A raw download URL's token lives\n * as long as the object, so it is never the redirect.\n * - an author-typed hotlink, unchanged, because nothing of ours runs there.\n *\n * A video whose asset is still PUBLIC is refused rather than delivered:\n * its URL already works for anyone, so redirecting a buyer to it is the\n * leak. The 409 is what the gated player reports to the viewer, and the\n * log line is what tells the operator which product needs its video made\n * private.\n *\n * `?r=auto` rides inside the signed URL (AGL-2766). The signature covers\n * `(scope, mediaId, exp)` and not the parameter, so it asks the CDN for the\n * best delivery copy of the one asset the signature names and can reach no\n * other. Nothing the caller sends reaches this `Location`: the GET half\n * reads `hostId`, `productId`, `video`, `exp` and `sig`, and builds the\n * target from the product document.\n *\n * With a delivery provider configured and its release flag on for the\n * owning org, a video with a current copy there is redirected straight to\n * the provider's short-lived URL instead (AGL-2824), for the same session\n * and the same rendition the CDN would pick. `Accept` is the one request\n * header that reaches that choice, and only to pick among the asset's own\n * encodings.\n */\n const delivery = await resolvePaidMediaDelivery({\n stored: file.url,\n hostId,\n ttlMs: GATED_VIDEO_SESSION_TTL_MS,\n cdnParams: [[MEDIA_CDN_RENDITION_PARAM, MEDIA_CDN_RENDITION_AUTO]],\n delivery: { accept: req.headers?.['accept'] },\n io: createPaidMediaDeliveryIo({\n firestore: app.firestore(),\n bucket: app\n .storage()\n .bucket(process.env['NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET']),\n }),\n })\n // Per-member and time-boxed, whichever way it goes: no cache may keep the\n // redirect past the session it opens, nor a refusal past the fix.\n res.setHeader('Cache-Control', 'private, no-store')\n if (delivery.ok === false) {\n console.error(\n '[commerce/stream] gated video not delivered',\n JSON.stringify({ hostId, productId, video, refusal: delivery.refusal }),\n )\n return delivery.refusal === 'not-private'\n ? res.status(409).send('This video is not ready to play yet')\n : res.status(404).send('No video')\n }\n /*\n * `Vary: Accept` only when this response chose the rendition (AGL-2824).\n *\n * A signed CDN URL is the same for every client: the `Location` is a\n * function of the product document and the clock, not of the request's\n * `Accept`, and the negotiation happens on the SECOND response, where\n * `serveMediaCdn` declares `Vary` (AGL-2766). A delivery provider's URL is\n * minted after the rendition is picked, so there the `Accept` shaped this\n * `Location`, and saying so is correct even under `no-store`.\n */\n if (delivery.via === 'delivery') res.setHeader('Vary', 'Accept')\n return res.redirect(302, delivery.location)\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Stream unavailable' })\n }\n}\n"],"names":["MEDIA_CDN_RENDITION_AUTO","MEDIA_CDN_RENDITION_PARAM","CommerceModel","firebaseAdmin","GATED_VIDEO_SESSION_TTL_MS","createPaidMediaDeliveryIo","resolvePaidMediaDelivery","createHmac","timingSafeEqual","requireActiveMember","checkMemberEntitlement","tokenSigningSecret","TTL_MS","sign","hostId","productId","video","exp","update","digest","slice","signatureMatches","presented","expected","a","Buffer","from","String","b","length","Uint8Array","streamHandler","req","res","method","body","query","Math","max","Number","status","json","error","productSnapshot","product","auth","email","member","get","entitled","Date","now","url","encodeURIComponent","setHeader","expiresAtMs","sig","send","app","firestore","collection","doc","liftLegacyProduct","data","file","gatedVideos","delivery","stored","ttlMs","cdnParams","accept","headers","io","bucket","storage","process","env","ok","console","JSON","stringify","refusal","via","redirect","location"],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { PluginApiHandler } from '@aglyn/aglyn/server';
|
|
18
|
+
/**
|
|
19
|
+
* 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
|
+
*/
|
|
24
|
+
export declare const subscriptionPortalHandler: PluginApiHandler;
|