@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,231 @@
|
|
|
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
|
+
*/ /**
|
|
18
|
+
* Discounts engine v2 (AGL-305): code-based AND automatic promotions
|
|
19
|
+
* with scoping, schedules, and usage limits, superseding the AGL-96
|
|
20
|
+
* percent-only coupons (which keep working through the legacy path).
|
|
21
|
+
* One resolver serves cart, checkout, and POS. Docs live at
|
|
22
|
+
* `hosts/{hostId}/discounts`. Pure — callers own I/O and redemption
|
|
23
|
+
* increments.
|
|
24
|
+
*/ import { PROMOTION_EXHAUSTED_MESSAGE, promotionExhausted } from "./commerce-promotions.js";
|
|
25
|
+
/**
|
|
26
|
+
* The document a proposed discount is created as (AGL-2916): switched OFF,
|
|
27
|
+
* with no redemptions, its code in the shape the discounts card stores, and
|
|
28
|
+
* only the value its kind reads. A person switches it on from the discounts
|
|
29
|
+
* card. `null` for a proposal no discount can be made from: a percentage
|
|
30
|
+
* outside 1 to 100, or an amount off of nothing.
|
|
31
|
+
*/ export function switchedOffDiscount(proposal) {
|
|
32
|
+
var _proposal_code, _proposal_minSubtotalCents;
|
|
33
|
+
const code = ((_proposal_code = proposal.code) != null ? _proposal_code : '').trim().toUpperCase().replace(/[^A-Z0-9_-]/g, '');
|
|
34
|
+
const minimum = Math.round(Number((_proposal_minSubtotalCents = proposal.minSubtotalCents) != null ? _proposal_minSubtotalCents : 0));
|
|
35
|
+
const base = _extends({
|
|
36
|
+
name: proposal.name.trim().slice(0, 80),
|
|
37
|
+
code: code || null,
|
|
38
|
+
kind: proposal.kind
|
|
39
|
+
}, minimum > 0 ? {
|
|
40
|
+
minSubtotalCents: minimum
|
|
41
|
+
} : {}, {
|
|
42
|
+
enabled: false,
|
|
43
|
+
redemptions: 0
|
|
44
|
+
});
|
|
45
|
+
if (proposal.kind === 'percent') {
|
|
46
|
+
const pct = Math.round(Number(proposal.valuePct));
|
|
47
|
+
return pct >= 1 && pct <= 100 ? _extends({}, base, {
|
|
48
|
+
valuePct: pct
|
|
49
|
+
}) : null;
|
|
50
|
+
}
|
|
51
|
+
if (proposal.kind === 'fixed') {
|
|
52
|
+
const cents = Math.round(Number(proposal.valueCents));
|
|
53
|
+
return cents > 0 ? _extends({}, base, {
|
|
54
|
+
valueCents: cents
|
|
55
|
+
}) : null;
|
|
56
|
+
}
|
|
57
|
+
return proposal.kind === 'free_shipping' ? base : null;
|
|
58
|
+
}
|
|
59
|
+
function applies(discount, context) {
|
|
60
|
+
var _context_nowMs;
|
|
61
|
+
var _discount_productIds;
|
|
62
|
+
const now = (_context_nowMs = context.nowMs) != null ? _context_nowMs : Date.now();
|
|
63
|
+
if (discount.enabled === false) return 'This discount is disabled';
|
|
64
|
+
if (discount.startAtMs != null && now < discount.startAtMs) {
|
|
65
|
+
return 'This discount has not started yet';
|
|
66
|
+
}
|
|
67
|
+
if (discount.endAtMs != null && now > discount.endAtMs) {
|
|
68
|
+
return 'This discount has expired';
|
|
69
|
+
}
|
|
70
|
+
// Holds count (AGL-2453). This read used to be `redemptions` alone, which is
|
|
71
|
+
// a figure that only moves once the webhook lands — minutes after this
|
|
72
|
+
// question is asked and answered. Every shopper who reached checkout while
|
|
73
|
+
// the counter sat one below the cap passed, so the cap bounded nothing but
|
|
74
|
+
// the paperwork. `promotionExhausted` counts the slots currently held by live
|
|
75
|
+
// checkouts as taken, which is the same question the in-transaction re-read
|
|
76
|
+
// asks below in `promotion-hold.ts` — deliberately the same function, so the
|
|
77
|
+
// pre-filter and the enforcement cannot drift.
|
|
78
|
+
if (promotionExhausted(discount, now)) {
|
|
79
|
+
return PROMOTION_EXHAUSTED_MESSAGE;
|
|
80
|
+
}
|
|
81
|
+
if (discount.minSubtotalCents != null && context.subtotalCents < discount.minSubtotalCents) {
|
|
82
|
+
return `Spend $${(discount.minSubtotalCents / 100).toFixed(2)} to use this`;
|
|
83
|
+
}
|
|
84
|
+
if (((_discount_productIds = discount.productIds) == null ? void 0 : _discount_productIds.length) && !context.productIds.some((id)=>discount.productIds.includes(id))) {
|
|
85
|
+
return 'This discount does not apply to your items';
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The single derivation of what one discount is worth against one subtotal.
|
|
91
|
+
*
|
|
92
|
+
* Exhaustive over {@link DiscountKind} by construction: the `never` binding
|
|
93
|
+
* below fails the BUILD when a kind is added to the union and not handled
|
|
94
|
+
* here, so the next `free_shipping` cannot reach production as a silent zero.
|
|
95
|
+
* The runtime arm underneath it is not redundant — Firestore documents are not
|
|
96
|
+
* typechecked, and a `kind` written by a newer build than the one reading it
|
|
97
|
+
* arrives as an ordinary unrecognised string.
|
|
98
|
+
*/ export function discountBenefit(discount, subtotalCents, lines) {
|
|
99
|
+
const kind = discount.kind;
|
|
100
|
+
if (kind === 'percent' || kind === 'fixed') {
|
|
101
|
+
var _discount_valuePct, _discount_valueCents;
|
|
102
|
+
// WHAT A SCOPE IS WORTH.
|
|
103
|
+
//
|
|
104
|
+
// `applies` already refuses a cart containing NONE of the scoped products,
|
|
105
|
+
// so the scope was never entirely dead — but the amount was still computed
|
|
106
|
+
// against the whole subtotal. A discount scoped to three products
|
|
107
|
+
// therefore discounted the entire basket the moment one of the three was
|
|
108
|
+
// in it: the merchant chose a scope, we charged as though they had not,
|
|
109
|
+
// and the margin went with it.
|
|
110
|
+
//
|
|
111
|
+
// A scoped discount with no line detail REFUSES rather than falling back
|
|
112
|
+
// to the whole subtotal. The fallback is the defect restated, and it would
|
|
113
|
+
// be a second pricing path for one feature split by what the caller
|
|
114
|
+
// happened to pass.
|
|
115
|
+
const scope = discount.productIds;
|
|
116
|
+
let base = subtotalCents;
|
|
117
|
+
if (scope == null ? void 0 : scope.length) {
|
|
118
|
+
if (!lines) {
|
|
119
|
+
return {
|
|
120
|
+
kind: 'none',
|
|
121
|
+
reason: 'this discount covers only some products, and the cart did not ' + 'say what each item costs'
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
base = lines.filter((line)=>scope.includes(line.productId)).reduce((sum, line)=>{
|
|
125
|
+
var _line_amountCents;
|
|
126
|
+
return sum + Math.max(0, Math.round(Number((_line_amountCents = line.amountCents) != null ? _line_amountCents : 0)));
|
|
127
|
+
}, 0);
|
|
128
|
+
}
|
|
129
|
+
const centsOff = kind === 'percent' ? Math.round(base * Math.min(100, Math.max(0, (_discount_valuePct = discount.valuePct) != null ? _discount_valuePct : 0)) / 100) : Math.min(base, Math.max(0, (_discount_valueCents = discount.valueCents) != null ? _discount_valueCents : 0));
|
|
130
|
+
// A discount configured to take nothing off — 0%, or a zero amount — is
|
|
131
|
+
// worth nothing, and saying so is what stops it being applied as a
|
|
132
|
+
// successful reduction of zero.
|
|
133
|
+
return centsOff > 0 ? {
|
|
134
|
+
kind: 'amount',
|
|
135
|
+
centsOff
|
|
136
|
+
} : {
|
|
137
|
+
kind: 'none',
|
|
138
|
+
reason: 'this discount takes nothing off the items in the cart'
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (kind === 'free_shipping') return {
|
|
142
|
+
kind: 'free-shipping'
|
|
143
|
+
};
|
|
144
|
+
// Unreachable while `DiscountKind` is fully handled above; the annotation is
|
|
145
|
+
// what makes adding a kind a compile error rather than a quiet no-op.
|
|
146
|
+
const unhandled = kind;
|
|
147
|
+
return {
|
|
148
|
+
kind: 'none',
|
|
149
|
+
reason: `this store has a discount of a kind this site cannot apply (${String(unhandled)})`
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Best applicable discount: an entered code wins when valid (its
|
|
154
|
+
* failure reason is surfaced); otherwise the largest automatic
|
|
155
|
+
* promotion applies. Discounts never stack — Squarespace/Shopify
|
|
156
|
+
* baseline semantics.
|
|
157
|
+
*/ export function resolveDiscount(discounts, context) {
|
|
158
|
+
var _context_code;
|
|
159
|
+
const entered = (_context_code = context.code) == null ? void 0 : _context_code.trim().toUpperCase();
|
|
160
|
+
if (entered) {
|
|
161
|
+
const coded = discounts.find((discount)=>{
|
|
162
|
+
var _discount_code;
|
|
163
|
+
return ((_discount_code = discount.code) == null ? void 0 : _discount_code.toUpperCase()) === entered;
|
|
164
|
+
});
|
|
165
|
+
if (!coded) return null;
|
|
166
|
+
const problem = applies(coded, context);
|
|
167
|
+
if (problem) {
|
|
168
|
+
return {
|
|
169
|
+
discount: coded,
|
|
170
|
+
discountId: coded.$id,
|
|
171
|
+
discountCents: 0,
|
|
172
|
+
freeShipping: false,
|
|
173
|
+
benefit: {
|
|
174
|
+
kind: 'none',
|
|
175
|
+
reason: problem
|
|
176
|
+
},
|
|
177
|
+
codeProblem: problem
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const benefit = discountBenefit(coded, context.subtotalCents, context.lines);
|
|
181
|
+
// A code the shopper TYPED that turns out to be worth nothing is reported
|
|
182
|
+
// as a code problem, so it leaves through the caller's existing refusal
|
|
183
|
+
// rather than as a successful discount of zero. This is the free-shipping
|
|
184
|
+
// defect's actual exit: the caller's apply-block tests `discountCents > 0`
|
|
185
|
+
// and its invalid-code branch only fires when nothing resolved, so a
|
|
186
|
+
// benefit of `none` used to satisfy neither and the shopper was charged in
|
|
187
|
+
// full without a word.
|
|
188
|
+
if (benefit.kind === 'none') {
|
|
189
|
+
return {
|
|
190
|
+
discount: coded,
|
|
191
|
+
discountId: coded.$id,
|
|
192
|
+
discountCents: 0,
|
|
193
|
+
freeShipping: false,
|
|
194
|
+
benefit,
|
|
195
|
+
codeProblem: benefit.reason
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
discount: coded,
|
|
200
|
+
discountId: coded.$id,
|
|
201
|
+
discountCents: benefit.kind === 'amount' ? benefit.centsOff : 0,
|
|
202
|
+
freeShipping: benefit.kind === 'free-shipping',
|
|
203
|
+
benefit
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
let best = null;
|
|
207
|
+
for (const discount of discounts){
|
|
208
|
+
if (discount.code) continue;
|
|
209
|
+
if (applies(discount, context)) continue;
|
|
210
|
+
const benefit = discountBenefit(discount, context.subtotalCents, context.lines);
|
|
211
|
+
// An automatic promotion worth nothing is passed over rather than reported:
|
|
212
|
+
// nobody asked for it, so there is no shopper to answer and a better one
|
|
213
|
+
// may still be in the list. An entered CODE is the opposite case and
|
|
214
|
+
// refuses above — the shopper made a request and is owed the reason.
|
|
215
|
+
if (benefit.kind === 'none') continue;
|
|
216
|
+
const cents = benefit.kind === 'amount' ? benefit.centsOff : 0;
|
|
217
|
+
const candidate = {
|
|
218
|
+
discount,
|
|
219
|
+
discountId: discount.$id,
|
|
220
|
+
discountCents: cents,
|
|
221
|
+
freeShipping: benefit.kind === 'free-shipping',
|
|
222
|
+
benefit
|
|
223
|
+
};
|
|
224
|
+
if (!best || cents > best.discountCents || candidate.freeShipping && !best.freeShipping && cents === best.discountCents) {
|
|
225
|
+
best = candidate;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return best;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
//# sourceMappingURL=commerce-discounts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-discounts.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 * Discounts engine v2 (AGL-305): code-based AND automatic promotions\n * with scoping, schedules, and usage limits, superseding the AGL-96\n * percent-only coupons (which keep working through the legacy path).\n * One resolver serves cart, checkout, and POS. Docs live at\n * `hosts/{hostId}/discounts`. Pure — callers own I/O and redemption\n * increments.\n */\n\nimport {\n PROMOTION_EXHAUSTED_MESSAGE,\n type PromotionHold,\n promotionExhausted,\n} from './commerce-promotions'\n\nexport type DiscountKind = 'percent' | 'fixed' | 'free_shipping'\n\n/** `hosts/{hostId}/discounts/{id}` doc. */\nexport interface HostDiscount {\n /** Uppercase entry code; absent = automatic (applies to every cart). */\n code?: string\n /** Display name for automatic promotions (\"Summer sale\"). */\n name?: string\n kind: DiscountKind\n /** percent kind: 1-100. */\n valuePct?: number\n /** fixed kind: cents off the items subtotal. */\n valueCents?: number\n /** Only carts at/above this subtotal qualify. */\n minSubtotalCents?: number\n /** Scope: at least one cart line must be one of these products. */\n productIds?: string[]\n maxRedemptions?: number\n redemptions?: number\n /**\n * In-flight claims on the remaining slots (AGL-2453), keyed by checkout\n * attempt. Counted as redeemed by {@link applies} for as long as they stand,\n * which is what stops N concurrent checkouts all passing a cap of one.\n */\n holds?: Record<string, PromotionHold>\n startAtMs?: number\n endAtMs?: number\n enabled?: boolean\n}\n\n/** A discount proposed for a store, before anyone has switched it on (AGL-2916). */\nexport interface ProposedDiscount {\n name: string\n /** The code a shopper types, or `null` for a discount that applies on its own. */\n code: string | null\n kind: DiscountKind\n valuePct: number | null\n valueCents: number | null\n minSubtotalCents: number | null\n}\n\n/** A stored discount as the discounts card writes one: an automatic discount's code is `null`. */\nexport type StoredDiscount = Omit<HostDiscount, 'code'> & { code: string | null }\n\n/**\n * The document a proposed discount is created as (AGL-2916): switched OFF,\n * with no redemptions, its code in the shape the discounts card stores, and\n * only the value its kind reads. A person switches it on from the discounts\n * card. `null` for a proposal no discount can be made from: a percentage\n * outside 1 to 100, or an amount off of nothing.\n */\nexport function switchedOffDiscount(proposal: ProposedDiscount): StoredDiscount | null {\n const code = (proposal.code ?? '').trim().toUpperCase().replace(/[^A-Z0-9_-]/g, '')\n const minimum = Math.round(Number(proposal.minSubtotalCents ?? 0))\n const base: StoredDiscount = {\n name: proposal.name.trim().slice(0, 80),\n code: code || null,\n kind: proposal.kind,\n ...(minimum > 0 ? { minSubtotalCents: minimum } : {}),\n enabled: false,\n redemptions: 0,\n }\n if (proposal.kind === 'percent') {\n const pct = Math.round(Number(proposal.valuePct))\n return pct >= 1 && pct <= 100 ? { ...base, valuePct: pct } : null\n }\n if (proposal.kind === 'fixed') {\n const cents = Math.round(Number(proposal.valueCents))\n return cents > 0 ? { ...base, valueCents: cents } : null\n }\n return proposal.kind === 'free_shipping' ? base : null\n}\n\nexport interface DiscountContext {\n /** Entered code, if any (case-insensitive). */\n code?: string\n subtotalCents: number\n /** Product ids present in the cart (scope checks). */\n productIds: string[]\n /**\n * What each cart line is worth, for pricing a SCOPED discount.\n *\n * Required whenever a discount carries `productIds`, and the reason is that\n * `subtotalCents` alone cannot price one: a scope names some of the cart, so\n * the discount is worth some of the cart. A caller that omits these gets a\n * refusal rather than a whole-cart discount — see `discountBenefit`.\n */\n lines?: readonly { productId: string; amountCents: number }[]\n nowMs?: number\n}\n\nexport interface ResolvedDiscount {\n discount: HostDiscount\n discountId: string\n discountCents: number\n freeShipping: boolean\n /**\n * What this discount confers, as one value. `discountCents` and\n * `freeShipping` are projections of it and are kept because callers read\n * them; the benefit is what a caller should GATE on, because it is the only\n * one of the three that can say \"nothing, and here is why\".\n */\n benefit: DiscountBenefit\n /** Why a specifically-entered code failed; unset when one applied. */\n codeProblem?: string\n}\n\nfunction applies(\n discount: HostDiscount,\n context: DiscountContext,\n): string | null {\n const now = context.nowMs ?? Date.now()\n if (discount.enabled === false) return 'This discount is disabled'\n if (discount.startAtMs != null && now < discount.startAtMs) {\n return 'This discount has not started yet'\n }\n if (discount.endAtMs != null && now > discount.endAtMs) {\n return 'This discount has expired'\n }\n // Holds count (AGL-2453). This read used to be `redemptions` alone, which is\n // a figure that only moves once the webhook lands — minutes after this\n // question is asked and answered. Every shopper who reached checkout while\n // the counter sat one below the cap passed, so the cap bounded nothing but\n // the paperwork. `promotionExhausted` counts the slots currently held by live\n // checkouts as taken, which is the same question the in-transaction re-read\n // asks below in `promotion-hold.ts` — deliberately the same function, so the\n // pre-filter and the enforcement cannot drift.\n if (promotionExhausted(discount, now)) {\n return PROMOTION_EXHAUSTED_MESSAGE\n }\n if (\n discount.minSubtotalCents != null &&\n context.subtotalCents < discount.minSubtotalCents\n ) {\n return `Spend $${(discount.minSubtotalCents / 100).toFixed(2)} to use this`\n }\n if (\n discount.productIds?.length &&\n !context.productIds.some((id) => discount.productIds!.includes(id))\n ) {\n return 'This discount does not apply to your items'\n }\n return null\n}\n\n/**\n * WHAT A DISCOUNT ACTUALLY CONFERS, named rather than implied.\n *\n * The whole point of this type is that \"nothing\" is a STATED outcome with a\n * reason, not the number zero. `valueCents` used to answer a bare `number`,\n * and every kind it did not understand — `free_shipping` included — answered\n * `0`. The cart's apply-block gates on `discountCents > 0`, and the\n * invalid-code 400 beside it only fires when NO discount resolved at all, so a\n * free-shipping code took neither branch: it resolved successfully, conferred\n * nothing, raised no error, and the shopper paid full shipping. A silent zero\n * cannot be told from \"this discount is worth nothing\", which is exactly the\n * distinction a money path has to make.\n *\n * So a caller cannot read a benefit without also seeing the `none` case, and a\n * kind this build does not recognise reports itself instead of underpaying.\n */\nexport type DiscountBenefit =\n /** Cents off the items subtotal. Always > 0 — a zero lands on `none`. */\n | { kind: 'amount'; centsOff: number }\n /** The shopper pays no shipping, whichever rate they pick. */\n | { kind: 'free-shipping' }\n /** Resolvable, but worth nothing. The caller must refuse, never proceed. */\n | { kind: 'none'; reason: string }\n\n/**\n * The single derivation of what one discount is worth against one subtotal.\n *\n * Exhaustive over {@link DiscountKind} by construction: the `never` binding\n * below fails the BUILD when a kind is added to the union and not handled\n * here, so the next `free_shipping` cannot reach production as a silent zero.\n * The runtime arm underneath it is not redundant — Firestore documents are not\n * typechecked, and a `kind` written by a newer build than the one reading it\n * arrives as an ordinary unrecognised string.\n */\nexport function discountBenefit(\n discount: HostDiscount,\n subtotalCents: number,\n lines?: readonly { productId: string; amountCents: number }[],\n): DiscountBenefit {\n const kind = discount.kind\n if (kind === 'percent' || kind === 'fixed') {\n // WHAT A SCOPE IS WORTH.\n //\n // `applies` already refuses a cart containing NONE of the scoped products,\n // so the scope was never entirely dead — but the amount was still computed\n // against the whole subtotal. A discount scoped to three products\n // therefore discounted the entire basket the moment one of the three was\n // in it: the merchant chose a scope, we charged as though they had not,\n // and the margin went with it.\n //\n // A scoped discount with no line detail REFUSES rather than falling back\n // to the whole subtotal. The fallback is the defect restated, and it would\n // be a second pricing path for one feature split by what the caller\n // happened to pass.\n const scope = discount.productIds\n let base = subtotalCents\n if (scope?.length) {\n if (!lines) {\n return {\n kind: 'none',\n reason:\n 'this discount covers only some products, and the cart did not ' +\n 'say what each item costs',\n }\n }\n base = lines\n .filter((line) => scope.includes(line.productId))\n .reduce(\n (sum, line) => sum + Math.max(0, Math.round(Number(line.amountCents ?? 0))),\n 0,\n )\n }\n const centsOff =\n kind === 'percent'\n ? Math.round(\n (base * Math.min(100, Math.max(0, discount.valuePct ?? 0))) / 100,\n )\n : Math.min(base, Math.max(0, discount.valueCents ?? 0))\n // A discount configured to take nothing off — 0%, or a zero amount — is\n // worth nothing, and saying so is what stops it being applied as a\n // successful reduction of zero.\n return centsOff > 0\n ? { kind: 'amount', centsOff }\n : {\n kind: 'none',\n reason: 'this discount takes nothing off the items in the cart',\n }\n }\n if (kind === 'free_shipping') return { kind: 'free-shipping' }\n // Unreachable while `DiscountKind` is fully handled above; the annotation is\n // what makes adding a kind a compile error rather than a quiet no-op.\n const unhandled: never = kind\n return {\n kind: 'none',\n reason: `this store has a discount of a kind this site cannot apply (${String(\n unhandled,\n )})`,\n }\n}\n\n/**\n * Best applicable discount: an entered code wins when valid (its\n * failure reason is surfaced); otherwise the largest automatic\n * promotion applies. Discounts never stack — Squarespace/Shopify\n * baseline semantics.\n */\nexport function resolveDiscount(\n discounts: Array<HostDiscount & { $id: string }>,\n context: DiscountContext,\n): ResolvedDiscount | null {\n const entered = context.code?.trim().toUpperCase()\n if (entered) {\n const coded = discounts.find(\n (discount) => discount.code?.toUpperCase() === entered,\n )\n if (!coded) return null\n const problem = applies(coded, context)\n if (problem) {\n return {\n discount: coded,\n discountId: coded.$id,\n discountCents: 0,\n freeShipping: false,\n benefit: { kind: 'none', reason: problem },\n codeProblem: problem,\n }\n }\n const benefit = discountBenefit(coded, context.subtotalCents, context.lines)\n // A code the shopper TYPED that turns out to be worth nothing is reported\n // as a code problem, so it leaves through the caller's existing refusal\n // rather than as a successful discount of zero. This is the free-shipping\n // defect's actual exit: the caller's apply-block tests `discountCents > 0`\n // and its invalid-code branch only fires when nothing resolved, so a\n // benefit of `none` used to satisfy neither and the shopper was charged in\n // full without a word.\n if (benefit.kind === 'none') {\n return {\n discount: coded,\n discountId: coded.$id,\n discountCents: 0,\n freeShipping: false,\n benefit,\n codeProblem: benefit.reason,\n }\n }\n return {\n discount: coded,\n discountId: coded.$id,\n discountCents: benefit.kind === 'amount' ? benefit.centsOff : 0,\n freeShipping: benefit.kind === 'free-shipping',\n benefit,\n }\n }\n let best: ResolvedDiscount | null = null\n for (const discount of discounts) {\n if (discount.code) continue\n if (applies(discount, context)) continue\n const benefit = discountBenefit(discount, context.subtotalCents, context.lines)\n // An automatic promotion worth nothing is passed over rather than reported:\n // nobody asked for it, so there is no shopper to answer and a better one\n // may still be in the list. An entered CODE is the opposite case and\n // refuses above — the shopper made a request and is owed the reason.\n if (benefit.kind === 'none') continue\n const cents = benefit.kind === 'amount' ? benefit.centsOff : 0\n const candidate: ResolvedDiscount = {\n discount,\n discountId: discount.$id,\n discountCents: cents,\n freeShipping: benefit.kind === 'free-shipping',\n benefit,\n }\n if (\n !best ||\n cents > best.discountCents ||\n (candidate.freeShipping && !best.freeShipping && cents === best.discountCents)\n ) {\n best = candidate\n }\n }\n return best\n}\n"],"names":["PROMOTION_EXHAUSTED_MESSAGE","promotionExhausted","switchedOffDiscount","proposal","code","trim","toUpperCase","replace","minimum","Math","round","Number","minSubtotalCents","base","name","slice","kind","enabled","redemptions","pct","valuePct","cents","valueCents","applies","discount","context","now","nowMs","Date","startAtMs","endAtMs","subtotalCents","toFixed","productIds","length","some","id","includes","discountBenefit","lines","scope","reason","filter","line","productId","reduce","sum","max","amountCents","centsOff","min","unhandled","String","resolveDiscount","discounts","entered","coded","find","problem","discountId","$id","discountCents","freeShipping","benefit","codeProblem","best","candidate"],"mappings":"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"}
|
|
@@ -0,0 +1,124 @@
|
|
|
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 { HostOrder, OrderDispute } from './commerce-orders';
|
|
18
|
+
/** How the merchant should read a dispute at a glance. */
|
|
19
|
+
export type OrderDisputeTone = 'open' | 'lost' | 'won' | 'settled';
|
|
20
|
+
/**
|
|
21
|
+
* A dispute Stripe has not decided yet — the case with a DEADLINE attached.
|
|
22
|
+
*
|
|
23
|
+
* Three independent signals, all of which must say "open", because saying
|
|
24
|
+
* "evidence due" about a settled dispute is the one error with a cost: it
|
|
25
|
+
* sends a merchant to gather documents for a case that is closed. `outcome`
|
|
26
|
+
* and `closedAtMs` are both written by `charge.dispute.closed` and `status` is
|
|
27
|
+
* overwritten with the outcome, so a record that disagrees with itself is
|
|
28
|
+
* malformed — and this resolves a malformed record to "settled", the answer
|
|
29
|
+
* that cannot waste anyone's afternoon.
|
|
30
|
+
*
|
|
31
|
+
* Note `evidenceDueByMs` is NOT one of the signals: Stripe sends
|
|
32
|
+
* `evidence_details.due_by` on the `closed` event too, so a closed dispute
|
|
33
|
+
* still carries a deadline and testing for one would report every settled
|
|
34
|
+
* dispute as open.
|
|
35
|
+
*/
|
|
36
|
+
export declare function isOrderDisputeOpen(dispute?: OrderDispute): boolean;
|
|
37
|
+
/** Whether this order has a dispute still running against it. */
|
|
38
|
+
export declare function orderHasOpenDispute(order: Pick<HostOrder, 'dispute'>): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Whether a refund on this order must be REFUSED because of its dispute
|
|
41
|
+
* (AGL-1809).
|
|
42
|
+
*
|
|
43
|
+
* Not the same question as `orderHasOpenDispute`, and the difference is the
|
|
44
|
+
* whole decision. A formal open dispute (`needs_response`, `under_review`)
|
|
45
|
+
* means the bank has already pulled the disputed funds and Stripe's refund
|
|
46
|
+
* API refuses the charge outright (`charge_disputed` — "The charge you're
|
|
47
|
+
* attempting to refund has been charged back"); a refund alongside it would
|
|
48
|
+
* pay the shopper twice, once by the refund and once by the chargeback. An
|
|
49
|
+
* open INQUIRY is the opposite case: no money has moved, Stripe permits the
|
|
50
|
+
* refund, and its docs name "issuing a full refund" as the way to resolve an
|
|
51
|
+
* inquiry before it escalates — refusing that would forbid the recommended
|
|
52
|
+
* exit. Settled disputes refuse nothing here: a lost one already moved the
|
|
53
|
+
* order to `refunded` (the status guard's job), and a won or closed one left
|
|
54
|
+
* the charge refundable again.
|
|
55
|
+
*
|
|
56
|
+
* An open dispute whose status is unrecognised blocks — on a question of
|
|
57
|
+
* sending money twice, an unknown state fails closed.
|
|
58
|
+
*/
|
|
59
|
+
export declare function orderDisputeBlocksRefund(order: Pick<HostOrder, 'dispute'>): boolean;
|
|
60
|
+
/** What the console renders beside an order's status when a dispute exists. */
|
|
61
|
+
export interface OrderDisputeBadge {
|
|
62
|
+
tone: OrderDisputeTone;
|
|
63
|
+
/** Short chip label, e.g. `Chargeback open`, `Charged back`. */
|
|
64
|
+
label: string;
|
|
65
|
+
/** A sentence for the tooltip: reason, amount, deadline or outcome. */
|
|
66
|
+
detail: string;
|
|
67
|
+
/** The deadline, present ONLY while the dispute is open. */
|
|
68
|
+
evidenceDueByMs?: number;
|
|
69
|
+
/**
|
|
70
|
+
* Whole days until evidence is due, negative once the deadline has passed.
|
|
71
|
+
* Present only alongside `evidenceDueByMs`.
|
|
72
|
+
*/
|
|
73
|
+
evidenceDaysLeft?: number;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The badge for an order's dispute, or `null` when there is none.
|
|
77
|
+
*
|
|
78
|
+
* `nowMs` is a parameter rather than a `Date.now()` call so the deadline
|
|
79
|
+
* arithmetic is testable without freezing the clock.
|
|
80
|
+
*/
|
|
81
|
+
export declare function describeOrderDispute(order: Pick<HostOrder, 'dispute'>, nowMs?: number): OrderDisputeBadge | null;
|
|
82
|
+
/** What the orders list needs to raise the alarm, over a window of orders. */
|
|
83
|
+
export interface OpenDisputeSummary {
|
|
84
|
+
/** Orders with a dispute still running. */
|
|
85
|
+
count: number;
|
|
86
|
+
/** The soonest evidence deadline among them, when any carries one. */
|
|
87
|
+
soonestDueByMs?: number;
|
|
88
|
+
/** Whole days to `soonestDueByMs`; negative once it has passed. */
|
|
89
|
+
soonestDaysLeft?: number;
|
|
90
|
+
/** True when at least one deadline is already behind us. */
|
|
91
|
+
overdue: boolean;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Count the open disputes in a window of orders and find the tightest
|
|
95
|
+
* deadline (AGL-1796).
|
|
96
|
+
*
|
|
97
|
+
* A filter only helps a merchant who already suspects there is something to
|
|
98
|
+
* filter FOR, and an evidence window is days long — so the orders list has to
|
|
99
|
+
* say it unprompted. Computed over every loaded order rather than the visible
|
|
100
|
+
* ones: a merchant filtered to "delivered" still needs to know a dispute is
|
|
101
|
+
* running on a `paid` one.
|
|
102
|
+
*/
|
|
103
|
+
export declare function summariseOpenDisputes(orders: ReadonlyArray<Pick<HostOrder, 'dispute'>>, nowMs?: number): OpenDisputeSummary;
|
|
104
|
+
/** Money off this order, split by the door it left through. */
|
|
105
|
+
export interface OrderReversalSplit {
|
|
106
|
+
/** Cents refunded by a decision the merchant made. */
|
|
107
|
+
refundedCents: number;
|
|
108
|
+
/** Cents taken back by a lost chargeback. */
|
|
109
|
+
chargedBackCents: number;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Split `order.refundedCents` into the part the merchant chose and the part
|
|
113
|
+
* a chargeback took.
|
|
114
|
+
*
|
|
115
|
+
* AGL-1787 writes BOTH into `refundedCents`, deliberately — money reversed is
|
|
116
|
+
* money reversed, and the CSV, the analytics card and the member drawer all
|
|
117
|
+
* want the total. Only the label needs the split, and `dispute.reversedCents`
|
|
118
|
+
* is the piece to subtract.
|
|
119
|
+
*
|
|
120
|
+
* The chargeback share is clamped to the recorded total, so a `reversedCents`
|
|
121
|
+
* larger than `refundedCents` (which can only mean the two fields disagree)
|
|
122
|
+
* yields a zero refund line rather than a negative one.
|
|
123
|
+
*/
|
|
124
|
+
export declare function splitOrderReversal(order: Pick<HostOrder, 'refundedCents' | 'dispute'>): OrderReversalSplit;
|
|
@@ -0,0 +1,225 @@
|
|
|
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
|
+
/**
|
|
18
|
+
* Reading a chargeback back out of an order (AGL-1796).
|
|
19
|
+
*
|
|
20
|
+
* AGL-1787 decided that a lost dispute leaves the order `status: 'refunded'`,
|
|
21
|
+
* because five entitlement gates match that literal and a new status would
|
|
22
|
+
* have escaped all of them. The cost of that decision is paid HERE: a merchant
|
|
23
|
+
* opening the order sees "Refunded", the one word that says they chose it.
|
|
24
|
+
* The distinction rides on the `dispute` record beside the status, and these
|
|
25
|
+
* are the pure readers that turn it back into something a console can say.
|
|
26
|
+
*
|
|
27
|
+
* Kept out of `commerce-orders.ts` on the same principle the model directory
|
|
28
|
+
* already follows — cart, discounts, shipping and tax each own a file — and
|
|
29
|
+
* because this is presentation-shaped: it answers "what should the merchant be
|
|
30
|
+
* told", not "what is true of the order".
|
|
31
|
+
*
|
|
32
|
+
* Dates render as `YYYY-MM-DD` through `toISOString`, which is what the
|
|
33
|
+
* webhook's own timeline entries do. Deterministic, and the console shows
|
|
34
|
+
* these strings beside those entries.
|
|
35
|
+
*/ /**
|
|
36
|
+
* Stripe dispute statuses that mean it is over.
|
|
37
|
+
*
|
|
38
|
+
* `warning_*` statuses belong to an inquiry that has not become a dispute yet;
|
|
39
|
+
* `warning_closed` is the one that ends. The other `warning_*` values
|
|
40
|
+
* (`warning_needs_response`, `warning_under_review`) are OPEN, which is why
|
|
41
|
+
* this is a settled-list rather than a "starts with warning" test.
|
|
42
|
+
*/ const SETTLED_DISPUTE_STATUSES = new Set([
|
|
43
|
+
'won',
|
|
44
|
+
'lost',
|
|
45
|
+
'warning_closed'
|
|
46
|
+
]);
|
|
47
|
+
/**
|
|
48
|
+
* A dispute Stripe has not decided yet — the case with a DEADLINE attached.
|
|
49
|
+
*
|
|
50
|
+
* Three independent signals, all of which must say "open", because saying
|
|
51
|
+
* "evidence due" about a settled dispute is the one error with a cost: it
|
|
52
|
+
* sends a merchant to gather documents for a case that is closed. `outcome`
|
|
53
|
+
* and `closedAtMs` are both written by `charge.dispute.closed` and `status` is
|
|
54
|
+
* overwritten with the outcome, so a record that disagrees with itself is
|
|
55
|
+
* malformed — and this resolves a malformed record to "settled", the answer
|
|
56
|
+
* that cannot waste anyone's afternoon.
|
|
57
|
+
*
|
|
58
|
+
* Note `evidenceDueByMs` is NOT one of the signals: Stripe sends
|
|
59
|
+
* `evidence_details.due_by` on the `closed` event too, so a closed dispute
|
|
60
|
+
* still carries a deadline and testing for one would report every settled
|
|
61
|
+
* dispute as open.
|
|
62
|
+
*/ export function isOrderDisputeOpen(dispute) {
|
|
63
|
+
var _dispute_status;
|
|
64
|
+
if (!dispute) return false;
|
|
65
|
+
if (dispute.outcome) return false;
|
|
66
|
+
if (dispute.closedAtMs) return false;
|
|
67
|
+
return !SETTLED_DISPUTE_STATUSES.has(String((_dispute_status = dispute.status) != null ? _dispute_status : ''));
|
|
68
|
+
}
|
|
69
|
+
/** Whether this order has a dispute still running against it. */ export function orderHasOpenDispute(order) {
|
|
70
|
+
return isOrderDisputeOpen(order == null ? void 0 : order.dispute);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The OPEN statuses that belong to an INQUIRY — the pre-dispute phase, where
|
|
74
|
+
* no chargeback exists yet and no funds have been pulled.
|
|
75
|
+
*
|
|
76
|
+
* An explicit list rather than a "starts with warning" test, for the same
|
|
77
|
+
* reason as `SETTLED_DISPUTE_STATUSES` above: `warning_closed` also starts
|
|
78
|
+
* with `warning`, and an unknown future status must not be mistaken for a
|
|
79
|
+
* refundable inquiry by its prefix.
|
|
80
|
+
*/ const OPEN_INQUIRY_STATUSES = new Set([
|
|
81
|
+
'warning_needs_response',
|
|
82
|
+
'warning_under_review'
|
|
83
|
+
]);
|
|
84
|
+
/**
|
|
85
|
+
* Whether a refund on this order must be REFUSED because of its dispute
|
|
86
|
+
* (AGL-1809).
|
|
87
|
+
*
|
|
88
|
+
* Not the same question as `orderHasOpenDispute`, and the difference is the
|
|
89
|
+
* whole decision. A formal open dispute (`needs_response`, `under_review`)
|
|
90
|
+
* means the bank has already pulled the disputed funds and Stripe's refund
|
|
91
|
+
* API refuses the charge outright (`charge_disputed` — "The charge you're
|
|
92
|
+
* attempting to refund has been charged back"); a refund alongside it would
|
|
93
|
+
* pay the shopper twice, once by the refund and once by the chargeback. An
|
|
94
|
+
* open INQUIRY is the opposite case: no money has moved, Stripe permits the
|
|
95
|
+
* refund, and its docs name "issuing a full refund" as the way to resolve an
|
|
96
|
+
* inquiry before it escalates — refusing that would forbid the recommended
|
|
97
|
+
* exit. Settled disputes refuse nothing here: a lost one already moved the
|
|
98
|
+
* order to `refunded` (the status guard's job), and a won or closed one left
|
|
99
|
+
* the charge refundable again.
|
|
100
|
+
*
|
|
101
|
+
* An open dispute whose status is unrecognised blocks — on a question of
|
|
102
|
+
* sending money twice, an unknown state fails closed.
|
|
103
|
+
*/ export function orderDisputeBlocksRefund(order) {
|
|
104
|
+
var _ref;
|
|
105
|
+
var _order_dispute;
|
|
106
|
+
if (!orderHasOpenDispute(order)) return false;
|
|
107
|
+
return !OPEN_INQUIRY_STATUSES.has(String((_ref = (_order_dispute = order.dispute) == null ? void 0 : _order_dispute.status) != null ? _ref : ''));
|
|
108
|
+
}
|
|
109
|
+
/** `product_not_received` reads as `product not received`. */ function humanReason(reason) {
|
|
110
|
+
return String(reason != null ? reason : '').replace(/_/g, ' ').trim();
|
|
111
|
+
}
|
|
112
|
+
const isoDay = (ms)=>new Date(ms).toISOString().slice(0, 10);
|
|
113
|
+
const usd = (cents)=>`$${(Math.max(0, cents) / 100).toFixed(2)}`;
|
|
114
|
+
/**
|
|
115
|
+
* The badge for an order's dispute, or `null` when there is none.
|
|
116
|
+
*
|
|
117
|
+
* `nowMs` is a parameter rather than a `Date.now()` call so the deadline
|
|
118
|
+
* arithmetic is testable without freezing the clock.
|
|
119
|
+
*/ export function describeOrderDispute(order, nowMs = Date.now()) {
|
|
120
|
+
var _ref, _dispute_outcome;
|
|
121
|
+
const dispute = order == null ? void 0 : order.dispute;
|
|
122
|
+
if (!dispute) return null;
|
|
123
|
+
const reason = humanReason(dispute.reason);
|
|
124
|
+
const because = reason ? ` (${reason})` : '';
|
|
125
|
+
if (isOrderDisputeOpen(dispute)) {
|
|
126
|
+
var _dispute_evidenceDueByMs;
|
|
127
|
+
const dueBy = Number((_dispute_evidenceDueByMs = dispute.evidenceDueByMs) != null ? _dispute_evidenceDueByMs : 0);
|
|
128
|
+
// `Math.ceil` so a deadline eight hours away reads as "1 day left" rather
|
|
129
|
+
// than "0" — rounding a live deadline down to zero reads as expired.
|
|
130
|
+
const daysLeft = dueBy > 0 ? Math.ceil((dueBy - nowMs) / 86400000) : undefined;
|
|
131
|
+
return _extends({
|
|
132
|
+
tone: 'open',
|
|
133
|
+
label: 'Chargeback open',
|
|
134
|
+
detail: `${usd(dispute.amountCents)} disputed${because}, opened ` + `${isoDay(dispute.openedAtMs)}. ` + (dueBy > 0 ? `Evidence is due to Stripe by ${isoDay(dueBy)}` + (daysLeft !== undefined && daysLeft >= 0 ? ` — ${daysLeft} day${daysLeft === 1 ? '' : 's'} left.` : ' — that deadline has passed.') : 'Respond in the Stripe dashboard while the case is open.')
|
|
135
|
+
}, dueBy > 0 ? {
|
|
136
|
+
evidenceDueByMs: dueBy,
|
|
137
|
+
evidenceDaysLeft: daysLeft
|
|
138
|
+
} : {});
|
|
139
|
+
}
|
|
140
|
+
const outcome = String((_ref = (_dispute_outcome = dispute.outcome) != null ? _dispute_outcome : dispute.status) != null ? _ref : '');
|
|
141
|
+
const closed = dispute.closedAtMs ? ` on ${isoDay(dispute.closedAtMs)}` : '';
|
|
142
|
+
if (outcome === 'lost') {
|
|
143
|
+
var _dispute_reversedCents;
|
|
144
|
+
const reversed = Number((_dispute_reversedCents = dispute.reversedCents) != null ? _dispute_reversedCents : 0);
|
|
145
|
+
return {
|
|
146
|
+
tone: 'lost',
|
|
147
|
+
label: 'Charged back',
|
|
148
|
+
// The reversed figure can be LESS than the disputed one: AGL-1787 caps
|
|
149
|
+
// the reversal against what is left, so a partly-refunded order loses
|
|
150
|
+
// only the remainder. Saying only the disputed amount would overstate
|
|
151
|
+
// what left the merchant's books.
|
|
152
|
+
detail: `Dispute lost${closed}. ${usd(reversed)} was taken back` + `${because}` + (reversed < dispute.amountCents ? ` (of ${usd(dispute.amountCents)} disputed — the rest was already refunded).` : '.')
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (outcome === 'won') {
|
|
156
|
+
return {
|
|
157
|
+
tone: 'won',
|
|
158
|
+
label: 'Dispute won',
|
|
159
|
+
detail: `Dispute won${closed}${because} — no money was reversed.`
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
tone: 'settled',
|
|
164
|
+
label: 'Dispute closed',
|
|
165
|
+
detail: `Dispute closed${closed}${because}` + (outcome ? ` as ${humanReason(outcome)}` : '') + ' — no money was reversed.'
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Count the open disputes in a window of orders and find the tightest
|
|
170
|
+
* deadline (AGL-1796).
|
|
171
|
+
*
|
|
172
|
+
* A filter only helps a merchant who already suspects there is something to
|
|
173
|
+
* filter FOR, and an evidence window is days long — so the orders list has to
|
|
174
|
+
* say it unprompted. Computed over every loaded order rather than the visible
|
|
175
|
+
* ones: a merchant filtered to "delivered" still needs to know a dispute is
|
|
176
|
+
* running on a `paid` one.
|
|
177
|
+
*/ export function summariseOpenDisputes(orders, nowMs = Date.now()) {
|
|
178
|
+
let count = 0;
|
|
179
|
+
let soonestDueByMs;
|
|
180
|
+
for (const order of orders != null ? orders : []){
|
|
181
|
+
var _ref;
|
|
182
|
+
var _order_dispute;
|
|
183
|
+
if (!orderHasOpenDispute(order)) continue;
|
|
184
|
+
count += 1;
|
|
185
|
+
const dueBy = Number((_ref = (_order_dispute = order.dispute) == null ? void 0 : _order_dispute.evidenceDueByMs) != null ? _ref : 0);
|
|
186
|
+
if (dueBy > 0 && (soonestDueByMs === undefined || dueBy < soonestDueByMs)) {
|
|
187
|
+
soonestDueByMs = dueBy;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (soonestDueByMs === undefined) return {
|
|
191
|
+
count,
|
|
192
|
+
overdue: false
|
|
193
|
+
};
|
|
194
|
+
const soonestDaysLeft = Math.ceil((soonestDueByMs - nowMs) / 86400000);
|
|
195
|
+
return {
|
|
196
|
+
count,
|
|
197
|
+
soonestDueByMs,
|
|
198
|
+
soonestDaysLeft,
|
|
199
|
+
overdue: soonestDueByMs < nowMs
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Split `order.refundedCents` into the part the merchant chose and the part
|
|
204
|
+
* a chargeback took.
|
|
205
|
+
*
|
|
206
|
+
* AGL-1787 writes BOTH into `refundedCents`, deliberately — money reversed is
|
|
207
|
+
* money reversed, and the CSV, the analytics card and the member drawer all
|
|
208
|
+
* want the total. Only the label needs the split, and `dispute.reversedCents`
|
|
209
|
+
* is the piece to subtract.
|
|
210
|
+
*
|
|
211
|
+
* The chargeback share is clamped to the recorded total, so a `reversedCents`
|
|
212
|
+
* larger than `refundedCents` (which can only mean the two fields disagree)
|
|
213
|
+
* yields a zero refund line rather than a negative one.
|
|
214
|
+
*/ export function splitOrderReversal(order) {
|
|
215
|
+
var _ref, _ref1;
|
|
216
|
+
var _order_dispute;
|
|
217
|
+
const total = Math.max(0, Number((_ref = order == null ? void 0 : order.refundedCents) != null ? _ref : 0) || 0);
|
|
218
|
+
const chargedBackCents = Math.min(total, Math.max(0, Number((_ref1 = order == null ? void 0 : (_order_dispute = order.dispute) == null ? void 0 : _order_dispute.reversedCents) != null ? _ref1 : 0) || 0));
|
|
219
|
+
return {
|
|
220
|
+
refundedCents: total - chargedBackCents,
|
|
221
|
+
chargedBackCents
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
//# sourceMappingURL=commerce-dispute.js.map
|