@aglyn/plugins-commerce 1.0.0-beta.230 → 1.0.0-beta.232
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/package.json +13 -13
- package/src/index.d.ts +0 -1
- package/src/index.js +0 -1
- package/src/index.js.map +1 -1
- package/src/lib/barcode/barcode-decode.d.ts +71 -0
- package/src/lib/barcode/barcode-decode.js +498 -0
- package/src/lib/barcode/barcode-decode.js.map +1 -0
- package/src/lib/barcode/barcode-encode.d.ts +44 -0
- package/src/lib/barcode/barcode-encode.js +138 -0
- package/src/lib/barcode/barcode-encode.js.map +1 -0
- package/src/lib/barcode/barcode-scanner.component.d.ts +23 -0
- package/src/lib/barcode/barcode-scanner.component.js +260 -0
- package/src/lib/barcode/barcode-scanner.component.js.map +1 -0
- package/src/lib/barcode/scan-button.component.d.ts +10 -0
- package/src/lib/barcode/scan-button.component.js +62 -0
- package/src/lib/barcode/scan-button.component.js.map +1 -0
- package/src/lib/barcode/scanner-wedge.d.ts +35 -0
- package/src/lib/barcode/scanner-wedge.js +108 -0
- package/src/lib/barcode/scanner-wedge.js.map +1 -0
- package/src/lib/components/account.js +10 -1
- package/src/lib/components/account.js.map +1 -1
- package/src/lib/components/cart-extras.d.ts +45 -0
- package/src/lib/components/cart-extras.js +127 -0
- package/src/lib/components/cart-extras.js.map +1 -0
- package/src/lib/components/cart.js +82 -15
- package/src/lib/components/cart.js.map +1 -1
- package/src/lib/components/checkout-return-notice.d.ts +26 -0
- package/src/lib/components/checkout-return-notice.js +48 -0
- package/src/lib/components/checkout-return-notice.js.map +1 -0
- package/src/lib/components/commerce-console-page.js +124 -0
- package/src/lib/components/commerce-console-page.js.map +1 -1
- package/src/lib/components/commerce-console-sections.d.ts +1 -1
- package/src/lib/components/commerce-console-sections.js +4 -0
- package/src/lib/components/commerce-console-sections.js.map +1 -1
- package/src/lib/components/console/bulk-fulfill.d.ts +44 -0
- package/src/lib/components/console/bulk-fulfill.js +84 -0
- package/src/lib/components/console/bulk-fulfill.js.map +1 -0
- package/src/lib/components/console/catalog-organization-card.component.js +1 -0
- package/src/lib/components/console/catalog-organization-card.component.js.map +1 -1
- package/src/lib/components/console/customer-notifications-card.component.d.ts +19 -0
- package/src/lib/components/console/customer-notifications-card.component.js +149 -0
- package/src/lib/components/console/customer-notifications-card.component.js.map +1 -0
- package/src/lib/components/console/host-orders-card.component.js +88 -6
- package/src/lib/components/console/host-orders-card.component.js.map +1 -1
- package/src/lib/components/console/locations-card.component.js +164 -57
- package/src/lib/components/console/locations-card.component.js.map +1 -1
- package/src/lib/components/console/order-detail-dialog.component.js +209 -61
- package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
- package/src/lib/components/console/order-fulfillment-panel.component.d.ts +48 -0
- package/src/lib/components/console/order-fulfillment-panel.component.js +542 -0
- package/src/lib/components/console/order-fulfillment-panel.component.js.map +1 -0
- package/src/lib/components/console/order-invoice.component.d.ts +33 -0
- package/src/lib/components/console/order-invoice.component.js +81 -0
- package/src/lib/components/console/order-invoice.component.js.map +1 -0
- package/src/lib/components/console/order-receipt-resend.component.d.ts +21 -0
- package/src/lib/components/console/order-receipt-resend.component.js +203 -0
- package/src/lib/components/console/order-receipt-resend.component.js.map +1 -0
- package/src/lib/components/console/order-returns.component.d.ts +36 -0
- package/src/lib/components/console/order-returns.component.js +142 -0
- package/src/lib/components/console/order-returns.component.js.map +1 -0
- package/src/lib/components/console/order-shipping-actions.component.d.ts +54 -0
- package/src/lib/components/console/order-shipping-actions.component.js +149 -0
- package/src/lib/components/console/order-shipping-actions.component.js.map +1 -0
- package/src/lib/components/console/order-webhooks-card.component.d.ts +50 -0
- package/src/lib/components/console/order-webhooks-card.component.js +623 -0
- package/src/lib/components/console/order-webhooks-card.component.js.map +1 -0
- package/src/lib/components/console/order-zones.d.ts +155 -0
- package/src/lib/components/console/order-zones.js +40 -0
- package/src/lib/components/console/order-zones.js.map +1 -0
- package/src/lib/components/console/payment-methods-card.component.d.ts +67 -0
- package/src/lib/components/console/payment-methods-card.component.js +395 -0
- package/src/lib/components/console/payment-methods-card.component.js.map +1 -0
- package/src/lib/components/console/pos/pos-api.d.ts +124 -0
- package/src/lib/components/console/pos/pos-api.js +101 -0
- package/src/lib/components/console/pos/pos-api.js.map +1 -0
- package/src/lib/components/console/pos/pos-cart-panel.component.d.ts +36 -0
- package/src/lib/components/console/pos/pos-cart-panel.component.js +160 -0
- package/src/lib/components/console/pos/pos-cart-panel.component.js.map +1 -0
- package/src/lib/components/console/pos/pos-item-dialog.component.d.ts +39 -0
- package/src/lib/components/console/pos/pos-item-dialog.component.js +352 -0
- package/src/lib/components/console/pos/pos-item-dialog.component.js.map +1 -0
- package/src/lib/components/console/pos/pos-product-grid.component.d.ts +47 -0
- package/src/lib/components/console/pos/pos-product-grid.component.js +272 -0
- package/src/lib/components/console/pos/pos-product-grid.component.js.map +1 -0
- package/src/lib/components/console/pos/pos-receipt-panel.component.d.ts +27 -0
- package/src/lib/components/console/pos/pos-receipt-panel.component.js +282 -0
- package/src/lib/components/console/pos/pos-receipt-panel.component.js.map +1 -0
- package/src/lib/components/console/pos/pos-tender-dialogs.component.d.ts +53 -0
- package/src/lib/components/console/pos/pos-tender-dialogs.component.js +445 -0
- package/src/lib/components/console/pos/pos-tender-dialogs.component.js.map +1 -0
- package/src/lib/components/console/pos/pos-tender-panel.component.d.ts +31 -0
- package/src/lib/components/console/pos/pos-tender-panel.component.js +640 -0
- package/src/lib/components/console/pos/pos-tender-panel.component.js.map +1 -0
- package/src/lib/components/console/pos/use-pos-display.d.ts +43 -0
- package/src/lib/components/console/pos/use-pos-display.js +161 -0
- package/src/lib/components/console/pos/use-pos-display.js.map +1 -0
- package/src/lib/components/console/pos-devices-card.component.d.ts +14 -0
- package/src/lib/components/console/pos-devices-card.component.js +546 -0
- package/src/lib/components/console/pos-devices-card.component.js.map +1 -0
- package/src/lib/components/console/pos-display/brand-screen.d.ts +33 -0
- package/src/lib/components/console/pos-display/brand-screen.js +85 -0
- package/src/lib/components/console/pos-display/brand-screen.js.map +1 -0
- package/src/lib/components/console/pos-display/cart-screen.d.ts +29 -0
- package/src/lib/components/console/pos-display/cart-screen.js +166 -0
- package/src/lib/components/console/pos-display/cart-screen.js.map +1 -0
- package/src/lib/components/console/pos-display/pairing-screen.d.ts +24 -0
- package/src/lib/components/console/pos-display/pairing-screen.js +127 -0
- package/src/lib/components/console/pos-display/pairing-screen.js.map +1 -0
- package/src/lib/components/console/pos-display/pos-display-api.d.ts +90 -0
- package/src/lib/components/console/pos-display/pos-display-api.js +140 -0
- package/src/lib/components/console/pos-display/pos-display-api.js.map +1 -0
- package/src/lib/components/console/pos-display/receipt-screen.d.ts +35 -0
- package/src/lib/components/console/pos-display/receipt-screen.js +200 -0
- package/src/lib/components/console/pos-display/receipt-screen.js.map +1 -0
- package/src/lib/components/console/pos-display/tip-screen.d.ts +36 -0
- package/src/lib/components/console/pos-display/tip-screen.js +211 -0
- package/src/lib/components/console/pos-display/tip-screen.js.map +1 -0
- package/src/lib/components/console/pos-display-page.component.d.ts +16 -0
- package/src/lib/components/console/pos-display-page.component.js +407 -0
- package/src/lib/components/console/pos-display-page.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-customer-lookup.component.d.ts +40 -0
- package/src/lib/components/console/pos-ops/pos-customer-lookup.component.js +289 -0
- package/src/lib/components/console/pos-ops/pos-customer-lookup.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-last-receipt.component.d.ts +34 -0
- package/src/lib/components/console/pos-ops/pos-last-receipt.component.js +61 -0
- package/src/lib/components/console/pos-ops/pos-last-receipt.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.d.ts +38 -0
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.js +129 -0
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-ops-api.d.ts +37 -0
- package/src/lib/components/console/pos-ops/pos-ops-api.js +60 -0
- package/src/lib/components/console/pos-ops/pos-ops-api.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-pin-pad.component.d.ts +48 -0
- package/src/lib/components/console/pos-ops/pos-pin-pad.component.js +220 -0
- package/src/lib/components/console/pos-ops/pos-pin-pad.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-receipt-actions.component.d.ts +63 -0
- package/src/lib/components/console/pos-ops/pos-receipt-actions.component.js +186 -0
- package/src/lib/components/console/pos-ops/pos-receipt-actions.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-receipt.d.ts +39 -0
- package/src/lib/components/console/pos-ops/pos-receipt.js +182 -0
- package/src/lib/components/console/pos-ops/pos-receipt.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-return-dialog.component.d.ts +40 -0
- package/src/lib/components/console/pos-ops/pos-return-dialog.component.js +465 -0
- package/src/lib/components/console/pos-ops/pos-return-dialog.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-shift-history-card.component.d.ts +29 -0
- package/src/lib/components/console/pos-ops/pos-shift-history-card.component.js +253 -0
- package/src/lib/components/console/pos-ops/pos-shift-history-card.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-shift-panel.component.d.ts +34 -0
- package/src/lib/components/console/pos-ops/pos-shift-panel.component.js +494 -0
- package/src/lib/components/console/pos-ops/pos-shift-panel.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-shift-report.component.d.ts +37 -0
- package/src/lib/components/console/pos-ops/pos-shift-report.component.js +102 -0
- package/src/lib/components/console/pos-ops/pos-shift-report.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-staff-pins-card.component.d.ts +30 -0
- package/src/lib/components/console/pos-ops/pos-staff-pins-card.component.js +277 -0
- package/src/lib/components/console/pos-ops/pos-staff-pins-card.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/register-ops.d.ts +28 -0
- package/src/lib/components/console/pos-ops/register-ops.js +28 -0
- package/src/lib/components/console/pos-ops/register-ops.js.map +1 -0
- package/src/lib/components/console/pos-ops/use-pos-cashier.d.ts +46 -0
- package/src/lib/components/console/pos-ops/use-pos-cashier.js +128 -0
- package/src/lib/components/console/pos-ops/use-pos-cashier.js.map +1 -0
- package/src/lib/components/console/pos-ops/use-pos-sale-receipt.d.ts +36 -0
- package/src/lib/components/console/pos-ops/use-pos-sale-receipt.js +64 -0
- package/src/lib/components/console/pos-ops/use-pos-sale-receipt.js.map +1 -0
- package/src/lib/components/console/pos-page.component.d.ts +13 -5
- package/src/lib/components/console/pos-page.component.js +756 -654
- package/src/lib/components/console/pos-page.component.js.map +1 -1
- package/src/lib/components/console/pos-zones.d.ts +23 -0
- package/src/lib/components/console/pos-zones.js +31 -0
- package/src/lib/components/console/pos-zones.js.map +1 -0
- package/src/lib/components/console/product-editor-dialog.component.js +61 -71
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/product-labels-dialog.component.d.ts +13 -0
- package/src/lib/components/console/product-labels-dialog.component.js +197 -0
- package/src/lib/components/console/product-labels-dialog.component.js.map +1 -0
- package/src/lib/components/console/product-register-fields.component.d.ts +19 -0
- package/src/lib/components/console/product-register-fields.component.js +241 -0
- package/src/lib/components/console/product-register-fields.component.js.map +1 -0
- package/src/lib/components/console/product-zones.d.ts +44 -0
- package/src/lib/components/console/product-zones.js.map +1 -1
- package/src/lib/components/console/products-hub-card.component.js +23 -20
- package/src/lib/components/console/products-hub-card.component.js.map +1 -1
- package/src/lib/components/console/register-hardware-card.component.d.ts +33 -0
- package/src/lib/components/console/register-hardware-card.component.js +826 -0
- package/src/lib/components/console/register-hardware-card.component.js.map +1 -0
- package/src/lib/components/console/registers-card.component.js +24 -5
- package/src/lib/components/console/registers-card.component.js.map +1 -1
- package/src/lib/components/console/return-actions.d.ts +38 -0
- package/src/lib/components/console/return-actions.js +76 -0
- package/src/lib/components/console/return-actions.js.map +1 -0
- package/src/lib/components/console/return-detail-dialog.component.d.ts +34 -0
- package/src/lib/components/console/return-detail-dialog.component.js +718 -0
- package/src/lib/components/console/return-detail-dialog.component.js.map +1 -0
- package/src/lib/components/console/return-settings-card.component.d.ts +34 -0
- package/src/lib/components/console/return-settings-card.component.js +195 -0
- package/src/lib/components/console/return-settings-card.component.js.map +1 -0
- package/src/lib/components/console/return-zones.d.ts +87 -0
- package/src/lib/components/console/return-zones.js +31 -0
- package/src/lib/components/console/return-zones.js.map +1 -0
- package/src/lib/components/console/returns-card.component.d.ts +45 -0
- package/src/lib/components/console/returns-card.component.js +159 -0
- package/src/lib/components/console/returns-card.component.js.map +1 -0
- package/src/lib/components/console/shipping-settings-card.component.js +161 -5
- package/src/lib/components/console/shipping-settings-card.component.js.map +1 -1
- package/src/lib/components/console/shippingeasy-card.component.d.ts +59 -0
- package/src/lib/components/console/shippingeasy-card.component.js +378 -0
- package/src/lib/components/console/shippingeasy-card.component.js.map +1 -0
- package/src/lib/components/console/shipstation-card.component.d.ts +35 -0
- package/src/lib/components/console/shipstation-card.component.js +345 -0
- package/src/lib/components/console/shipstation-card.component.js.map +1 -0
- package/src/lib/components/console/start-return-dialog.component.d.ts +40 -0
- package/src/lib/components/console/start-return-dialog.component.js +243 -0
- package/src/lib/components/console/start-return-dialog.component.js.map +1 -0
- package/src/lib/components/console/stock-movements-card.component.js +2 -1
- package/src/lib/components/console/stock-movements-card.component.js.map +1 -1
- package/src/lib/components/console/store-settings-card.component.js +2 -66
- package/src/lib/components/console/store-settings-card.component.js.map +1 -1
- package/src/lib/components/console/store-zones.d.ts +42 -0
- package/src/lib/components/console/store-zones.js +39 -0
- package/src/lib/components/console/store-zones.js.map +1 -0
- package/src/lib/components/console/storefront-tax-summary-card.component.js +1 -0
- package/src/lib/components/console/storefront-tax-summary-card.component.js.map +1 -1
- package/src/lib/components/order-status.d.ts +36 -0
- package/src/lib/components/order-status.js +502 -0
- package/src/lib/components/order-status.js.map +1 -0
- package/src/lib/components/product-detail.js +41 -2
- package/src/lib/components/product-detail.js.map +1 -1
- package/src/lib/components/return-request.d.ts +32 -0
- package/src/lib/components/return-request.js +576 -0
- package/src/lib/components/return-request.js.map +1 -0
- package/src/lib/components/storefront-payment-appearance.d.ts +32 -0
- package/src/lib/components/storefront-payment-appearance.js +114 -0
- package/src/lib/components/storefront-payment-appearance.js.map +1 -0
- package/src/lib/components/storefront-payment-element.d.ts +57 -1
- package/src/lib/components/storefront-payment-element.js +332 -33
- package/src/lib/components/storefront-payment-element.js.map +1 -1
- package/src/lib/constants/order-status.d.ts +22 -0
- package/src/lib/constants/order-status.js +22 -0
- package/src/lib/constants/order-status.js.map +1 -0
- package/src/lib/constants/return-request.d.ts +27 -0
- package/src/lib/constants/return-request.js +32 -0
- package/src/lib/constants/return-request.js.map +1 -0
- package/src/lib/constants/stock-movement-query.js +2 -1
- package/src/lib/constants/stock-movement-query.js.map +1 -1
- package/src/lib/declarations.console-server.js +9 -0
- package/src/lib/declarations.console-server.js.map +1 -1
- package/src/lib/declarations.server.d.ts +2 -1
- package/src/lib/declarations.server.js +68 -1
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/model/buyer-notifications.d.ts +59 -0
- package/src/lib/model/buyer-notifications.js +97 -0
- package/src/lib/model/buyer-notifications.js.map +1 -0
- package/src/lib/model/commerce-orders.d.ts +171 -2
- package/src/lib/model/commerce-orders.js +26 -16
- package/src/lib/model/commerce-orders.js.map +1 -1
- package/src/lib/model/commerce-payment-methods.d.ts +146 -0
- package/src/lib/model/commerce-payment-methods.js +247 -0
- package/src/lib/model/commerce-payment-methods.js.map +1 -0
- package/src/lib/model/commerce-pos-ops.d.ts +437 -0
- package/src/lib/model/commerce-pos-ops.js +721 -0
- package/src/lib/model/commerce-pos-ops.js.map +1 -0
- package/src/lib/model/commerce-pos.d.ts +263 -0
- package/src/lib/model/commerce-pos.js +375 -0
- package/src/lib/model/commerce-pos.js.map +1 -0
- package/src/lib/model/commerce-printers.d.ts +211 -0
- package/src/lib/model/commerce-printers.js +181 -0
- package/src/lib/model/commerce-printers.js.map +1 -0
- package/src/lib/model/commerce-receipt.d.ts +97 -0
- package/src/lib/model/commerce-receipt.js +180 -0
- package/src/lib/model/commerce-receipt.js.map +1 -0
- package/src/lib/model/commerce-returns.d.ts +207 -0
- package/src/lib/model/commerce-returns.js +300 -0
- package/src/lib/model/commerce-returns.js.map +1 -0
- package/src/lib/model/commerce-shipping.d.ts +44 -1
- package/src/lib/model/commerce-shipping.js +53 -9
- package/src/lib/model/commerce-shipping.js.map +1 -1
- package/src/lib/model/commerce-tax-engine.d.ts +90 -0
- package/src/lib/model/commerce-tax-engine.js +129 -0
- package/src/lib/model/commerce-tax-engine.js.map +1 -0
- package/src/lib/model/commerce.d.ts +91 -2
- package/src/lib/model/commerce.js +89 -1
- package/src/lib/model/commerce.js.map +1 -1
- package/src/lib/model/index.d.ts +16 -0
- package/src/lib/model/index.js +16 -0
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/model/order-events.d.ts +154 -0
- package/src/lib/model/order-events.js +153 -0
- package/src/lib/model/order-events.js.map +1 -0
- package/src/lib/model/order-fulfillment.d.ts +117 -0
- package/src/lib/model/order-fulfillment.js +217 -0
- package/src/lib/model/order-fulfillment.js.map +1 -0
- package/src/lib/model/order-history.d.ts +58 -0
- package/src/lib/model/order-history.js +22 -0
- package/src/lib/model/order-history.js.map +1 -0
- package/src/lib/model/order-list-fields.d.ts +9 -1
- package/src/lib/model/order-list-fields.js +7 -1
- package/src/lib/model/order-list-fields.js.map +1 -1
- package/src/lib/model/order-shipping-export.d.ts +91 -0
- package/src/lib/model/order-shipping-export.js +192 -0
- package/src/lib/model/order-shipping-export.js.map +1 -0
- package/src/lib/model/order-status-view.d.ts +103 -0
- package/src/lib/model/order-status-view.js +156 -0
- package/src/lib/model/order-status-view.js.map +1 -0
- package/src/lib/model/order-webhooks.d.ts +142 -0
- package/src/lib/model/order-webhooks.js +91 -0
- package/src/lib/model/order-webhooks.js.map +1 -0
- package/src/lib/model/product-channel.d.ts +56 -0
- package/src/lib/model/product-channel.js +86 -0
- package/src/lib/model/product-channel.js.map +1 -0
- package/src/lib/model/product-modifiers.d.ts +80 -0
- package/src/lib/model/product-modifiers.js +140 -0
- package/src/lib/model/product-modifiers.js.map +1 -0
- package/src/lib/model/product-write.d.ts +186 -0
- package/src/lib/model/product-write.js +116 -0
- package/src/lib/model/product-write.js.map +1 -0
- package/src/lib/model/shippingeasy.d.ts +94 -0
- package/src/lib/model/shippingeasy.js +267 -0
- package/src/lib/model/shippingeasy.js.map +1 -0
- package/src/lib/model/shipstation.d.ts +125 -0
- package/src/lib/model/shipstation.js +399 -0
- package/src/lib/model/shipstation.js.map +1 -0
- package/src/lib/model/smart-collection-reads.d.ts +32 -0
- package/src/lib/model/smart-collection-reads.js +63 -0
- package/src/lib/model/smart-collection-reads.js.map +1 -0
- package/src/lib/model/tracking-url.d.ts +74 -0
- package/src/lib/model/tracking-url.js +159 -0
- package/src/lib/model/tracking-url.js.map +1 -0
- package/src/lib/plugin-config.d.ts +13 -0
- package/src/lib/plugin-config.js +77 -5
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +26 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/pos-ops-config.d.ts +39 -0
- package/src/lib/pos-ops-config.js +88 -0
- package/src/lib/pos-ops-config.js.map +1 -0
- package/src/lib/printing/print-document.d.ts +106 -0
- package/src/lib/printing/print-document.js +464 -0
- package/src/lib/printing/print-document.js.map +1 -0
- package/src/lib/printing/product-labels.d.ts +58 -0
- package/src/lib/printing/product-labels.js +116 -0
- package/src/lib/printing/product-labels.js.map +1 -0
- package/src/lib/printing/render-epson.d.ts +57 -0
- package/src/lib/printing/render-epson.js +135 -0
- package/src/lib/printing/render-epson.js.map +1 -0
- package/src/lib/printing/render-star.d.ts +45 -0
- package/src/lib/printing/render-star.js +100 -0
- package/src/lib/printing/render-star.js.map +1 -0
- package/src/lib/printing/render-text.d.ts +25 -0
- package/src/lib/printing/render-text.js +53 -0
- package/src/lib/printing/render-text.js.map +1 -0
- package/src/lib/server/api-v1/openapi.js +27 -4
- package/src/lib/server/api-v1/openapi.js.map +1 -1
- package/src/lib/server/api-v1/order-view.d.ts +67 -0
- package/src/lib/server/api-v1/order-view.js +120 -0
- package/src/lib/server/api-v1/order-view.js.map +1 -0
- package/src/lib/server/api-v1/orders-and-products.js +58 -65
- package/src/lib/server/api-v1/orders-and-products.js.map +1 -1
- package/src/lib/server/billing-webhook.js +135 -28
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/cancel-order.js +29 -0
- package/src/lib/server/cancel-order.js.map +1 -1
- package/src/lib/server/carrier-rates-availability.d.ts +26 -0
- package/src/lib/server/carrier-rates-availability.js +76 -0
- package/src/lib/server/carrier-rates-availability.js.map +1 -0
- package/src/lib/server/carrier-shipping.d.ts +84 -0
- package/src/lib/server/carrier-shipping.js +135 -0
- package/src/lib/server/carrier-shipping.js.map +1 -0
- package/src/lib/server/cart-checkout.d.ts +2 -0
- package/src/lib/server/cart-checkout.js +226 -22
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/channel-order-updates.d.ts +50 -0
- package/src/lib/server/channel-order-updates.js +290 -0
- package/src/lib/server/channel-order-updates.js.map +1 -0
- package/src/lib/server/channel-orders.d.ts +85 -0
- package/src/lib/server/channel-orders.js +543 -0
- package/src/lib/server/channel-orders.js.map +1 -0
- package/src/lib/server/checkout-extras.d.ts +64 -0
- package/src/lib/server/checkout-extras.js +157 -0
- package/src/lib/server/checkout-extras.js.map +1 -0
- package/src/lib/server/{feed.d.ts → checkout-status.d.ts} +2 -7
- package/src/lib/server/checkout-status.js +111 -0
- package/src/lib/server/checkout-status.js.map +1 -0
- package/src/lib/server/checkout.js +86 -9
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/draft-order.js +34 -5
- package/src/lib/server/draft-order.js.map +1 -1
- package/src/lib/server/fulfill-order.d.ts +127 -10
- package/src/lib/server/fulfill-order.js +451 -49
- package/src/lib/server/fulfill-order.js.map +1 -1
- package/src/lib/server/legacy-feed.d.ts +28 -0
- package/src/lib/server/legacy-feed.js +52 -0
- package/src/lib/server/legacy-feed.js.map +1 -0
- package/src/lib/server/native-checkout.d.ts +34 -15
- package/src/lib/server/native-checkout.js +32 -17
- package/src/lib/server/native-checkout.js.map +1 -1
- package/src/lib/server/order-event-host-emit.d.ts +23 -0
- package/src/lib/server/order-event-host-emit.js +30 -0
- package/src/lib/server/order-event-host-emit.js.map +1 -0
- package/src/lib/server/order-event-triggers.d.ts +37 -0
- package/src/lib/server/order-event-triggers.js +119 -0
- package/src/lib/server/order-event-triggers.js.map +1 -0
- package/src/lib/server/order-events.d.ts +62 -0
- package/src/lib/server/order-events.js +109 -0
- package/src/lib/server/order-events.js.map +1 -0
- package/src/lib/server/order-history.d.ts +23 -0
- package/src/lib/server/order-history.js +65 -0
- package/src/lib/server/order-history.js.map +1 -0
- package/src/lib/server/order-notifications.d.ts +185 -0
- package/src/lib/server/order-notifications.js +567 -0
- package/src/lib/server/order-notifications.js.map +1 -0
- package/src/lib/server/order-receipt-send.d.ts +39 -0
- package/src/lib/server/order-receipt-send.js +151 -0
- package/src/lib/server/order-receipt-send.js.map +1 -0
- package/src/lib/server/order-status-token.d.ts +31 -0
- package/src/lib/server/order-status-token.js +75 -0
- package/src/lib/server/order-status-token.js.map +1 -0
- package/src/lib/server/order-status.d.ts +58 -0
- package/src/lib/server/order-status.js +141 -0
- package/src/lib/server/order-status.js.map +1 -0
- package/src/lib/server/order-webhooks.d.ts +108 -0
- package/src/lib/server/order-webhooks.js +622 -0
- package/src/lib/server/order-webhooks.js.map +1 -0
- package/src/lib/server/orders-shipping-prepare.d.ts +26 -0
- package/src/lib/server/orders-shipping-prepare.js +121 -0
- package/src/lib/server/orders-shipping-prepare.js.map +1 -0
- package/src/lib/server/payment-method-domains.d.ts +209 -0
- package/src/lib/server/payment-method-domains.js +444 -0
- package/src/lib/server/payment-method-domains.js.map +1 -0
- package/src/lib/server/payment-methods.d.ts +77 -0
- package/src/lib/server/payment-methods.js +284 -0
- package/src/lib/server/payment-methods.js.map +1 -0
- package/src/lib/server/pos-auth.d.ts +50 -0
- package/src/lib/server/pos-auth.js +121 -0
- package/src/lib/server/pos-auth.js.map +1 -0
- package/src/lib/server/pos-customer.d.ts +49 -0
- package/src/lib/server/pos-customer.js +257 -0
- package/src/lib/server/pos-customer.js.map +1 -0
- package/src/lib/server/pos-display.d.ts +57 -0
- package/src/lib/server/pos-display.js +419 -0
- package/src/lib/server/pos-display.js.map +1 -0
- package/src/lib/server/pos-fee-month.d.ts +34 -0
- package/src/lib/server/pos-fee-month.js +36 -0
- package/src/lib/server/pos-fee-month.js.map +1 -0
- package/src/lib/server/pos-ops-gate.d.ts +161 -0
- package/src/lib/server/pos-ops-gate.js +295 -0
- package/src/lib/server/pos-ops-gate.js.map +1 -0
- package/src/lib/server/pos-ops-routes.d.ts +22 -0
- package/src/lib/server/pos-ops-routes.js +36 -0
- package/src/lib/server/pos-ops-routes.js.map +1 -0
- package/src/lib/server/pos-order.d.ts +1 -18
- package/src/lib/server/pos-order.js +222 -42
- package/src/lib/server/pos-order.js.map +1 -1
- package/src/lib/server/pos-payment.d.ts +39 -0
- package/src/lib/server/pos-payment.js +922 -0
- package/src/lib/server/pos-payment.js.map +1 -0
- package/src/lib/server/pos-print.d.ts +82 -0
- package/src/lib/server/pos-print.js +193 -0
- package/src/lib/server/pos-print.js.map +1 -0
- package/src/lib/server/pos-readers.d.ts +27 -0
- package/src/lib/server/pos-readers.js +276 -0
- package/src/lib/server/pos-readers.js.map +1 -0
- package/src/lib/server/pos-refund-authority.d.ts +60 -0
- package/src/lib/server/pos-refund-authority.js +72 -0
- package/src/lib/server/pos-refund-authority.js.map +1 -0
- package/src/lib/server/pos-return.d.ts +58 -0
- package/src/lib/server/pos-return.js +891 -0
- package/src/lib/server/pos-return.js.map +1 -0
- package/src/lib/server/pos-sale-stamp.d.ts +66 -0
- package/src/lib/server/pos-sale-stamp.js +84 -0
- package/src/lib/server/pos-sale-stamp.js.map +1 -0
- package/src/lib/server/pos-sale.d.ts +170 -0
- package/src/lib/server/pos-sale.js +461 -0
- package/src/lib/server/pos-sale.js.map +1 -0
- package/src/lib/server/pos-shift.d.ts +45 -0
- package/src/lib/server/pos-shift.js +484 -0
- package/src/lib/server/pos-shift.js.map +1 -0
- package/src/lib/server/pos-staff-pin.d.ts +26 -0
- package/src/lib/server/pos-staff-pin.js +339 -0
- package/src/lib/server/pos-staff-pin.js.map +1 -0
- package/src/lib/server/pos-stripe.d.ts +91 -0
- package/src/lib/server/pos-stripe.js +129 -0
- package/src/lib/server/pos-stripe.js.map +1 -0
- package/src/lib/server/pos-terminal-connection-token.d.ts +48 -0
- package/src/lib/server/pos-terminal-connection-token.js +166 -0
- package/src/lib/server/pos-terminal-connection-token.js.map +1 -0
- package/src/lib/server/pos-terminal.d.ts +218 -0
- package/src/lib/server/pos-terminal.js +1014 -0
- package/src/lib/server/pos-terminal.js.map +1 -0
- package/src/lib/server/print-queue.d.ts +124 -0
- package/src/lib/server/print-queue.js +301 -0
- package/src/lib/server/print-queue.js.map +1 -0
- package/src/lib/server/printer-poll.d.ts +75 -0
- package/src/lib/server/printer-poll.js +325 -0
- package/src/lib/server/printer-poll.js.map +1 -0
- package/src/lib/server/printer-secret.d.ts +39 -0
- package/src/lib/server/printer-secret.js +56 -0
- package/src/lib/server/printer-secret.js.map +1 -0
- package/src/lib/server/printers.d.ts +114 -0
- package/src/lib/server/printers.js +546 -0
- package/src/lib/server/printers.js.map +1 -0
- package/src/lib/server/product-catalog.d.ts +42 -0
- package/src/lib/server/product-catalog.js +309 -0
- package/src/lib/server/product-catalog.js.map +1 -0
- package/src/lib/server/product-drafts.d.ts +5 -1
- package/src/lib/server/product-drafts.js +3 -3
- package/src/lib/server/product-drafts.js.map +1 -1
- package/src/lib/server/product-writer.d.ts +19 -0
- package/src/lib/server/product-writer.js +261 -0
- package/src/lib/server/product-writer.js.map +1 -0
- package/src/lib/server/products-write.d.ts +61 -0
- package/src/lib/server/products-write.js +375 -0
- package/src/lib/server/products-write.js.map +1 -0
- package/src/lib/server/refund.js +110 -45
- package/src/lib/server/refund.js.map +1 -1
- package/src/lib/server/reserve.js +2 -0
- package/src/lib/server/reserve.js.map +1 -1
- package/src/lib/server/return-status-action.d.ts +37 -0
- package/src/lib/server/return-status-action.js +57 -0
- package/src/lib/server/return-status-action.js.map +1 -0
- package/src/lib/server/returns.d.ts +119 -0
- package/src/lib/server/returns.js +938 -0
- package/src/lib/server/returns.js.map +1 -0
- package/src/lib/server/shipment-records.d.ts +36 -0
- package/src/lib/server/shipment-records.js +289 -0
- package/src/lib/server/shipment-records.js.map +1 -0
- package/src/lib/server/shipping-connector-gates.d.ts +40 -0
- package/src/lib/server/shipping-connector-gates.js +97 -0
- package/src/lib/server/shipping-connector-gates.js.map +1 -0
- package/src/lib/server/shipping-connectors.d.ts +29 -0
- package/src/lib/server/shipping-connectors.js +236 -0
- package/src/lib/server/shipping-connectors.js.map +1 -0
- package/src/lib/server/shippingeasy.d.ts +238 -0
- package/src/lib/server/shippingeasy.js +787 -0
- package/src/lib/server/shippingeasy.js.map +1 -0
- package/src/lib/server/shipstation.d.ts +129 -0
- package/src/lib/server/shipstation.js +483 -0
- package/src/lib/server/shipstation.js.map +1 -0
- package/src/lib/server/site-page-resolver.d.ts +5 -0
- package/src/lib/server/site-page-resolver.js +156 -1
- package/src/lib/server/site-page-resolver.js.map +1 -1
- package/src/lib/server/stock-levels.d.ts +21 -0
- package/src/lib/server/stock-levels.js +263 -0
- package/src/lib/server/stock-levels.js.map +1 -0
- package/src/lib/server/stripe-refund.d.ts +47 -0
- package/src/lib/server/stripe-refund.js +87 -0
- package/src/lib/server/stripe-refund.js.map +1 -0
- package/src/lib/server/supplier-update.js +46 -5
- package/src/lib/server/supplier-update.js.map +1 -1
- package/src/lib/server/tax-engine-quote.d.ts +47 -0
- package/src/lib/server/tax-engine-quote.js +105 -0
- package/src/lib/server/tax-engine-quote.js.map +1 -0
- package/src/lib/server.js +143 -2
- package/src/lib/server.js.map +1 -1
- package/src/lib/site.js +12 -0
- package/src/lib/site.js.map +1 -1
- package/src/lib/subprocessors.d.ts +26 -0
- package/src/lib/subprocessors.js +50 -0
- package/src/lib/subprocessors.js.map +1 -0
- package/src/lib/tenant-emails.js +461 -3
- package/src/lib/tenant-emails.js.map +1 -1
- package/src/lib/testing/fake-firestore.d.ts +45 -0
- package/src/lib/testing/fake-firestore.js +189 -0
- package/src/lib/testing/fake-firestore.js.map +1 -0
- package/src/lib/testing/memory-firestore.d.ts +136 -0
- package/src/lib/testing/memory-firestore.js +282 -0
- package/src/lib/testing/memory-firestore.js.map +1 -0
- package/src/lib/testing/pos-ops-harness.d.ts +50 -0
- package/src/lib/testing/pos-ops-harness.js +184 -0
- package/src/lib/testing/pos-ops-harness.js.map +1 -0
- package/src/lib/testing/pos-ops-memory-firestore.d.ts +55 -0
- package/src/lib/testing/pos-ops-memory-firestore.js +376 -0
- package/src/lib/testing/pos-ops-memory-firestore.js.map +1 -0
- package/src/lib/transfer/products.server.d.ts +5 -0
- package/src/lib/transfer/products.server.js +2 -2
- package/src/lib/transfer/products.server.js.map +1 -1
- package/src/lib/transfer/records-transfer.d.ts +11 -1
- package/src/lib/transfer/records-transfer.js +127 -5
- package/src/lib/transfer/records-transfer.js.map +1 -1
- package/src/lib/transfer/records.server.d.ts +9 -2
- package/src/lib/transfer/records.server.js +58 -7
- package/src/lib/transfer/records.server.js.map +1 -1
- package/src/lib/transfer/register-transfer-resources.js +40 -1
- package/src/lib/transfer/register-transfer-resources.js.map +1 -1
- package/src/lib/transfer/register-transfer-ui.js +4 -1
- package/src/lib/transfer/register-transfer-ui.js.map +1 -1
- package/src/lib/transfer/shipping-presets.d.ts +23 -0
- package/src/lib/transfer/shipping-presets.js +156 -0
- package/src/lib/transfer/shipping-presets.js.map +1 -0
- package/src/lib/transfer/tracking-transfer.d.ts +101 -0
- package/src/lib/transfer/tracking-transfer.js +546 -0
- package/src/lib/transfer/tracking-transfer.js.map +1 -0
- package/src/lib/transfer/tracking.server.d.ts +37 -0
- package/src/lib/transfer/tracking.server.js +393 -0
- package/src/lib/transfer/tracking.server.js.map +1 -0
- package/src/lib/transfer/transfer-keys.d.ts +2 -1
- package/src/lib/transfer/transfer-keys.js +6 -2
- package/src/lib/transfer/transfer-keys.js.map +1 -1
- package/src/lib/utils/code128.d.ts +37 -0
- package/src/lib/utils/code128.js +199 -0
- package/src/lib/utils/code128.js.map +1 -0
- package/src/lib/utils/order-invoice.d.ts +55 -0
- package/src/lib/utils/order-invoice.js +127 -0
- package/src/lib/utils/order-invoice.js.map +1 -0
- package/src/lib/utils/use-checkout-return-notice.d.ts +34 -0
- package/src/lib/utils/use-checkout-return-notice.js +78 -0
- package/src/lib/utils/use-checkout-return-notice.js.map +1 -0
- package/src/lib/server/feed.js +0 -69
- package/src/lib/server/feed.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-orders.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 * Orders model v1 (AGL-283): line items snapshot the product/variant at\n * purchase time (renames and price changes never rewrite history), a\n * small status machine gates transitions, and totals are integer cents.\n * Docs live at `hosts/{hostId}/orders/{id}`; the Stripe webhook creates\n * them and the orders console (AGL-287) drives transitions. Pure — no\n * I/O here.\n */\n\nimport { stripeIdIsTestMode } from '@aglyn/aglyn/app-utils/stripe-deployment-mode'\nimport type { PaymentRisk } from '@aglyn/aglyn/app-utils/payment-risk'\nimport type { ProductType } from './commerce'\nimport type { StorefrontTaxMode } from './commerce-tax-decision'\n\nexport type OrderStatus =\n | 'pending'\n | 'paid'\n | 'partially_fulfilled'\n | 'fulfilled'\n | 'delivered'\n | 'cancelled'\n | 'refunded'\n\n/**\n * Where the sale came through. `subscription` is a recurring cycle of a\n * PHYSICAL subscription product (AGL-1750): each paid invoice mints one order\n * so fulfilment has something to pick, pack and label against, and so\n * recurring revenue reaches the surfaces that read `orders`. Digital and\n * service subscriptions still record on the subscription document alone —\n * there is nothing to ship, and AGL-1732's \"a subscription is not an order\"\n * stands for them.\n */\nexport type OrderChannel = 'online' | 'pos' | 'draft' | 'subscription'\n\n/** Snapshot of what was bought — self-contained for history. */\nexport interface OrderLineItem {\n productId: string\n variantId?: string\n /** Display snapshot at purchase time. */\n name: string\n variantLabel?: string\n sku?: string\n productType?: ProductType\n quantity: number\n /** Per-unit price in cents at purchase time. */\n unitAmountCents: number\n /** Supplier at purchase time (dropship routing, AGL-289). */\n supplierId?: string\n /** Fulfillment id once this line ships (AGL-288). */\n fulfillmentId?: string\n}\n\nexport interface OrderTotals {\n itemsCents: number\n shippingCents: number\n taxCents: number\n /** Positive number subtracted from the total. */\n discountCents: number\n totalCents: number\n /** Aglyn platform fee (Connect application fee, AGL-278/307). */\n feeCents: number\n}\n\nexport interface OrderAddress {\n name?: string\n line1?: string\n line2?: string\n city?: string\n state?: string\n postalCode?: string\n country?: string\n phone?: string\n}\n\nexport interface OrderTimelineEvent {\n atMs: number\n /** Machine event key, e.g. 'paid', 'fulfilled', 'refund', 'note'. */\n event: string\n /** Human-readable detail shown in the console timeline. */\n detail?: string\n}\n\nexport interface OrderFulfillment {\n id: string\n lineItemIds: number[]\n carrier?: string\n trackingNumber?: string\n trackingUrl?: string\n atMs: number\n}\n\n/**\n * A card dispute against this order's charge (AGL-1787).\n *\n * The DISTINCTION a chargeback carries, kept off `status` on purpose. A lost\n * dispute leaves the order `refunded` — that is what every reader of the status\n * already means by it, and five entitlement gates (`gate.ts`, `download.ts`,\n * `reviews.ts`, `membership-account.ts` and the glance card) match on the\n * literal `'refunded'`, so a new terminal status would have left the shopper\n * their digital downloads and their verified-purchase review. That is AGL-1546\n * reproduced on the tenant side, and the reason this record sits BESIDE the\n * status rather than replacing it: the money question is answered by `status`\n * and `refundedCents`, the \"by what door\" question by this.\n *\n * Written whole rather than merged into, so a second dispute on the same charge\n * cannot inherit the previous one's `outcome` — see the handler.\n */\nexport interface OrderDispute {\n /** Stripe dispute id (`dp_…`). */\n id: string\n /** Stripe's dispute status verbatim, e.g. `needs_response`, `lost`. */\n status: string\n /** Stripe's reason code, e.g. `fraudulent`, `product_not_received`. */\n reason?: string\n /** Disputed cents — a partial dispute is less than the order total. */\n amountCents: number\n openedAtMs: number\n /** Evidence deadline, when Stripe supplied one. */\n evidenceDueByMs?: number\n closedAtMs?: number\n /** `won` | `lost` | `warning_closed`, set once the dispute closes. */\n outcome?: string\n /**\n * Cents this dispute actually reversed on the order — a LOST dispute only,\n * and capped against what was left, so it is not always `amountCents`.\n */\n reversedCents?: number\n /**\n * Stripe transfer-reversal id (`trr_…`) that pulled the seller's share back\n * from the connected account (AGL-1794) — set only when a reversal was\n * actually created or found already sitting on the transfer.\n */\n transferReversalId?: string\n /**\n * Cents pulled back from the CONNECTED account for this lost dispute\n * (AGL-1794). Its presence — 0 included — is the settle marker for the\n * reversal step: 0 means the step ran and found nothing to reverse (no\n * transfer on the charge, or a transfer with nothing left), recorded so a\n * redelivery does not retry a failure no redelivery can fix.\n */\n reversedTransferCents?: number\n}\n\n/**\n * One line whose stock a reversal MAY need to put back (AGL-1797).\n *\n * Only lines the sale actually DECREMENTED appear here — an untracked variant\n * (`inventory == null`) had nothing taken off it, so it has nothing to return,\n * and a digital line is untracked for that same reason rather than by a\n * type test of its own. A merchant who tracks license stock on a digital\n * product does get it back.\n */\nexport interface OrderRestockLine {\n productId: string\n variantId: string\n /**\n * Units this line sold. It is the MOST that can come back, never a claim\n * that they did: on a partial reversal the merchant reversed some of the\n * money and only they know which goods returned.\n */\n quantity: number\n /** Purchase-time snapshots, so a reader renders the prompt with no product read. */\n name?: string\n variantLabel?: string\n /**\n * Which `lineItems` entry this came from, matching `refundedLineItemIds`\n * (AGL-2325).\n *\n * The index rather than the product, because a cart can hold one product on\n * two lines and a line-scoped refund may name only one of them. It is what\n * lets a SECOND partial refund be told apart from a repeat of the first: a\n * reversal naming a line the open question already covers adds nothing, and\n * one naming a line it misses is a new question rather than noise.\n *\n * Absent on a check written before this shipped, and that absence is read as\n * \"covers the whole order\" — the assumption those checks were written under.\n */\n lineIndex?: number\n}\n\n/**\n * Stock left off the shelf by a reversed order, FLAGGED rather than released\n * (AGL-1797).\n *\n * The checkout webhook decrements variant inventory on a sale and nothing put\n * it back, so a fully reversed order read one unit light forever and the error\n * compounded with every return. The fix is not the obvious increment, because\n * an increment is wrong more often than it is right:\n *\n * - a **returned** item genuinely comes back, and stock should rise — but only\n * once it is RECEIVED, and there is no fulfilment event that records receipt\n * (`OrderFulfillment` has no returned state);\n * - a **refund with no return** — goodwill, damaged, lost in post — leaves the\n * goods gone, so incrementing invents stock the merchant does not have and\n * sells something that is not on the shelf. That is worse than the bug it\n * replaces: under-counting refuses a sale, over-counting takes one it cannot\n * fill;\n * - a **chargeback** is the clearest do-not-restock case of all, since the\n * shopper kept the item and took the money back.\n *\n * And the quantities are not always knowable on a partial reversal: a refund\n * requested as an AMOUNT names no line at all, so \"$17 of a $62 order\" selects\n * nothing. `quantity` is an upper bound there and `fullyReversed` says whether\n * it is a tight one.\n *\n * A refund that DID name its lines is scoped to them (AGL-2325). `refund.ts`\n * records `refundedLineItemIds` with the money (AGL-2454), so the flag asks\n * about the lines that reversal withdrew rather than about the whole order —\n * and `lineIndex` on each line is what lets a second partial withdrawing a\n * DIFFERENT line re-ask, instead of being swallowed by the still-open question\n * about the first.\n *\n * So this records the question instead of guessing the answer, and the merchant\n * answers it from the stock adjustment they already have. The release action is\n * NOT rebuilt here: the products hub's \"Adjust stock\" already writes the\n * variant counts and an `InventoryAdjustment` row with a reason.\n */\nexport interface OrderRestockCheck {\n /** Which door the money left by; the two deserve different default wording. */\n kind: 'refund' | 'chargeback'\n /** Inventory-tracked lines only — exactly what the sale decremented. */\n lines: OrderRestockLine[]\n /** Sum of `lines[].quantity`, denormalized so a badge needs no arithmetic. */\n units: number\n /**\n * False when only part of the money came back, which is what makes\n * `quantity` an upper bound rather than a proposal.\n */\n fullyReversed: boolean\n flaggedAtMs: number\n /**\n * Set once a merchant answers. Absent means the question is still open, and\n * it is the only state a fresh reversal will overwrite.\n */\n resolution?: 'restocked' | 'dismissed'\n resolvedAtMs?: number\n /** Console uid that answered. */\n resolvedBy?: string\n}\n\n/**\n * A cart line a paid order could not record (AGL-2149). All that survives a\n * deleted product is what the shopper's cart asked for.\n */\nexport interface OrderUnresolvedLine {\n productId: string\n variantId?: string\n quantity: number\n}\n\n/** `hosts/{hostId}/orders/{id}` doc. */\nexport interface HostOrder {\n /** Human order number, sequential per host (e.g. #1042). */\n number?: number\n status: OrderStatus\n channel?: OrderChannel\n /**\n * Which location's stock this sale came off, for the multi-location counts\n * of AGL-286. Written by the POS register, the only sale path that decrements\n * a location bucket rather than the flat count — and read back when the order\n * is cancelled (AGL-1808), because putting the units on the flat total when\n * the sale took them out of a bucket leaves the two disagreeing, and the next\n * location-aware write recomputes the total from the buckets and silently\n * erases the restock.\n */\n locationId?: string\n /**\n * The register discount that was applied, and the member who applied it\n * (AGL-2161). Present only on a POS order that carries a discount.\n *\n * `totals.discountCents` records that money came off; these record who\n * decided it should. Without them a full comp read exactly like a\n * correctly-priced sale after the fact, on the one channel where a person is\n * standing in front of the goods and can ring whatever they like. The\n * ceiling that bounds `discountPct` is the org's `posMaxDiscountPct` plugin\n * setting, enforced server-side in `pos-order.ts`.\n */\n discountPct?: number\n discountBy?: string\n lineItems?: OrderLineItem[]\n totals?: OrderTotals\n /**\n * WHICH TAX REGIME this order carried (AGL-2451), resolved at the moment the\n * sale decided it and stamped here rather than re-derived later.\n *\n * `totals.taxCents` says how much tax was charged and nothing about who\n * computed it. Those are different facts about whose registration the money\n * is held under: a `stripe-automatic` storefront sale's tax is computed\n * against AGLYN's registrations and settles into Aglyn's balance (MEASURED —\n * see `server/storefront-tax.ts`), while a `manual` one is the merchant's own\n * rate on their own origin. An order that cannot say which one it carried\n * cannot be reconciled against the return, or corrected afterwards.\n *\n * AGL-2440's merchant report reads the authoritative\n * `storefrontTaxCollected` record; this is the same fact on the single order,\n * and both come from `storefrontTaxMode` so the two cannot drift.\n *\n * ## ABSENT MEANS \"NOT RECORDED\" — AND NOTHING MAY DEFAULT IT\n *\n * Every order written before AGL-2451 shipped genuinely has no stamp, and\n * that is the honest reading. `liftLegacyOrder` deliberately does NOT\n * synthesise one the way it synthesises `totals.taxCents: 0`: it runs on the\n * READ path, its result is spread into merge writes, and a defaulted `none`\n * would overwrite a real `stripe-automatic` — the converter-on-partial-writes\n * hazard, in a place where the destroyed value is a tax fact. The console\n * renders an absent mode as unknown, never as `none`\n * (`describeOrderTaxMode`).\n */\n taxMode?: StorefrontTaxMode\n customerEmail?: string | null\n customerName?: string | null\n /** Storefront customer id once accounts exist (AGL-294). */\n customerId?: string\n shippingAddress?: OrderAddress\n billingAddress?: OrderAddress\n timeline?: OrderTimelineEvent[]\n fulfillments?: OrderFulfillment[]\n note?: string\n couponCode?: string\n /** Stripe references for refunds. */\n paymentIntentId?: string\n checkoutSessionId?: string\n /**\n * The recurring cycle this order fulfils (channel `subscription`,\n * AGL-1750). The order's own doc id is the invoice id — that identity is\n * the redelivery key — and these carry the join back to\n * `subscriptions/{subscriptionId}/invoices/{invoiceId}`.\n */\n subscriptionId?: string\n invoiceId?: string\n /** Draft orders (AGL-287): the link sent to the buyer. */\n paymentLinkUrl?: string\n refundedCents?: number\n /**\n * Line indexes an admin refunded BY NAME (AGL-2454), and therefore the lines\n * whose digital entitlements are withdrawn.\n *\n * A refund carries an AMOUNT, not lines — `refund.ts` takes `amountCents`\n * and Stripe knows nothing about what was in the basket — so a partial refund\n * cannot in general be attributed to anything. That is why five entitlement\n * gates matched the literal `'refunded'` on `status` and a 99%-refunded order\n * kept every download, licence key, gated page and verified review.\n *\n * This is the case where attribution IS possible: the console refund dialog\n * lets an admin pick the lines they are refunding, and when it does the\n * amount is computed FROM those lines rather than typed beside them. An\n * amount-only refund still revokes nothing per-line — deciding for the\n * merchant which lines a bare figure covers would be a guess about their\n * goods — but it is no longer silent: `orderRefundState` reports it, the\n * console shows it, and the order timeline records it.\n *\n * Indexes rather than product ids, because a cart can hold two lines of the\n * same product (different variants) and only one may be coming back.\n */\n refundedLineItemIds?: number[]\n /** The card dispute against this charge, open or settled (AGL-1787). */\n dispute?: OrderDispute\n /**\n * Stripe's fraud signals on this order's payment (AGL-3360): an issuer's\n * early fraud warning or a Radar review, stamped by the billing webhook\n * through the shared `paymentRisk` shape. A dispute lives in `dispute`.\n */\n paymentRisk?: PaymentRisk\n /** Stock a reversal left off the shelf, awaiting the merchant (AGL-1797). */\n restockCheck?: OrderRestockCheck\n /**\n * Cart lines the webhook could not price because the product was deleted\n * between session creation and payment (AGL-2149). Present ONLY on an order\n * that is short of what the shopper was charged: `totals.itemsCents` is\n * missing these lines while `amountCents` still holds the full\n * `amount_total`, and this is the record of which lines the difference is.\n *\n * Deliberately NOT an `OrderLineItem[]`: the product doc is gone, so there is\n * no name, no price and no type to record — only what the cart asked for.\n */\n unresolvedLines?: OrderUnresolvedLine[]\n createdAtMs?: number\n // Legacy Commerce Starter fields (AGL-90) kept readable.\n productId?: string\n amountCents?: number\n feeCents?: number\n}\n\n/**\n * Legal status transitions. Refund/cancel policies: anything paid can\n * refund; only unfulfilled orders cancel (refund instead once shipped).\n *\n * `cancelled` IS NOT TERMINAL, and treating it as one trapped money (AGL-2149).\n * `cancel-order.ts` accepts a **paid** order — `paid: [… 'cancelled' …]` above\n * — and moves no money when it does: it flips the status and returns the stock.\n * `refund.ts` gates on `canTransitionOrder(order.status, 'refunded')`, so with\n * `cancelled: []` the refund door closed behind the cancel and answered \"orders\n * in cancelled cannot refund\" forever. An admin who cancelled a paid order\n * instead of refunding it — the two buttons sit next to each other in the order\n * dialog — had permanently locked the shopper's money out of every refund route\n * this product has. The shopper's only remaining recourse was a chargeback,\n * which costs the merchant the goods, the money AND the dispute fee.\n *\n * Whether cancelling a PAID order should refuse, or refund on the merchant's\n * behalf, is a real product question and is deliberately NOT answered here:\n * either would change what an existing console button does to money. Making the\n * money reachable is the part that must not wait for that answer.\n *\n * A cancelled order that never took money simply has nothing to refund —\n * `refund.ts` resolves no payment intent and answers \"No payment to refund\" —\n * so the widened edge costs a lapsed `pending` order nothing.\n */\nconst ORDER_TRANSITIONS: Record<OrderStatus, OrderStatus[]> = {\n pending: ['paid', 'cancelled'],\n paid: ['partially_fulfilled', 'fulfilled', 'cancelled', 'refunded'],\n partially_fulfilled: ['fulfilled', 'refunded'],\n fulfilled: ['delivered', 'refunded'],\n delivered: ['refunded'],\n cancelled: ['refunded'],\n refunded: [],\n}\n\nexport function canTransitionOrder(\n from: OrderStatus,\n to: OrderStatus,\n): boolean {\n return (ORDER_TRANSITIONS[from] ?? []).includes(to)\n}\n\n/**\n * One money part, coerced to a whole non-negative number of cents (AGL-2224).\n *\n * `Math.max(0, Math.round(x))` is NOT this guard, and the difference is the\n * whole defect: `Math.max` and `Math.min` PROPAGATE `NaN` rather than\n * discarding it, so a single non-finite input made every field below `NaN` and\n * `totalCents` with them. That is not a cosmetic wrong number — it is a number\n * that defeats comparison. `cashReceivedCents < NaN` is `false`, so the POS\n * \"cash received is short\" guard passed a sale that had taken no money; and a\n * `NaN` written to Firestore (a legal double there) poisons every downstream\n * sum, so ONE bad order renders a merchant's whole revenue figure `NaN`\n * forever after.\n *\n * The inputs that can be non-finite are real, not theoretical: a POS\n * `discountPct` that is not a number, a product whose `priceUsd` is a string\n * or absent (`Number(undefined) * 100`), a `taxCents` derived from either. So\n * this coerces at the boundary every order in the product passes through,\n * rather than at each of the five callers, and answers 0 — the only value that\n * cannot silently overstate what the shopper owes.\n */\nconst wholeCents = (value: unknown): number => {\n const cents = Math.round(Number(value ?? 0))\n return Number.isFinite(cents) && cents > 0 ? cents : 0\n}\n\n/**\n * Split `totalCents` across `weights` so the parts sum to it EXACTLY.\n *\n * Largest-remainder (Hamilton) apportionment. Each weight takes its exact\n * share, the shares are floored, and the cents left over by flooring are handed\n * one each to the largest fractional parts, ties going to the lower index so\n * the answer is deterministic for a given input.\n *\n * The exactness is the whole point and it is a money property, not a\n * neatness one: `Math.round` per line loses or invents cents against the total\n * — three lines splitting a 10¢ discount round to 3+3+3=9 and the missing cent\n * has to come from somewhere. A refund built on a split that does not close is\n * a reconciliation defect that surfaces months later against Stripe's numbers.\n *\n * Pure and total. A non-positive total, an empty weight list, or weights that\n * sum to nothing all answer zeros — never a division by zero, never a NaN\n * propagated into an amount someone is charged.\n */\nexport function apportionCents(\n weights: readonly number[],\n totalCents: number,\n): number[] {\n const safe = (weights ?? []).map((weight) =>\n Math.max(0, Math.round(Number(weight ?? 0))),\n )\n const basis = safe.reduce((sum, weight) => sum + weight, 0)\n const total = Math.max(0, Math.round(Number(totalCents ?? 0)))\n if (safe.length === 0 || basis <= 0 || total <= 0) return safe.map(() => 0)\n // Never hand back more than there is to split. A discount larger than the\n // items it came off is a data fault, and apportioning it in full would\n // credit lines with money the order never carried.\n const pot = Math.min(total, basis)\n const exact = safe.map((weight) => (weight * pot) / basis)\n const shares = exact.map((value) => Math.floor(value))\n let remainder = pot - shares.reduce((sum, share) => sum + share, 0)\n const order = exact\n .map((value, index) => ({ index, fraction: value - Math.floor(value) }))\n .sort((a, b) => b.fraction - a.fraction || a.index - b.index)\n for (const entry of order) {\n if (remainder <= 0) break\n shares[entry.index] += 1\n remainder -= 1\n }\n return shares\n}\n\n/**\n * What refunding the named lines is actually worth.\n *\n * A line's LIST value is `unitAmountCents x quantity`, and that is what the\n * refund route used to hand back. It is the wrong number on any order that\n * carried an order-level discount, because the buyer never paid it: a $10\n * coupon on two $50 lines means each line cost $45, and refunding one at $50\n * pays back $5 the buyer never spent. The order then has less left than its\n * remaining line is worth, so the SECOND line refund is refused outright and\n * the merchant cannot finish the job they started.\n *\n * So each line carries its share of the discount, apportioned by list value\n * through {@link apportionCents} — which guarantees the shares sum to the whole\n * discount, so refunding every line returns exactly `itemsCents - discountCents`\n * and no cent is stranded or invented.\n *\n * SHIPPING AND TAX ARE DELIBERATELY NOT IN THIS FIGURE. Refunding a line\n * returns what the goods cost. Shipping is charged for the consignment rather\n * than the line, so no split of it is defensible without knowing whether the\n * parcel still ships; tax follows the goods but is Stripe's to recompute, and\n * an order whose tax was collected under Aglyn's own registration\n * (`taxMode: 'stripe-automatic'`) is not one this function may quietly decide\n * to hand back. Both remain whole-order concerns, refundable by amount.\n *\n * Pure: unknown indexes contribute nothing rather than throwing.\n */\nexport function orderLineRefundCents(\n order: Pick<Partial<HostOrder>, 'lineItems' | 'totals'>,\n lineIndexes: readonly number[],\n): number {\n const lines = order?.lineItems ?? []\n const gross = lines.map((line) =>\n Math.max(\n 0,\n Math.round(Number(line?.unitAmountCents ?? 0)) *\n Math.max(1, Math.round(Number(line?.quantity ?? 1))),\n ),\n )\n const shares = apportionCents(\n gross,\n Math.max(0, Math.round(Number(order?.totals?.discountCents ?? 0))),\n )\n const wanted = new Set(\n (lineIndexes ?? []).filter(\n (index) => Number.isInteger(index) && index >= 0 && index < lines.length,\n ),\n )\n let cents = 0\n for (const index of wanted) cents += gross[index] - shares[index]\n return Math.max(0, cents)\n}\n\n/**\n * A REHEARSAL IS NOT REVENUE.\n *\n * A smoke-test checkout writes a real order document. Stripe never moved money\n * for it — the session id is `cs_test_…` — but every surface that sums paid\n * orders counted it, so a storefront with one test order and no sales reported\n * revenue it had never earned. Found in production: a single $18.00 order from\n * an end-to-end run, standing as the whole platform's storefront revenue.\n *\n * ## Two signals, in order of trust\n *\n * `livemode` is a RECORDED FACT and wins whenever it is present: the webhook\n * knows which Stripe environment an event came from and now writes it onto the\n * order. Only a literal boolean counts, the same three-valued reading every\n * other money gate in this codebase uses.\n *\n * The session id prefix is the FALLBACK, for the orders written before\n * anything recorded the fact. It is Stripe's convention rather than our data,\n * so it lives behind `stripeIdIsTestMode` and is not spelled out here.\n *\n * ## An unknowable order is LIVE\n *\n * An order with no `livemode` and no Stripe session id — a POS cash sale, a\n * folio charge, a draft order paid offline — is real money and is counted.\n * Answering \"test\" for anything we cannot identify would zero genuine sales,\n * which is the one direction this must never fail in: a merchant under-reporting\n * their own revenue has no way to tell it is happening.\n */\nexport function orderIsTestMode(\n order: Pick<Partial<HostOrder>, 'checkoutSessionId'> & {\n livemode?: unknown\n /** The Firestore doc id, which for a storefront order IS the session id. */\n $id?: unknown\n },\n): boolean {\n if (typeof order?.livemode === 'boolean') return !order.livemode\n // `$id` as well as the field: every storefront order is keyed BY its session\n // id, so the doc id carries the same signal and is present on rows read\n // straight out of Firestore even where the field was never written. A POS or\n // draft order has an auto-generated id, which matches no Stripe prefix and\n // correctly reads as live.\n return (\n stripeIdIsTestMode(order?.checkoutSessionId) || stripeIdIsTestMode(order?.$id)\n )\n}\n\n/** Sums line items and folds in shipping/tax/discount/fee, all cents. */\nexport function computeOrderTotals(\n lineItems: OrderLineItem[],\n parts?: Partial<Pick<OrderTotals, 'shippingCents' | 'taxCents' | 'discountCents' | 'feeCents'>>,\n): OrderTotals {\n const itemsCents = (lineItems ?? []).reduce(\n (sum, line) => sum + wholeCents(line?.unitAmountCents * line?.quantity),\n 0,\n )\n const shippingCents = wholeCents(parts?.shippingCents)\n const taxCents = wholeCents(parts?.taxCents)\n const discountCents = Math.min(\n wholeCents(parts?.discountCents),\n itemsCents + shippingCents,\n )\n return {\n itemsCents,\n shippingCents,\n taxCents,\n discountCents,\n feeCents: wholeCents(parts?.feeCents),\n totalCents: itemsCents + shippingCents + taxCents - discountCents,\n }\n}\n\n/**\n * The completed Checkout Session fields an online order's totals are built\n * from. Typed structurally rather than against Stripe's SDK: the plugin talks\n * to Stripe over raw `fetch` and never installs the package.\n */\nexport interface CheckoutSessionTotalsSource {\n amount_total?: unknown\n total_details?: {\n amount_tax?: unknown\n amount_shipping?: unknown\n amount_discount?: unknown\n } | null\n}\n\n/**\n * Stored `OrderTotals` for an `online` order, from the completed session.\n *\n * AGL-1698: the webhook used to read `amount_tax` and `amount_discount` and\n * silently skip their third sibling, so `computeOrderTotals` defaulted\n * `shippingCents` to 0 on every online order while the shipping the shopper\n * paid sat inside `amount_total`. The stored parts then did not sum to the\n * stored total, and every merchant reconciling their own books against these\n * records understated shipping by exactly the amount charged. `amount_shipping`\n * is the figure to read — `shipping_cost.amount_total` carries the same number\n * but is null unless a rate was chosen, whereas `total_details.amount_shipping`\n * is always present and sits beside the two fields already read.\n *\n * `totalCents` still comes from Stripe's `amount_total` verbatim rather than\n * from our arithmetic: `itemsCents` is priced from the host's product docs, so\n * a price edit between session creation and webhook delivery would otherwise\n * make our sum, not Stripe's charge, the stored truth. With shipping passed the\n * two agree by construction — which is now an invariant a fixture can pin\n * rather than a discrepancy that papers over a missing part.\n *\n * Note this also unclamps the discount correctly: `computeOrderTotals` caps\n * `discountCents` at `itemsCents + shippingCents`, so a discount that reached\n * into shipping was previously clamped down as well.\n */\nexport interface CheckoutSessionTotalsParts {\n /** Aglyn's cut, from the session metadata rather than from Stripe. */\n feeCents?: number\n /**\n * Tax we charged as an ordinary Stripe line item (AGL-1711). It is inside\n * `amount_total` but Stripe was never told it was tax, so it is absent from\n * `total_details.amount_tax` and has to be supplied by the caller that built\n * the session. Added to Stripe's figure, not substituted for it: the two are\n * mutually exclusive by construction — `checkout.ts` adds the manual line\n * only in `manual` tax mode and sets `automatic_tax` only in `stripe` mode —\n * so summing is right in every reachable case and correct in principle if\n * both were ever charged at once.\n *\n * A manual-tax SUBSCRIPTION session is the third construction (AGL-1751):\n * there the tax is a real Stripe Tax Rate (a one-time line would bill on\n * the first invoice only), `amount_tax` carries it, and `checkout.ts`\n * writes `metadata[taxCents]` as 0 — the exclusivity this sum rests on,\n * kept from the other side.\n */\n lineItemTaxCents?: number\n /**\n * A discount applied by lowering the unit price we sent to Stripe rather\n * than as a Stripe discount (AGL-1711). Same shape as `lineItemTaxCents`:\n * invisible to `total_details.amount_discount`, so it is added to it.\n */\n pricedInDiscountCents?: number\n}\n\nexport function computeCheckoutSessionTotals(\n lineItems: OrderLineItem[],\n session: CheckoutSessionTotalsSource | null | undefined,\n parts?: CheckoutSessionTotalsParts,\n): OrderTotals {\n const details = session?.total_details ?? {}\n const totals = computeOrderTotals(lineItems, {\n feeCents: Number(parts?.feeCents ?? 0),\n taxCents:\n Number(details?.amount_tax ?? 0) + Number(parts?.lineItemTaxCents ?? 0),\n shippingCents: Number(details?.amount_shipping ?? 0),\n discountCents:\n Number(details?.amount_discount ?? 0) +\n Number(parts?.pricedInDiscountCents ?? 0),\n })\n const amountTotal = Number(session?.amount_total ?? NaN)\n return {\n ...totals,\n totalCents: Number.isFinite(amountTotal) ? amountTotal : totals.totalCents,\n }\n}\n\n/** Non-negative integer cents from an untyped metadata value; 0 otherwise. */\nfunction metadataCents(value: unknown): number {\n const cents = Math.round(Number(value ?? 0))\n return Number.isFinite(cents) && cents > 0 ? cents : 0\n}\n\n/** The buy-now Checkout Session as the webhook sees it — metadata included. */\nexport interface BuyNowSessionSource extends CheckoutSessionTotalsSource {\n metadata?: Record<string, unknown> | null\n}\n\n/** What the host's product doc contributes to the line-item snapshot. */\nexport interface BuyNowProductSnapshot {\n /** Product name at purchase time. */\n name: string\n /** Joined variant options, e.g. `Large / Blue`. */\n variantLabel?: string\n sku?: string\n productType?: ProductType\n supplierId?: string\n}\n\n/**\n * The stored line items and totals for a `commerce-order` (buy-now) session\n * (AGL-1711).\n *\n * ## What was wrong\n *\n * The webhook built the whole order from one number — it read\n * `object.amount_total` into `amountCents`, wrote a single line item with\n * `quantity` literal 1 and `unitAmountCents` set to that `amountCents`, and\n * passed `computeOrderTotals` nothing but `feeCents`.\n *\n * `amount_total` is the ENTIRE charge, so the merchant's record said the\n * product's unit price was the whole session total, that one unit was sold, and\n * that tax and discount were zero. A 3 × $100 purchase recorded as 1 × $300.\n *\n * The dangerous property, and why this outlived AGL-1698: it is not internally\n * inconsistent. `itemsCents` equalled `totalCents`, so the parts summed and any\n * arithmetic check passed while every individual component was wrong. The\n * useful test is not \"do the parts sum\" but \"does each part match what Stripe\n * actually charged\", which is what the fixtures assert component by component.\n *\n * ## Why metadata, when AGL-1698's lesson was to read Stripe's own fields\n *\n * Two of the four parts are genuinely absent from Stripe's decomposition, and\n * `checkout.ts` is what hid them:\n *\n * - **Tax.** In `manual` mode the tax is appended as `line_items[1]`, a normal\n * product line labelled e.g. \"Tax (8.25%)\". Stripe does not know it is tax,\n * so `total_details.amount_tax` is 0 while the money sits in `amount_total`.\n * (In `stripe` mode `automatic_tax` makes `amount_tax` real — and it was not\n * read either, so that tax was lost as well.)\n * - **Discount.** A host coupon is applied by lowering the unit price we send,\n * not as a Stripe discount, so `total_details.amount_discount` is 0.\n *\n * So `checkout.ts` now records both figures, plus the list unit price, in the\n * session metadata as it computes them. This is NOT the reconstruction AGL-1698\n * warned against: the guard there is against pricing from the host's product\n * docs at webhook time, where an edit between session creation and delivery\n * makes our sum disagree with the actual charge. Metadata is a snapshot of the\n * numbers we handed Stripe, frozen at session creation, so it cannot drift.\n * Everything Stripe does know — `amount_total`, `amount_tax`, `amount_shipping`,\n * `amount_discount` — still comes from Stripe, via the shared\n * `computeCheckoutSessionTotals` rather than a parallel decomposition.\n *\n * Shipping is read even though `checkout.ts` declares no `shipping_options`\n * today (AGL-1720): the read is not conditioned on that, so the figure lands on\n * its own the moment buy-now starts charging shipping.\n *\n * ## Sessions created before this change\n *\n * They carry `quantity` (always sent) but none of the three new keys, so the\n * unit price falls back to what Stripe charged per unit, derived from\n * `amount_total` with tax, shipping and discount taken back out. Those orders\n * still reconcile against `amount_total` and their quantity is now right; what\n * cannot be recovered is a priced-in coupon, which stays folded into the unit\n * price exactly as it is today. Deriving from the product doc instead would\n * make `itemsCents` gross with no matching `discountCents`, breaking the sum.\n *\n * ## Worked example — the spec fixture\n *\n * Three $100 units, a 10% host coupon, manual destination tax at 8.25%:\n *\n * listUnit 10000 metadata `unitAmountCents`\n * × quantity 3 metadata `quantity`\n * = itemsCents 30000\n * - discountCents 3000 metadata `discountCents` (priced into the\n * 9000 unit price sent to Stripe)\n * + taxCents 2228 metadata `taxCents` (Stripe `line_items[1]`;\n * `total_details.amount_tax` is 0)\n * + shippingCents 0 Stripe `total_details.amount_shipping`\n * = totalCents 29228 Stripe `amount_total`, verbatim\n *\n * The pre-fix record for the same purchase: `1 × 29228`, tax 0, discount 0 —\n * which also sums to 29228.\n *\n * ## Also the subscription sale record (AGL-1732)\n *\n * `checkout.ts` builds a subscription session with the same function and the\n * same metadata snapshot — only `mode` differs — so the initial charge of a\n * storefront subscription decomposes identically. The webhook's\n * `commerce-subscription` branch stores the result on the subscription\n * document rather than as an order; the arithmetic is the same either way,\n * which is why it lives here and is not duplicated there.\n */\nexport function computeBuyNowOrder(\n session: BuyNowSessionSource | null | undefined,\n snapshot: BuyNowProductSnapshot,\n): { lineItems: OrderLineItem[]; totals: OrderTotals } {\n const metadata = (session?.metadata ?? {}) as Record<string, unknown>\n const details = session?.total_details ?? {}\n const rawQuantity = Math.round(Number(metadata.quantity ?? 1))\n const quantity =\n Number.isFinite(rawQuantity) && rawQuantity > 0 ? rawQuantity : 1\n const lineItemTaxCents = metadataCents(metadata.taxCents)\n const pricedInDiscountCents = metadataCents(metadata.discountCents)\n // What Stripe charged for the goods themselves, used only when the session\n // predates the `unitAmountCents` metadata: strip every non-goods component\n // back out of the total and add the discount back in.\n const goodsCents =\n Number(session?.amount_total ?? 0) -\n Number(details?.amount_tax ?? 0) -\n Number(details?.amount_shipping ?? 0) -\n lineItemTaxCents +\n Number(details?.amount_discount ?? 0) +\n pricedInDiscountCents\n const unitAmountCents =\n metadataCents(metadata.unitAmountCents) ||\n Math.max(0, Math.round(goodsCents / quantity))\n const variantId = String(metadata.variantId ?? '')\n const lineItems: OrderLineItem[] = [\n {\n productId: String(metadata.productId ?? ''),\n ...(variantId ? { variantId } : {}),\n name: snapshot.name,\n ...(snapshot.variantLabel ? { variantLabel: snapshot.variantLabel } : {}),\n ...(snapshot.sku ? { sku: snapshot.sku } : {}),\n ...(snapshot.productType ? { productType: snapshot.productType } : {}),\n ...(snapshot.supplierId ? { supplierId: snapshot.supplierId } : {}),\n quantity,\n unitAmountCents,\n },\n ]\n return {\n lineItems,\n totals: computeCheckoutSessionTotals(lineItems, session, {\n feeCents: metadataCents(metadata.feeCents),\n lineItemTaxCents,\n pricedInDiscountCents,\n }),\n }\n}\n\n/** One line on a Stripe invoice, structurally typed like the session above. */\nexport interface SubscriptionInvoiceLine {\n /** The line total in cents, after any line-level discount, excluding tax. */\n amount?: unknown\n quantity?: unknown\n description?: unknown\n /** True on the credit/charge pair a mid-cycle plan switch generates. */\n proration?: unknown\n price?: {\n unit_amount?: unknown\n recurring?: { interval?: unknown } | null\n } | null\n}\n\n/**\n * The paid-invoice fields a renewal's totals are built from (AGL-1743).\n *\n * ## An invoice is not a Checkout Session\n *\n * This was checked field by field rather than assumed, and the two objects\n * agree on almost nothing:\n *\n * - **There is no `total_details` on an invoice at all.** That object is a\n * Checkout Session field. `computeCheckoutSessionTotals` cannot be handed an\n * invoice — every part would read 0 and the whole renewal would decompose to\n * `amount_total` with nothing in it, which is exactly the AGL-1711 failure\n * shape (internally consistent, individually wrong).\n * - **Tax** is `tax` (a scalar) on the API versions this repo pins, and\n * `total_taxes[]` on newer ones, where the scalar was removed. Both are read,\n * the scalar preferred, because a webhook endpoint's version is dashboard\n * configuration this repo cannot see.\n * - **Discount** is `total_discount_amounts[]`, an array of per-discount\n * amounts, not a single `amount_discount`.\n * - **Shipping** is `shipping_cost.amount_total` — note AGL-1698 preferred\n * `total_details.amount_shipping` over exactly this field ON A SESSION,\n * because there it is null unless a rate was chosen. On an invoice it is the\n * only form there is.\n * - **The fee** is a real field here: a subscription carries\n * `application_fee_percent`, so every invoice reports the resulting\n * `application_fee_amount`. The session path has to read it out of metadata.\n * - **What was bought** is `lines.data[]` rather than the session's metadata\n * snapshot — `checkout.ts` puts only `type`, `hostId` and `productId` on\n * `subscription_data[metadata]`, so the quantity and unit price of a renewal\n * have to come from the invoice itself.\n *\n * The arithmetic is still the shared one: the fields above are mapped onto the\n * session shape and handed to `computeCheckoutSessionTotals`, so a renewal and\n * a sale clamp, sum and reconcile identically.\n */\nexport interface SubscriptionInvoiceSource {\n /** What actually arrived — the figure this records as collected. */\n amount_paid?: unknown\n total?: unknown\n subtotal?: unknown\n /** Total tax; removed in favor of `total_taxes` on newer API versions. */\n tax?: unknown\n total_taxes?: readonly { amount?: unknown }[] | null\n total_tax_amounts?: readonly { amount?: unknown }[] | null\n total_discount_amounts?: readonly { amount?: unknown }[] | null\n shipping_cost?: { amount_total?: unknown } | null\n application_fee_amount?: unknown\n /** Why Stripe raised it: `subscription_cycle`, `subscription_update`, … */\n billing_reason?: unknown\n lines?: { data?: readonly SubscriptionInvoiceLine[] | null } | null\n}\n\n/**\n * The base a subscription invoice's platform fee SHOULD be taken on\n * (AGL-2317): post-discount items, with tax and shipping removed.\n *\n * Every one-time door — `checkout.ts`, `cart-checkout.ts`, `draft-order.ts`,\n * `pos-order.ts` and (since AGL-2315) bookings — sends a cents amount computed\n * on items alone. The recurring door sent `application_fee_percent`, which\n * Stripe applies to the WHOLE invoice, so Aglyn took a cut of sales tax — money\n * that is the state's — and of shipping. On the issue's worked example ($30/mo\n * digital, Business 2%, an 8.25% TX rate) the one-time base is $30.00 → 60¢ and\n * the invoice total is $32.48 → 65¢.\n *\n * `total` and not a re-summed `lines.data[]`, deliberately. Stripe's own total\n * is already net of every invoice-level discount and of any proration CREDIT,\n * both of which `computeSubscriptionInvoiceOrder` drops (it floors a line at 0,\n * which is right for a stored line item and wrong for a fee base — a mid-cycle\n * downgrade credit reduces what the merchant is actually paid). Subtracting the\n * two parts Stripe reports separately leaves exactly what was charged for goods.\n *\n * Tax is read on both spellings for the reason stated on `SubscriptionInvoiceSource`\n * — the scalar was removed on newer API versions and the endpoint's version is\n * dashboard configuration this repo cannot see. Reading only one of them would\n * silently value tax at 0 and hand back the whole invoice total as the base,\n * which is the very bug this closes.\n */\nexport function subscriptionInvoiceFeeBasisCents(\n invoice: SubscriptionInvoiceSource | null | undefined,\n): number {\n const totalCents = metadataCents(invoice?.total)\n if (totalCents <= 0) return 0\n const scalarTax = Number(invoice?.tax ?? NaN)\n const taxCents =\n Number.isFinite(scalarTax) && scalarTax > 0\n ? scalarTax\n : sumAmounts(invoice?.total_taxes ?? invoice?.total_tax_amounts)\n const shippingCents = metadataCents(invoice?.shipping_cost?.amount_total)\n return Math.max(0, totalCents - Math.max(0, taxCents) - shippingCents)\n}\n\n/**\n * What the platform fee on a paid subscription invoice WOULD have been on the\n * items-only base, at the rate Stripe actually applied (AGL-2317).\n *\n * ## The rate is never named here, on purpose\n *\n * Pricing is locked for Sept 1, so this is a BASE correction and must not be\n * able to become a rate change. It therefore takes no plan, no entitlement and\n * no `resolveTransactionFeePct` call: it scales the fee Stripe already charged\n * by `base / total`. Whatever rate produced `application_fee_amount` — the\n * sale-time one, a rate AGL-2289 has since re-priced, or a staff override —\n * comes through untouched, and the only thing removed is the portion that was\n * taken on tax and shipping. A test that changed a rate could not make this\n * function agree with it.\n *\n * `Math.max(1, …)` matches `checkout.ts`: where a rate applies at all, the\n * floor is a cent, never zero. An invoice with no fee, no total, or nothing but\n * tax answers 0 and asks for no correction it cannot justify.\n */\nexport function subscriptionInvoiceItemsOnlyFeeCents(\n invoice: SubscriptionInvoiceSource | null | undefined,\n): number {\n const chargedCents = metadataCents(invoice?.application_fee_amount)\n if (chargedCents <= 0) return 0\n const totalCents = metadataCents(invoice?.total)\n // Nothing to scale against — leave what Stripe took rather than inventing a\n // reduction from a total we cannot read.\n if (totalCents <= 0) return chargedCents\n const basisCents = subscriptionInvoiceFeeBasisCents(invoice)\n if (basisCents <= 0) return 0\n // Tax-free and shipping-free invoices — the majority — land here and are\n // byte-identical to today, which is what makes this safe inside the freeze.\n if (basisCents >= totalCents) return chargedCents\n return Math.max(1, Math.round((chargedCents * basisCents) / totalCents))\n}\n\n/**\n * The recurring goods a storefront subscription's `application_fee_percent`\n * is sized against when a paid invoice re-prices it (AGL-2655).\n *\n * The percent carries Stripe's fixed 30¢ folded into a rate, so it depends\n * on the amount it will be applied to — and the amount that matters is the\n * NEXT cycle's, which nobody has yet. The best predictor is this cycle's\n * items-only basis, for a cycle that bills the full recurring price:\n * `subscription_cycle`, and `subscription_create` for the opening charge.\n * Any other reason — a `subscription_update` proration, a threshold\n * invoice — bills a fraction, and a percent sized on a fraction would be\n * applied to the following full cycle and over-recover on it. Those, and a\n * $0 opening invoice (a trial converts later), fall back to what the sale\n * recorded: the sold lines' price × quantity, the stable figure the\n * subscription bills every cycle it is not being adjusted.\n *\n * Zero when neither source knows — and the caller then leaves the rate as it\n * stands rather than sizing a percent on nothing.\n */\nexport function subscriptionRecurringBasisCents(\n invoice: SubscriptionInvoiceSource | null | undefined,\n soldLineItems:\n | readonly Pick<OrderLineItem, 'quantity' | 'unitAmountCents'>[]\n | null\n | undefined,\n): number {\n const reason = String(invoice?.billing_reason ?? '')\n const billsTheFullPrice =\n reason === 'subscription_cycle' || reason === 'subscription_create'\n const cycleBasis = billsTheFullPrice\n ? subscriptionInvoiceFeeBasisCents(invoice)\n : 0\n if (cycleBasis > 0) return cycleBasis\n return (soldLineItems ?? []).reduce((sum, line) => {\n const unit = Math.round(Number(line?.unitAmountCents ?? 0))\n const quantity = Math.round(Number(line?.quantity ?? 0))\n if (!Number.isFinite(unit) || !Number.isFinite(quantity)) return sum\n return sum + Math.max(0, unit) * Math.max(0, quantity)\n }, 0)\n}\n\n/** Sum of an `[{ amount }]` list, ignoring anything unreadable. */\nfunction sumAmounts(list: readonly { amount?: unknown }[] | null | undefined) {\n return (list ?? []).reduce((sum, entry) => {\n const amount = Number(entry?.amount ?? 0)\n return sum + (Number.isFinite(amount) ? amount : 0)\n }, 0)\n}\n\n/** `price.recurring.interval` when it is one we sell, else undefined. */\nfunction lineInterval(\n line: SubscriptionInvoiceLine | null | undefined,\n): 'month' | 'year' | undefined {\n const interval = line?.price?.recurring?.interval\n return interval === 'month' || interval === 'year' ? interval : undefined\n}\n\n/**\n * The line that describes the SUBSCRIPTION being billed.\n *\n * `lines.data[0]` is not it, for the AGL-1640 reason: a mid-cycle plan switch\n * invoices the proration against the OLD price ahead of the new plan, and an\n * invoice also carries one-off items and credits, any of which can sort first.\n * Prefer a non-proration line stating a cadence; fall back to a proration one,\n * since Stripe requires every recurring item on one subscription to share a\n * cadence; fall back to the first line so a renewal still records a name.\n */\nexport function selectSubscriptionInvoiceLine(\n invoice: SubscriptionInvoiceSource | null | undefined,\n): SubscriptionInvoiceLine | undefined {\n const lines = invoice?.lines?.data ?? []\n return (\n lines.find((line) => !line?.proration && lineInterval(line)) ??\n lines.find((line) => lineInterval(line)) ??\n lines[0]\n )\n}\n\n/**\n * The cadence a paid invoice was billed on, or undefined when it does not say.\n *\n * Never guessed — the same three-state discipline as `billingIntervalFromInvoice`\n * (AGL-1640). Storing `month` for an invoice that did not state one would make\n * the merchant's own record of what a subscriber pays wrong in the one way that\n * cannot be spotted by looking at it.\n */\nexport function subscriptionInvoiceInterval(\n invoice: SubscriptionInvoiceSource | null | undefined,\n): 'month' | 'year' | undefined {\n return lineInterval(selectSubscriptionInvoiceLine(invoice))\n}\n\n/**\n * The stored line items and totals for one paid subscription invoice\n * (AGL-1743) — a renewal, or the opening cycle.\n *\n * `snapshot` supplies the product identity, taken from what the sale already\n * recorded rather than re-derived: an invoice line knows a description and a\n * price, not a productId, a variant or a SKU. It is applied to the\n * subscription's own line only; a proration or one-off line on the same invoice\n * keeps Stripe's description and carries no product identity, because it is not\n * a sale of that product.\n *\n * `totalCents` is Stripe's `amount_paid` verbatim, for the AGL-1698 reason —\n * our sum is priced from lines we did not authorize and must never become the\n * stored truth. Where a customer credit balance covers part of an invoice the\n * parts will therefore exceed the collected total; the collected figure is the\n * one that matters and the invoice's own `total` is stored beside it.\n */\nexport function computeSubscriptionInvoiceOrder(\n invoice: SubscriptionInvoiceSource | null | undefined,\n snapshot: BuyNowProductSnapshot & { productId: string; variantId?: string },\n): { lineItems: OrderLineItem[]; totals: OrderTotals } {\n const subscriptionLine = selectSubscriptionInvoiceLine(invoice)\n const lineItems: OrderLineItem[] = []\n for (const line of invoice?.lines?.data ?? []) {\n const amount = Math.round(Number(line?.amount ?? 0))\n // A credit line (negative proration) is not a sale of anything, and\n // `computeOrderTotals` floors a line at 0 rather than subtracting it.\n // Stripe's own collected total still carries its effect.\n if (!Number.isFinite(amount) || amount <= 0) continue\n const rawQuantity = Math.round(Number(line?.quantity ?? 1))\n const quantity =\n Number.isFinite(rawQuantity) && rawQuantity > 0 ? rawQuantity : 1\n // The list unit price, except on a proration line, where `unit_amount` is\n // the full price and `amount` is only the fraction being billed.\n const listUnit = Math.round(Number(line?.price?.unit_amount ?? 0))\n const unitAmountCents =\n !line?.proration && Number.isFinite(listUnit) && listUnit > 0\n ? listUnit\n : Math.max(0, Math.round(amount / quantity))\n const isSubscriptionLine = line === subscriptionLine\n lineItems.push({\n productId: isSubscriptionLine ? snapshot.productId : '',\n ...(isSubscriptionLine && snapshot.variantId\n ? { variantId: snapshot.variantId }\n : {}),\n name: isSubscriptionLine\n ? snapshot.name\n : String(line?.description ?? 'Adjustment').slice(0, 200),\n ...(isSubscriptionLine && snapshot.variantLabel\n ? { variantLabel: snapshot.variantLabel }\n : {}),\n ...(isSubscriptionLine && snapshot.sku ? { sku: snapshot.sku } : {}),\n ...(isSubscriptionLine && snapshot.productType\n ? { productType: snapshot.productType }\n : {}),\n ...(isSubscriptionLine && snapshot.supplierId\n ? { supplierId: snapshot.supplierId }\n : {}),\n quantity,\n unitAmountCents,\n })\n }\n const scalarTax = Number(invoice?.tax ?? NaN)\n const taxCents =\n Number.isFinite(scalarTax) && scalarTax > 0\n ? scalarTax\n : // `total_taxes` replaced `total_tax_amounts`, which replaced the\n // scalar; they do not coexist, so this cannot double-count.\n sumAmounts(invoice?.total_taxes ?? invoice?.total_tax_amounts)\n return {\n lineItems,\n totals: computeCheckoutSessionTotals(\n lineItems,\n {\n amount_total: invoice?.amount_paid,\n total_details: {\n amount_tax: taxCents,\n amount_shipping: metadataCents(invoice?.shipping_cost?.amount_total),\n amount_discount: sumAmounts(invoice?.total_discount_amounts),\n },\n },\n { feeCents: metadataCents(invoice?.application_fee_amount) },\n ),\n }\n}\n\n/** Display form: `#1042`; falls back to a doc-id stub for legacy rows. */\nexport function formatOrderNumber(order: Pick<HostOrder, 'number'>, docId?: string): string {\n if (order.number != null) return `#${order.number}`\n return docId ? `#${docId.slice(-6).toUpperCase()}` : '#—'\n}\n\n/**\n * Lifts a legacy Commerce Starter order row (flat productId/amountCents)\n * into the v1 shape for display; already-shaped orders pass through.\n */\nexport function liftLegacyOrder(raw: Partial<HostOrder>): HostOrder {\n if (Array.isArray(raw.lineItems) && raw.lineItems.length > 0) {\n return { status: 'paid', ...raw } as HostOrder\n }\n const amountCents = Number(raw.amountCents ?? 0)\n return {\n ...raw,\n status: raw.status ?? 'paid',\n channel: raw.channel ?? 'online',\n lineItems: raw.productId\n ? [\n {\n productId: raw.productId,\n name: 'Product',\n quantity: 1,\n unitAmountCents: amountCents,\n },\n ]\n : [],\n totals: raw.totals ?? {\n itemsCents: amountCents,\n shippingCents: 0,\n taxCents: 0,\n discountCents: 0,\n feeCents: Number(raw.feeCents ?? 0),\n totalCents: amountCents,\n },\n }\n}\n\n/**\n * Does this order contain `productId`?\n *\n * Line items first, legacy flat `productId` as the fallback — the same test\n * `gate.ts`, `download.ts` and `reviews.ts` already apply. Only the two\n * buy-now-shaped Stripe paths ever write the flat field, so a legacy-only\n * check silently excludes every cart, POS and draft order.\n */\nexport function orderContainsProduct(\n order: Partial<HostOrder>,\n productId: string,\n): boolean {\n return (\n (order.lineItems ?? []).some((line) => line.productId === productId) ||\n order.productId === productId\n )\n}\n\n/**\n * Line indexes already covered by a recorded fulfillment (AGL-2455).\n *\n * The set the supplier callback and the console's own fulfil both have to\n * reason about, and the reason `status` alone could not: `supplier-update.ts`\n * wrote `status: 'fulfilled'` as a LITERAL, so the first supplier to post\n * tracking on a two-supplier order marked every line shipped — including lines\n * they have never seen — and the second supplier's POST met a 409 because\n * `fulfilled` cannot transition to `fulfilled`. The buyer was told their whole\n * order shipped with one carrier and one tracking number.\n */\nexport function coveredLineItemIds(order: Partial<HostOrder>): Set<number> {\n const covered = new Set<number>()\n for (const fulfillment of order.fulfillments ?? []) {\n for (const index of fulfillment?.lineItemIds ?? []) {\n const line = Math.round(Number(index))\n if (Number.isFinite(line) && line >= 0) covered.add(line)\n }\n }\n return covered\n}\n\n/** Distinct suppliers with at least one line on this order (AGL-2455). */\nexport function orderSupplierIds(order: Partial<HostOrder>): string[] {\n return [\n ...new Set(\n (order.lineItems ?? [])\n .map((line) => String(line.supplierId ?? ''))\n .filter(Boolean),\n ),\n ]\n}\n\n/** Line indexes belonging to one supplier (AGL-2455). */\nexport function supplierLineItemIds(\n order: Partial<HostOrder>,\n supplierId: string,\n): number[] {\n return (order.lineItems ?? [])\n .map((line, index) => ({ line, index }))\n .filter(({ line }) => String(line.supplierId ?? '') === supplierId)\n .map(({ index }) => index)\n}\n\n/**\n * The status an order reaches once `covered` lines are shipped (AGL-2455).\n *\n * COMPUTED from the fulfillment set rather than written as a literal, which is\n * the whole of this issue's third requirement. `partially_fulfilled` has existed\n * in `ORDER_TRANSITIONS` since orders shipped and nothing ever wrote it.\n *\n * An order with no lines at all reads as fulfilled — there is nothing left to\n * ship, and answering `partially_fulfilled` would strand it forever.\n */\nexport function statusAfterFulfilling(\n order: Partial<HostOrder>,\n covered: Set<number>,\n): OrderStatus {\n const lines = order.lineItems ?? []\n if (lines.length === 0) return 'fulfilled'\n return lines.every((_line, index) => covered.has(index))\n ? 'fulfilled'\n : 'partially_fulfilled'\n}\n\n/**\n * Statuses under which an order grants NO entitlement at all (AGL-2454).\n *\n * `pending` has not paid, `cancelled` was undone, `refunded` was given back.\n * The literal list was copied into `download.ts`, `gate.ts`,\n * `membership-account.ts` and `reviews.ts`; it lives here now so a change to\n * what \"withdrawn\" means reaches all of them at once.\n */\nconst WITHDRAWN_ORDER_STATUSES: OrderStatus[] = [\n 'pending',\n 'cancelled',\n 'refunded',\n]\n\n/**\n * How much of this order has been given back (AGL-2454).\n *\n * `full` follows `status` OR the money, because the two can legitimately\n * disagree for a moment: `refund.ts` reserves `refundedCents` before it calls\n * Stripe and flips `status` after, and a lost dispute reverses money on an\n * order whose status is still `paid` until it closes. Reading only the status\n * would treat that window as \"nothing refunded\".\n */\nexport function orderRefundState(\n order: Partial<HostOrder>,\n): 'none' | 'partial' | 'full' {\n const refundedCents = Math.max(0, Number(order.refundedCents ?? 0))\n const totalCents =\n order.totals?.totalCents ?? Number(order.amountCents ?? 0)\n if (order.status === 'refunded') return 'full'\n if (!(refundedCents > 0)) return 'none'\n return totalCents > 0 && refundedCents >= totalCents ? 'full' : 'partial'\n}\n\n/**\n * Is this line's entitlement withdrawn?\n *\n * By INDEX, matching `refundedLineItemIds`. A cart can hold the same product on\n * two lines and only one may be coming back, so a product-keyed test would\n * revoke goods the buyer still owns.\n */\nexport function orderLineRefunded(\n order: Partial<HostOrder>,\n lineIndex: number,\n): boolean {\n return (order.refundedLineItemIds ?? []).includes(lineIndex)\n}\n\n/**\n * THE ONE ENTITLEMENT TEST (AGL-2454). Does this order still entitle the buyer\n * to `productId`?\n *\n * Five gates asked this question and all five asked it as\n * `!['pending','cancelled','refunded'].includes(status) && orderContainsProduct(…)`.\n * That reading is why a partial refund revoked NOTHING: `refund.ts` writes\n * `status: 'refunded'` only when the order is FULLY refunded, so a 99%-refunded\n * order stayed `paid` and every gate said yes — downloads, licence keys, gated\n * content and the right to post a verified review, all still live.\n *\n * Withdrawn now means either of two things, and both had to be expressible in\n * one place or the five gates would drift again:\n *\n * - the whole order is withdrawn by `status`, as before; or\n * - every line carrying this product was refunded BY NAME, which is the case a\n * line-scoped refund creates and the only case where an amount can honestly\n * be attributed to goods.\n *\n * `'any'` asks whether the order still entitles ANYTHING, which is what the\n * membership paywall's subscription-shaped probe needs.\n *\n * The legacy flat `productId` order (one line, no `lineItems`) is lifted by\n * `liftLegacyOrder` before it gets here, so index 0 is its only line and a\n * line-scoped refund of it behaves like a full one.\n */\nexport function orderEntitlesProduct(\n order: Partial<HostOrder>,\n productId: string | 'any',\n): boolean {\n if (WITHDRAWN_ORDER_STATUSES.includes(order.status as OrderStatus)) {\n return false\n }\n const lines = order.lineItems ?? []\n if (productId === 'any') {\n if (lines.length === 0) return true\n return lines.some((_line, index) => !orderLineRefunded(order, index))\n }\n const matching = lines\n .map((line, index) => ({ line, index }))\n .filter(({ line }) => line.productId === productId)\n if (matching.length === 0) {\n // The legacy flat shape, which `liftLegacyOrder` normally converts. Kept so\n // an unlifted row is not silently un-entitled — a false negative here locks\n // a paying buyer out of goods they bought.\n return order.productId === productId\n }\n return matching.some(({ index }) => !orderLineRefunded(order, index))\n}\n\n/**\n * Product ids whose entitlement this order has withdrawn line by line\n * (AGL-2454) — every line for the product refunded, none left standing.\n *\n * What `refund.ts` uses to decide which licence keys to RETIRE. Not \"any line\n * refunded\": a buyer who returned one of two copies still holds the product.\n */\nexport function refundedProductIds(order: Partial<HostOrder>): string[] {\n const lines = order.lineItems ?? []\n const ids = new Set<string>()\n for (const line of lines) {\n if (!ids.has(line.productId) && !orderEntitlesProduct(order, line.productId)) {\n ids.add(line.productId)\n }\n }\n return [...ids]\n}\n\n/**\n * The refund line the console and the merchant read (AGL-2454).\n *\n * Kept in the model because three surfaces show it and \"partially refunded\"\n * must not mean different things on two of them. Empty for an order with\n * nothing refunded — there is nothing to say.\n */\nexport function orderRefundSummary(order: Partial<HostOrder>): string {\n const state = orderRefundState(order)\n if (state === 'none') return ''\n const refundedCents = Math.max(0, Number(order.refundedCents ?? 0))\n const totalCents = order.totals?.totalCents ?? Number(order.amountCents ?? 0)\n if (state === 'full') return `Refunded in full ($${usd(refundedCents)})`\n const revoked = (order.refundedLineItemIds ?? []).length\n const lines = (order.lineItems ?? []).length\n const scope =\n revoked > 0\n ? `${revoked} of ${lines} line${lines === 1 ? '' : 's'} withdrawn`\n : 'no lines withdrawn — refunded by amount'\n return `Partially refunded ($${usd(refundedCents)} of $${usd(\n totalCents,\n )}) — ${scope}`\n}\n\n/** `12345` -> `123.45`, the CSV's money format. */\nfunction usd(cents: number): string {\n return (cents / 100).toFixed(2)\n}\n\n/**\n * Display labels for {@link OrderStatus}, and the MUI chip colour each one\n * carries (AGL-2136). The orders screen advertised on `/product/commerce`\n * shows the status as a COLOURED pill — `Paid`, `Fulfilled`, `Refunded` in\n * three different colours — where the console rendered every status as the\n * same default outlined chip, so the one column a merchant scans first\n * carried no signal at all.\n *\n * Kept in the model rather than in the card because three surfaces render a\n * status (the orders list, the detail dialog and the dashboard glance\n * widget) and a colour that means \"refunded\" on one of them must not mean\n * something else on the next.\n */\nexport const ORDER_STATUS_LABELS: Record<OrderStatus, string> = {\n pending: 'Pending',\n paid: 'Paid',\n partially_fulfilled: 'Partly fulfilled',\n fulfilled: 'Fulfilled',\n delivered: 'Delivered',\n cancelled: 'Canceled',\n refunded: 'Refunded',\n}\n\n/**\n * Chip colour per status. `pending` is deliberately `default` rather than\n * `warning`: an unpaid order is not a problem, it is simply not money yet,\n * and reserving the alarming colours for `cancelled`/`refunded` is what\n * makes them readable at a glance.\n */\nexport const ORDER_STATUS_COLOR: Record<\n OrderStatus,\n 'default' | 'success' | 'info' | 'warning' | 'error'\n> = {\n pending: 'default',\n paid: 'success',\n partially_fulfilled: 'warning',\n fulfilled: 'info',\n delivered: 'info',\n cancelled: 'default',\n refunded: 'error',\n}\n\n/** Display labels for {@link OrderChannel}. */\nexport const ORDER_CHANNEL_LABELS: Record<OrderChannel, string> = {\n online: 'Online',\n pos: 'POS',\n draft: 'Draft',\n subscription: 'Subscription',\n}\n\n/** Human label for a channel, tolerating a legacy/unknown value. */\nexport function orderChannelLabel(channel: string | undefined): string {\n return ORDER_CHANNEL_LABELS[(channel ?? 'online') as OrderChannel] ?? channel ?? 'Online'\n}\n\n/**\n * Net cents an order actually earned: the charged total less anything\n * refunded. The three commerce surfaces each had their own inline copy of\n * this expression; a refunded order counted as revenue on whichever one\n * was written first.\n */\nexport function orderNetCents(order: Partial<HostOrder>): number {\n const gross = Number(\n (order as { totals?: { totalCents?: number } }).totals?.totalCents ??\n (order as { amountCents?: number }).amountCents ??\n 0,\n )\n return gross - Number((order as { refundedCents?: number }).refundedCents ?? 0)\n}\n\n/** Milliseconds an order was created at, across every writer's field shape. */\nexport function orderCreatedAtMs(order: Partial<HostOrder> & {\n createdAtMs?: number\n createdAt?: { seconds?: number; toDate?: () => Date }\n}): number {\n if (typeof order.createdAtMs === 'number') return order.createdAtMs\n const seconds = order.createdAt?.seconds\n if (typeof seconds === 'number') return seconds * 1000\n const date = order.createdAt?.toDate?.()\n return date ? date.getTime() : 0\n}\n\n/** One window's totals, plus how it moved against the window before it. */\nexport interface OrderWindowSummary {\n /** Net revenue in cents over the window. */\n revenueCents: number\n /** Orders counted in the window. */\n orders: number\n /** Average order value in cents, 0 when the window is empty. */\n aovCents: number\n /**\n * Percentage change vs the immediately preceding window of the same\n * length, rounded to one decimal. `null` when the prior window is EMPTY —\n * \"+100%\" against zero is not a growth rate, it is a first sale, and the\n * tile must say nothing rather than say something false.\n */\n revenueDeltaPct: number | null\n ordersDeltaPct: number | null\n aovDeltaPct: number | null\n}\n\nconst ORDER_WINDOW_DAY_MS = 24 * 60 * 60 * 1000\n\n/**\n * Percentage change from `previous` to `current`, or `null` when there is\n * no baseline to divide by.\n */\nfunction deltaPct(current: number, previous: number): number | null {\n if (!previous) return null\n return Math.round(((current - previous) / previous) * 1000) / 10\n}\n\n/**\n * Summarises a window of orders and the window before it (AGL-2136).\n *\n * Every commerce mockup we advertise shows the money tiles carrying a\n * period-over-period delta — `Revenue $26,540 +8.1%` — and the product had\n * no prior-window computation anywhere in the repo, so no surface could\n * have rendered one.\n *\n * `pending` and `cancelled` orders are excluded, matching the analytics\n * card: neither is money. Refunds are subtracted rather than dropped, so a\n * refund inside the window pushes the delta DOWN, which is the whole point\n * of showing it.\n */\nexport function summarizeOrderWindow(\n orders: readonly (Partial<HostOrder> & {\n createdAtMs?: number\n createdAt?: { seconds?: number; toDate?: () => Date }\n })[],\n options: { nowMs: number; days?: number } ,\n): OrderWindowSummary {\n const days = options.days ?? 30\n const spanMs = days * ORDER_WINDOW_DAY_MS\n const { nowMs } = options\n let revenueCents = 0\n let count = 0\n let priorRevenueCents = 0\n let priorCount = 0\n for (const order of orders) {\n const lifted = liftLegacyOrder(order)\n if (lifted.status === 'pending' || lifted.status === 'cancelled') continue\n const age = nowMs - orderCreatedAtMs(order)\n if (age < 0) continue\n if (age < spanMs) {\n revenueCents += orderNetCents(order)\n count += 1\n } else if (age < spanMs * 2) {\n priorRevenueCents += orderNetCents(order)\n priorCount += 1\n }\n }\n const aovCents = count ? Math.round(revenueCents / count) : 0\n const priorAovCents = priorCount\n ? Math.round(priorRevenueCents / priorCount)\n : 0\n return {\n revenueCents,\n orders: count,\n aovCents,\n revenueDeltaPct: deltaPct(revenueCents, priorRevenueCents),\n ordersDeltaPct: deltaPct(count, priorCount),\n aovDeltaPct: deltaPct(aovCents, priorAovCents),\n }\n}\n\n/** Appends a timeline event immutably (webhook + console share this). */\nexport function appendOrderEvent(\n order: Pick<HostOrder, 'timeline'>,\n event: string,\n detail?: string,\n atMs = Date.now(),\n): OrderTimelineEvent[] {\n return [...(order.timeline ?? []), { atMs, event, ...(detail ? { detail } : {}) }]\n}\n"],"names":["stripeIdIsTestMode","ORDER_TRANSITIONS","pending","paid","partially_fulfilled","fulfilled","delivered","cancelled","refunded","canTransitionOrder","from","to","includes","wholeCents","value","cents","Math","round","Number","isFinite","apportionCents","weights","totalCents","safe","map","weight","max","basis","reduce","sum","total","length","pot","min","exact","shares","floor","remainder","share","order","index","fraction","sort","a","b","entry","orderLineRefundCents","lineIndexes","lines","lineItems","gross","line","unitAmountCents","quantity","totals","discountCents","wanted","Set","filter","isInteger","orderIsTestMode","livemode","checkoutSessionId","$id","computeOrderTotals","parts","itemsCents","shippingCents","taxCents","feeCents","computeCheckoutSessionTotals","session","details","total_details","amount_tax","lineItemTaxCents","amount_shipping","amount_discount","pricedInDiscountCents","amountTotal","amount_total","NaN","metadataCents","computeBuyNowOrder","snapshot","metadata","rawQuantity","goodsCents","variantId","String","productId","name","variantLabel","sku","productType","supplierId","subscriptionInvoiceFeeBasisCents","invoice","scalarTax","tax","sumAmounts","total_taxes","total_tax_amounts","shipping_cost","subscriptionInvoiceItemsOnlyFeeCents","chargedCents","application_fee_amount","basisCents","subscriptionRecurringBasisCents","soldLineItems","reason","billing_reason","billsTheFullPrice","cycleBasis","unit","list","amount","lineInterval","interval","price","recurring","undefined","selectSubscriptionInvoiceLine","data","find","proration","subscriptionInvoiceInterval","computeSubscriptionInvoiceOrder","subscriptionLine","listUnit","unit_amount","isSubscriptionLine","push","description","slice","amount_paid","total_discount_amounts","formatOrderNumber","docId","number","toUpperCase","liftLegacyOrder","raw","Array","isArray","status","amountCents","channel","orderContainsProduct","some","coveredLineItemIds","covered","fulfillment","fulfillments","lineItemIds","add","orderSupplierIds","Boolean","supplierLineItemIds","statusAfterFulfilling","every","_line","has","WITHDRAWN_ORDER_STATUSES","orderRefundState","refundedCents","orderLineRefunded","lineIndex","refundedLineItemIds","orderEntitlesProduct","matching","refundedProductIds","ids","orderRefundSummary","state","usd","revoked","scope","toFixed","ORDER_STATUS_LABELS","ORDER_STATUS_COLOR","ORDER_CHANNEL_LABELS","online","pos","draft","subscription","orderChannelLabel","orderNetCents","orderCreatedAtMs","createdAtMs","seconds","createdAt","date","toDate","getTime","ORDER_WINDOW_DAY_MS","deltaPct","current","previous","summarizeOrderWindow","orders","options","days","spanMs","nowMs","revenueCents","count","priorRevenueCents","priorCount","lifted","age","aovCents","priorAovCents","revenueDeltaPct","ordersDeltaPct","aovDeltaPct","appendOrderEvent","event","detail","atMs","Date","now","timeline"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-orders.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 * Orders model v1 (AGL-283): line items snapshot the product/variant at\n * purchase time (renames and price changes never rewrite history), a\n * small status machine gates transitions, and totals are integer cents.\n * Docs live at `hosts/{hostId}/orders/{id}`; the Stripe webhook creates\n * them and the orders console (AGL-287) drives transitions. Pure — no\n * I/O here.\n */\n\nimport type { OrderLineModifier } from './product-modifiers'\nimport { stripeIdIsTestMode } from '@aglyn/aglyn/app-utils/stripe-deployment-mode'\nimport type { PaymentRisk } from '@aglyn/aglyn/app-utils/payment-risk'\nimport type { ProductType } from './commerce'\nimport type { StorefrontTaxMode } from './commerce-tax-decision'\nimport { lineRequiresShipping, orderLineFulfillmentStates } from './order-fulfillment'\nimport type { OrderPayment } from './commerce-pos'\n\nexport type OrderStatus =\n | 'pending'\n | 'paid'\n | 'partially_fulfilled'\n | 'fulfilled'\n | 'delivered'\n | 'cancelled'\n | 'refunded'\n\n/**\n * Where the sale came through. `subscription` is a recurring cycle of a\n * PHYSICAL subscription product (AGL-1750): each paid invoice mints one order\n * so fulfilment has something to pick, pack and label against, and so\n * recurring revenue reaches the surfaces that read `orders`. Digital and\n * service subscriptions still record on the subscription document alone —\n * there is nothing to ship, and AGL-1732's \"a subscription is not an order\"\n * stands for them.\n */\nexport type OrderChannel = 'online' | 'pos' | 'draft' | 'subscription' | 'marketplace'\n\n/**\n * Where an order sold on an outside channel came from (AGL-3638): a\n * marketplace that took the buyer's money and pays the merchant out. Written\n * once, by the import that recorded the order (`server/channel-orders.ts`).\n */\nexport interface OrderChannelSource {\n /** The importer's id for the channel, e.g. `ebay`. */\n channelId: string\n /** What the merchant calls it, e.g. `eBay`. */\n channelLabel: string\n /** The channel's own order id, the import's idempotency key. */\n externalOrderId: string\n /** What the merchant sees on the channel. */\n externalRef: string\n /** Which order line each of the channel's lines became, for a shipment sent back to it. */\n lines: Array<{ lineIndex: number; externalLineId: string }>\n /** ISO 4217, upper case: the currency every amount on the order is in. */\n currency: string\n /** The channel collected this order's tax and remits it, as the marketplace facilitator. */\n taxRemittedByChannel: true\n /**\n * What the channel charged the merchant for the sale, RECORDED for the\n * merchant's books and never charged by Aglyn; `null` until the channel\n * says.\n */\n fees: Array<{ label: string; amountCents: number }> | null\n feesTotalCents: number | null\n /**\n * How the goods leave (AGL-3644): `courier`, handed to the channel's own\n * courier (a delivery app); absent reads as `ship`.\n */\n handoff?: 'ship' | 'courier'\n /** The channel's ids of the refunds and adjustments recorded on the order, so each is recorded once (AGL-3644). */\n refundIds?: string[]\n}\n\n/** Snapshot of what was bought — self-contained for history. */\nexport interface OrderLineItem {\n productId: string\n variantId?: string\n /** Display snapshot at purchase time. */\n name: string\n variantLabel?: string\n sku?: string\n productType?: ProductType\n quantity: number\n /** Per-unit price in cents at purchase time, its modifiers included. */\n unitAmountCents: number\n /**\n * Choices added at the register (AGL-3607), as sold. Already folded into\n * `variantLabel` and `unitAmountCents`; kept for reports and reorders.\n */\n modifiers?: OrderLineModifier[]\n /** Supplier at purchase time (dropship routing, AGL-289). */\n supplierId?: string\n /** Fulfillment id once this line ships (AGL-288). */\n fulfillmentId?: string\n}\n\nexport interface OrderTotals {\n itemsCents: number\n shippingCents: number\n taxCents: number\n /** Positive number subtracted from the total. */\n discountCents: number\n totalCents: number\n /** Aglyn platform fee (Connect application fee, AGL-278/307). */\n feeCents: number\n /**\n * Gratuity taken at the register (AGL-3607), on top of `totalCents` and\n * never inside it: a tip is the merchant's, not a sale, so it stays out of\n * revenue figures and out of the platform's take.\n */\n tipCents?: number\n /**\n * Optional lines the buyer added at checkout from another plugin's offer\n * (AGL-3635) — package protection — summed. Inside `totalCents`, never in\n * `itemsCents`: an extra is not goods, so no fulfillment, stock or\n * platform take reads it.\n */\n extrasCents?: number\n}\n\n/**\n * One optional line the buyer took at checkout (AGL-3635), as core's\n * checkout-extras seam records it: whose offer it was, what it was called\n * and what it cost. Never a line item: it does not ship and is never\n * refunded by line.\n */\nexport interface OrderExtra {\n /** `{pluginId}.{key}`. */\n id: string\n pluginId: string\n key: string\n label: string\n amountCents: number\n /** The offering plugin's quote id. */\n quoteRef?: string\n}\n\nexport interface OrderAddress {\n name?: string\n line1?: string\n line2?: string\n city?: string\n state?: string\n postalCode?: string\n country?: string\n phone?: string\n}\n\nexport interface OrderTimelineEvent {\n atMs: number\n /** Machine event key, e.g. 'paid', 'fulfilled', 'refund', 'note'. */\n event: string\n /** Human-readable detail shown in the console timeline. */\n detail?: string\n}\n\nexport interface OrderFulfillment {\n id: string\n /** The lines this fulfillment touches; `lines` says how many units of each. */\n lineItemIds: number[]\n /**\n * Units per line (AGL-3611). Absent on a fulfillment written before\n * quantities, which reads as every unit of each line in `lineItemIds`.\n */\n lines?: Array<{ lineItemId: number; quantity: number }>\n carrier?: string\n trackingNumber?: string\n trackingUrl?: string\n /** A shipping label bought for this parcel, when a shipping plugin bought one. */\n labelUrl?: string\n /** The shipping plugin's id for that label (AGL-3612), so a retry finds this shipment. */\n labelRef?: string\n /**\n * Where the carrier says the parcel is (AGL-3612): `pre_transit`,\n * `in_transit`, `out_for_delivery`, `delivered`, `exception` or `returned`,\n * written by a tracking webhook through the shipment-records seam.\n */\n trackingStatus?: string\n trackingStatusAtMs?: number\n /** Absent reads as `active`; a cancelled fulfillment ships nothing. */\n status?: 'active' | 'cancelled'\n cancelledAtMs?: number\n updatedAtMs?: number\n /** Whether the buyer was to be told about this shipment. */\n notify?: boolean\n atMs: number\n}\n\n/**\n * A card dispute against this order's charge (AGL-1787).\n *\n * The DISTINCTION a chargeback carries, kept off `status` on purpose. A lost\n * dispute leaves the order `refunded` — that is what every reader of the status\n * already means by it, and five entitlement gates (`gate.ts`, `download.ts`,\n * `reviews.ts`, `membership-account.ts` and the glance card) match on the\n * literal `'refunded'`, so a new terminal status would have left the shopper\n * their digital downloads and their verified-purchase review. That is AGL-1546\n * reproduced on the tenant side, and the reason this record sits BESIDE the\n * status rather than replacing it: the money question is answered by `status`\n * and `refundedCents`, the \"by what door\" question by this.\n *\n * Written whole rather than merged into, so a second dispute on the same charge\n * cannot inherit the previous one's `outcome` — see the handler.\n */\nexport interface OrderDispute {\n /** Stripe dispute id (`dp_…`). */\n id: string\n /** Stripe's dispute status verbatim, e.g. `needs_response`, `lost`. */\n status: string\n /** Stripe's reason code, e.g. `fraudulent`, `product_not_received`. */\n reason?: string\n /** Disputed cents — a partial dispute is less than the order total. */\n amountCents: number\n openedAtMs: number\n /** Evidence deadline, when Stripe supplied one. */\n evidenceDueByMs?: number\n closedAtMs?: number\n /** `won` | `lost` | `warning_closed`, set once the dispute closes. */\n outcome?: string\n /**\n * Cents this dispute actually reversed on the order — a LOST dispute only,\n * and capped against what was left, so it is not always `amountCents`.\n */\n reversedCents?: number\n /**\n * Stripe transfer-reversal id (`trr_…`) that pulled the seller's share back\n * from the connected account (AGL-1794) — set only when a reversal was\n * actually created or found already sitting on the transfer.\n */\n transferReversalId?: string\n /**\n * Cents pulled back from the CONNECTED account for this lost dispute\n * (AGL-1794). Its presence — 0 included — is the settle marker for the\n * reversal step: 0 means the step ran and found nothing to reverse (no\n * transfer on the charge, or a transfer with nothing left), recorded so a\n * redelivery does not retry a failure no redelivery can fix.\n */\n reversedTransferCents?: number\n}\n\n/**\n * One line whose stock a reversal MAY need to put back (AGL-1797).\n *\n * Only lines the sale actually DECREMENTED appear here — an untracked variant\n * (`inventory == null`) had nothing taken off it, so it has nothing to return,\n * and a digital line is untracked for that same reason rather than by a\n * type test of its own. A merchant who tracks license stock on a digital\n * product does get it back.\n */\nexport interface OrderRestockLine {\n productId: string\n variantId: string\n /**\n * Units this line sold. It is the MOST that can come back, never a claim\n * that they did: on a partial reversal the merchant reversed some of the\n * money and only they know which goods returned.\n */\n quantity: number\n /** Purchase-time snapshots, so a reader renders the prompt with no product read. */\n name?: string\n variantLabel?: string\n /**\n * Which `lineItems` entry this came from, matching `refundedLineItemIds`\n * (AGL-2325).\n *\n * The index rather than the product, because a cart can hold one product on\n * two lines and a line-scoped refund may name only one of them. It is what\n * lets a SECOND partial refund be told apart from a repeat of the first: a\n * reversal naming a line the open question already covers adds nothing, and\n * one naming a line it misses is a new question rather than noise.\n *\n * Absent on a check written before this shipped, and that absence is read as\n * \"covers the whole order\" — the assumption those checks were written under.\n */\n lineIndex?: number\n}\n\n/**\n * Stock left off the shelf by a reversed order, FLAGGED rather than released\n * (AGL-1797).\n *\n * The checkout webhook decrements variant inventory on a sale and nothing put\n * it back, so a fully reversed order read one unit light forever and the error\n * compounded with every return. The fix is not the obvious increment, because\n * an increment is wrong more often than it is right:\n *\n * - a **returned** item genuinely comes back, and stock should rise — but only\n * once it is RECEIVED, and there is no fulfilment event that records receipt\n * (`OrderFulfillment` has no returned state);\n * - a **refund with no return** — goodwill, damaged, lost in post — leaves the\n * goods gone, so incrementing invents stock the merchant does not have and\n * sells something that is not on the shelf. That is worse than the bug it\n * replaces: under-counting refuses a sale, over-counting takes one it cannot\n * fill;\n * - a **chargeback** is the clearest do-not-restock case of all, since the\n * shopper kept the item and took the money back.\n *\n * And the quantities are not always knowable on a partial reversal: a refund\n * requested as an AMOUNT names no line at all, so \"$17 of a $62 order\" selects\n * nothing. `quantity` is an upper bound there and `fullyReversed` says whether\n * it is a tight one.\n *\n * A refund that DID name its lines is scoped to them (AGL-2325). `refund.ts`\n * records `refundedLineItemIds` with the money (AGL-2454), so the flag asks\n * about the lines that reversal withdrew rather than about the whole order —\n * and `lineIndex` on each line is what lets a second partial withdrawing a\n * DIFFERENT line re-ask, instead of being swallowed by the still-open question\n * about the first.\n *\n * So this records the question instead of guessing the answer, and the merchant\n * answers it from the stock adjustment they already have. The release action is\n * NOT rebuilt here: the products hub's \"Adjust stock\" already writes the\n * variant counts and an `InventoryAdjustment` row with a reason.\n */\nexport interface OrderRestockCheck {\n /** Which door the money left by; the two deserve different default wording. */\n kind: 'refund' | 'chargeback'\n /** Inventory-tracked lines only — exactly what the sale decremented. */\n lines: OrderRestockLine[]\n /** Sum of `lines[].quantity`, denormalized so a badge needs no arithmetic. */\n units: number\n /**\n * False when only part of the money came back, which is what makes\n * `quantity` an upper bound rather than a proposal.\n */\n fullyReversed: boolean\n flaggedAtMs: number\n /**\n * Set once a merchant answers. Absent means the question is still open, and\n * it is the only state a fresh reversal will overwrite.\n */\n resolution?: 'restocked' | 'dismissed'\n resolvedAtMs?: number\n /** Console uid that answered. */\n resolvedBy?: string\n}\n\n/**\n * A cart line a paid order could not record (AGL-2149). All that survives a\n * deleted product is what the shopper's cart asked for.\n */\nexport interface OrderUnresolvedLine {\n productId: string\n variantId?: string\n quantity: number\n}\n\n/** `hosts/{hostId}/orders/{id}` doc. */\nexport interface HostOrder {\n /** Human order number, sequential per host (e.g. #1042). */\n number?: number\n /** Optional lines the buyer added at checkout (AGL-3635); see {@link OrderExtra}. */\n extras?: OrderExtra[]\n status: OrderStatus\n channel?: OrderChannel\n /** The outside channel an order with channel `marketplace` was sold on (AGL-3638). */\n channelSource?: OrderChannelSource\n /**\n * Which location's stock this sale came off, for the multi-location counts\n * of AGL-286. Written by the POS register, the only sale path that decrements\n * a location bucket rather than the flat count — and read back when the order\n * is cancelled (AGL-1808), because putting the units on the flat total when\n * the sale took them out of a bucket leaves the two disagreeing, and the next\n * location-aware write recomputes the total from the buckets and silently\n * erases the restock.\n */\n locationId?: string\n /**\n * The register a POS sale was rung on, and the console user who rang it\n * (AGL-472, AGL-3607).\n */\n registerId?: string\n cashierId?: string\n /**\n * The register shift the sale was rung in (AGL-3609), which the shift's X\n * and Z reports count it under.\n */\n shiftId?: string\n /**\n * The person the cashier attached at the register (AGL-3609), as the\n * person-records seam names them: which record system, and its id there.\n */\n customerRecord?: { kind: string; id: string }\n /**\n * Every payment toward a register sale (AGL-3607): one per tender, until\n * the balance due is zero. Read through `orderPayments`, which infers one\n * payment for an order written before the ledger existed.\n */\n payments?: OrderPayment[]\n /** How the customer asked for their receipt at the register (AGL-3608). */\n receiptRequest?: { channel: 'email' | 'sms' | 'print' | 'none'; to?: string; atMs: number }\n /**\n * The register discount that was applied, and the member who applied it\n * (AGL-2161). Present only on a POS order that carries a discount.\n *\n * `totals.discountCents` records that money came off; these record who\n * decided it should. Without them a full comp read exactly like a\n * correctly-priced sale after the fact, on the one channel where a person is\n * standing in front of the goods and can ring whatever they like. The\n * ceiling that bounds `discountPct` is the org's `posMaxDiscountPct` plugin\n * setting, enforced server-side in `pos-order.ts`.\n */\n discountPct?: number\n discountBy?: string\n lineItems?: OrderLineItem[]\n totals?: OrderTotals\n /**\n * WHICH TAX REGIME this order carried (AGL-2451), resolved at the moment the\n * sale decided it and stamped here rather than re-derived later.\n *\n * `totals.taxCents` says how much tax was charged and nothing about who\n * computed it. Those are different facts about whose registration the money\n * is held under: a `stripe-automatic` storefront sale's tax is computed\n * against AGLYN's registrations and settles into Aglyn's balance (MEASURED —\n * see `server/storefront-tax.ts`), while a `manual` one is the merchant's own\n * rate on their own origin. An order that cannot say which one it carried\n * cannot be reconciled against the return, or corrected afterwards.\n *\n * AGL-2440's merchant report reads the authoritative\n * `storefrontTaxCollected` record; this is the same fact on the single order,\n * and both come from `storefrontTaxMode` so the two cannot drift.\n *\n * ## ABSENT MEANS \"NOT RECORDED\" — AND NOTHING MAY DEFAULT IT\n *\n * Every order written before AGL-2451 shipped genuinely has no stamp, and\n * that is the honest reading. `liftLegacyOrder` deliberately does NOT\n * synthesise one the way it synthesises `totals.taxCents: 0`: it runs on the\n * READ path, its result is spread into merge writes, and a defaulted `none`\n * would overwrite a real `stripe-automatic` — the converter-on-partial-writes\n * hazard, in a place where the destroyed value is a tax fact. The console\n * renders an absent mode as unknown, never as `none`\n * (`describeOrderTaxMode`).\n */\n taxMode?: StorefrontTaxMode\n customerEmail?: string | null\n customerName?: string | null\n /**\n * The buyer's phone for order texts (AGL-3610), written only by a flow that\n * asked for it for that purpose — never copied from a shipping address,\n * which a buyer gives the carrier, not the store.\n */\n customerPhone?: string | null\n /**\n * Which buyer messages this order has sent (AGL-3610), keyed by\n * `buyerNotificationMarker` — the idempotency record `notifyOrderBuyer`\n * claims in a transaction before it sends, so a retried webhook or a second\n * click never mails the buyer twice.\n */\n buyerNotifications?: Record<\n string,\n { state: 'sending' | 'sent'; channel: 'email' | 'sms'; atMs: number }\n >\n /** Storefront customer id once accounts exist (AGL-294). */\n customerId?: string\n shippingAddress?: OrderAddress\n billingAddress?: OrderAddress\n timeline?: OrderTimelineEvent[]\n fulfillments?: OrderFulfillment[]\n note?: string\n couponCode?: string\n /** Stripe references for refunds. */\n paymentIntentId?: string\n checkoutSessionId?: string\n /**\n * The recurring cycle this order fulfils (channel `subscription`,\n * AGL-1750). The order's own doc id is the invoice id — that identity is\n * the redelivery key — and these carry the join back to\n * `subscriptions/{subscriptionId}/invoices/{invoiceId}`.\n */\n subscriptionId?: string\n invoiceId?: string\n /** Draft orders (AGL-287): the link sent to the buyer. */\n paymentLinkUrl?: string\n refundedCents?: number\n /**\n * Line indexes an admin refunded BY NAME (AGL-2454), and therefore the lines\n * whose digital entitlements are withdrawn.\n *\n * A refund carries an AMOUNT, not lines — `refund.ts` takes `amountCents`\n * and Stripe knows nothing about what was in the basket — so a partial refund\n * cannot in general be attributed to anything. That is why five entitlement\n * gates matched the literal `'refunded'` on `status` and a 99%-refunded order\n * kept every download, licence key, gated page and verified review.\n *\n * This is the case where attribution IS possible: the console refund dialog\n * lets an admin pick the lines they are refunding, and when it does the\n * amount is computed FROM those lines rather than typed beside them. An\n * amount-only refund still revokes nothing per-line — deciding for the\n * merchant which lines a bare figure covers would be a guess about their\n * goods — but it is no longer silent: `orderRefundState` reports it, the\n * console shows it, and the order timeline records it.\n *\n * Indexes rather than product ids, because a cart can hold two lines of the\n * same product (different variants) and only one may be coming back.\n */\n refundedLineItemIds?: number[]\n /** The card dispute against this charge, open or settled (AGL-1787). */\n dispute?: OrderDispute\n /**\n * Stripe's fraud signals on this order's payment (AGL-3360): an issuer's\n * early fraud warning or a Radar review, stamped by the billing webhook\n * through the shared `paymentRisk` shape. A dispute lives in `dispute`.\n */\n paymentRisk?: PaymentRisk\n /** Stock a reversal left off the shelf, awaiting the merchant (AGL-1797). */\n restockCheck?: OrderRestockCheck\n /**\n * Cart lines the webhook could not price because the product was deleted\n * between session creation and payment (AGL-2149). Present ONLY on an order\n * that is short of what the shopper was charged: `totals.itemsCents` is\n * missing these lines while `amountCents` still holds the full\n * `amount_total`, and this is the record of which lines the difference is.\n *\n * Deliberately NOT an `OrderLineItem[]`: the product doc is gone, so there is\n * no name, no price and no type to record — only what the cart asked for.\n */\n unresolvedLines?: OrderUnresolvedLine[]\n createdAtMs?: number\n // Legacy Commerce Starter fields (AGL-90) kept readable.\n productId?: string\n amountCents?: number\n feeCents?: number\n}\n\n/**\n * Legal status transitions. Refund/cancel policies: anything paid can\n * refund; only unfulfilled orders cancel (refund instead once shipped).\n *\n * `cancelled` IS NOT TERMINAL, and treating it as one trapped money (AGL-2149).\n * `cancel-order.ts` accepts a **paid** order — `paid: [… 'cancelled' …]` above\n * — and moves no money when it does: it flips the status and returns the stock.\n * `refund.ts` gates on `canTransitionOrder(order.status, 'refunded')`, so with\n * `cancelled: []` the refund door closed behind the cancel and answered \"orders\n * in cancelled cannot refund\" forever. An admin who cancelled a paid order\n * instead of refunding it — the two buttons sit next to each other in the order\n * dialog — had permanently locked the shopper's money out of every refund route\n * this product has. The shopper's only remaining recourse was a chargeback,\n * which costs the merchant the goods, the money AND the dispute fee.\n *\n * Whether cancelling a PAID order should refuse, or refund on the merchant's\n * behalf, is a real product question and is deliberately NOT answered here:\n * either would change what an existing console button does to money. Making the\n * money reachable is the part that must not wait for that answer.\n *\n * A cancelled order that never took money simply has nothing to refund —\n * `refund.ts` resolves no payment intent and answers \"No payment to refund\" —\n * so the widened edge costs a lapsed `pending` order nothing.\n */\nconst ORDER_TRANSITIONS: Record<OrderStatus, OrderStatus[]> = {\n pending: ['paid', 'cancelled'],\n paid: ['partially_fulfilled', 'fulfilled', 'cancelled', 'refunded'],\n partially_fulfilled: ['fulfilled', 'refunded'],\n fulfilled: ['delivered', 'refunded'],\n delivered: ['refunded'],\n cancelled: ['refunded'],\n refunded: [],\n}\n\nexport function canTransitionOrder(\n from: OrderStatus,\n to: OrderStatus,\n): boolean {\n return (ORDER_TRANSITIONS[from] ?? []).includes(to)\n}\n\n/**\n * One money part, coerced to a whole non-negative number of cents (AGL-2224).\n *\n * `Math.max(0, Math.round(x))` is NOT this guard, and the difference is the\n * whole defect: `Math.max` and `Math.min` PROPAGATE `NaN` rather than\n * discarding it, so a single non-finite input made every field below `NaN` and\n * `totalCents` with them. That is not a cosmetic wrong number — it is a number\n * that defeats comparison. `cashReceivedCents < NaN` is `false`, so the POS\n * \"cash received is short\" guard passed a sale that had taken no money; and a\n * `NaN` written to Firestore (a legal double there) poisons every downstream\n * sum, so ONE bad order renders a merchant's whole revenue figure `NaN`\n * forever after.\n *\n * The inputs that can be non-finite are real, not theoretical: a POS\n * `discountPct` that is not a number, a product whose `priceUsd` is a string\n * or absent (`Number(undefined) * 100`), a `taxCents` derived from either. So\n * this coerces at the boundary every order in the product passes through,\n * rather than at each of the five callers, and answers 0 — the only value that\n * cannot silently overstate what the shopper owes.\n */\nconst wholeCents = (value: unknown): number => {\n const cents = Math.round(Number(value ?? 0))\n return Number.isFinite(cents) && cents > 0 ? cents : 0\n}\n\n/**\n * Split `totalCents` across `weights` so the parts sum to it EXACTLY.\n *\n * Largest-remainder (Hamilton) apportionment. Each weight takes its exact\n * share, the shares are floored, and the cents left over by flooring are handed\n * one each to the largest fractional parts, ties going to the lower index so\n * the answer is deterministic for a given input.\n *\n * The exactness is the whole point and it is a money property, not a\n * neatness one: `Math.round` per line loses or invents cents against the total\n * — three lines splitting a 10¢ discount round to 3+3+3=9 and the missing cent\n * has to come from somewhere. A refund built on a split that does not close is\n * a reconciliation defect that surfaces months later against Stripe's numbers.\n *\n * Pure and total. A non-positive total, an empty weight list, or weights that\n * sum to nothing all answer zeros — never a division by zero, never a NaN\n * propagated into an amount someone is charged.\n */\nexport function apportionCents(\n weights: readonly number[],\n totalCents: number,\n): number[] {\n const safe = (weights ?? []).map((weight) =>\n Math.max(0, Math.round(Number(weight ?? 0))),\n )\n const basis = safe.reduce((sum, weight) => sum + weight, 0)\n const total = Math.max(0, Math.round(Number(totalCents ?? 0)))\n if (safe.length === 0 || basis <= 0 || total <= 0) return safe.map(() => 0)\n // Never hand back more than there is to split. A discount larger than the\n // items it came off is a data fault, and apportioning it in full would\n // credit lines with money the order never carried.\n const pot = Math.min(total, basis)\n const exact = safe.map((weight) => (weight * pot) / basis)\n const shares = exact.map((value) => Math.floor(value))\n let remainder = pot - shares.reduce((sum, share) => sum + share, 0)\n const order = exact\n .map((value, index) => ({ index, fraction: value - Math.floor(value) }))\n .sort((a, b) => b.fraction - a.fraction || a.index - b.index)\n for (const entry of order) {\n if (remainder <= 0) break\n shares[entry.index] += 1\n remainder -= 1\n }\n return shares\n}\n\n/**\n * What refunding the named lines is actually worth.\n *\n * A line's LIST value is `unitAmountCents x quantity`, and that is what the\n * refund route used to hand back. It is the wrong number on any order that\n * carried an order-level discount, because the buyer never paid it: a $10\n * coupon on two $50 lines means each line cost $45, and refunding one at $50\n * pays back $5 the buyer never spent. The order then has less left than its\n * remaining line is worth, so the SECOND line refund is refused outright and\n * the merchant cannot finish the job they started.\n *\n * So each line carries its share of the discount, apportioned by list value\n * through {@link apportionCents} — which guarantees the shares sum to the whole\n * discount, so refunding every line returns exactly `itemsCents - discountCents`\n * and no cent is stranded or invented.\n *\n * SHIPPING AND TAX ARE DELIBERATELY NOT IN THIS FIGURE. Refunding a line\n * returns what the goods cost. Shipping is charged for the consignment rather\n * than the line, so no split of it is defensible without knowing whether the\n * parcel still ships; tax follows the goods but is Stripe's to recompute, and\n * an order whose tax was collected under Aglyn's own registration\n * (`taxMode: 'stripe-automatic'`) is not one this function may quietly decide\n * to hand back. Both remain whole-order concerns, refundable by amount.\n *\n * Pure: unknown indexes contribute nothing rather than throwing.\n */\nexport function orderLineRefundCents(\n order: Pick<Partial<HostOrder>, 'lineItems' | 'totals'>,\n lineIndexes: readonly number[],\n): number {\n const lines = order?.lineItems ?? []\n const gross = lines.map((line) =>\n Math.max(\n 0,\n Math.round(Number(line?.unitAmountCents ?? 0)) *\n Math.max(1, Math.round(Number(line?.quantity ?? 1))),\n ),\n )\n const shares = apportionCents(\n gross,\n Math.max(0, Math.round(Number(order?.totals?.discountCents ?? 0))),\n )\n const wanted = new Set(\n (lineIndexes ?? []).filter(\n (index) => Number.isInteger(index) && index >= 0 && index < lines.length,\n ),\n )\n let cents = 0\n for (const index of wanted) cents += gross[index] - shares[index]\n return Math.max(0, cents)\n}\n\n/**\n * A REHEARSAL IS NOT REVENUE.\n *\n * A smoke-test checkout writes a real order document. Stripe never moved money\n * for it — the session id is `cs_test_…` — but every surface that sums paid\n * orders counted it, so a storefront with one test order and no sales reported\n * revenue it had never earned. Found in production: a single $18.00 order from\n * an end-to-end run, standing as the whole platform's storefront revenue.\n *\n * ## Two signals, in order of trust\n *\n * `livemode` is a RECORDED FACT and wins whenever it is present: the webhook\n * knows which Stripe environment an event came from and now writes it onto the\n * order. Only a literal boolean counts, the same three-valued reading every\n * other money gate in this codebase uses.\n *\n * The session id prefix is the FALLBACK, for the orders written before\n * anything recorded the fact. It is Stripe's convention rather than our data,\n * so it lives behind `stripeIdIsTestMode` and is not spelled out here.\n *\n * ## An unknowable order is LIVE\n *\n * An order with no `livemode` and no Stripe session id — a POS cash sale, a\n * folio charge, a draft order paid offline — is real money and is counted.\n * Answering \"test\" for anything we cannot identify would zero genuine sales,\n * which is the one direction this must never fail in: a merchant under-reporting\n * their own revenue has no way to tell it is happening.\n */\nexport function orderIsTestMode(\n order: Pick<Partial<HostOrder>, 'checkoutSessionId'> & {\n livemode?: unknown\n /** The Firestore doc id, which for a storefront order IS the session id. */\n $id?: unknown\n },\n): boolean {\n if (typeof order?.livemode === 'boolean') return !order.livemode\n // `$id` as well as the field: every storefront order is keyed BY its session\n // id, so the doc id carries the same signal and is present on rows read\n // straight out of Firestore even where the field was never written. A POS or\n // draft order has an auto-generated id, which matches no Stripe prefix and\n // correctly reads as live.\n return (\n stripeIdIsTestMode(order?.checkoutSessionId) || stripeIdIsTestMode(order?.$id)\n )\n}\n\n/** Sums line items and folds in shipping/tax/discount/fee, all cents. */\nexport function computeOrderTotals(\n lineItems: OrderLineItem[],\n parts?: Partial<Pick<OrderTotals, 'shippingCents' | 'taxCents' | 'discountCents' | 'feeCents'>>,\n): OrderTotals {\n const itemsCents = (lineItems ?? []).reduce(\n (sum, line) => sum + wholeCents(line?.unitAmountCents * line?.quantity),\n 0,\n )\n const shippingCents = wholeCents(parts?.shippingCents)\n const taxCents = wholeCents(parts?.taxCents)\n const discountCents = Math.min(\n wholeCents(parts?.discountCents),\n itemsCents + shippingCents,\n )\n return {\n itemsCents,\n shippingCents,\n taxCents,\n discountCents,\n feeCents: wholeCents(parts?.feeCents),\n totalCents: itemsCents + shippingCents + taxCents - discountCents,\n }\n}\n\n/**\n * The completed Checkout Session fields an online order's totals are built\n * from. Typed structurally rather than against Stripe's SDK: the plugin talks\n * to Stripe over raw `fetch` and never installs the package.\n */\nexport interface CheckoutSessionTotalsSource {\n amount_total?: unknown\n total_details?: {\n amount_tax?: unknown\n amount_shipping?: unknown\n amount_discount?: unknown\n } | null\n}\n\n/**\n * Stored `OrderTotals` for an `online` order, from the completed session.\n *\n * AGL-1698: the webhook used to read `amount_tax` and `amount_discount` and\n * silently skip their third sibling, so `computeOrderTotals` defaulted\n * `shippingCents` to 0 on every online order while the shipping the shopper\n * paid sat inside `amount_total`. The stored parts then did not sum to the\n * stored total, and every merchant reconciling their own books against these\n * records understated shipping by exactly the amount charged. `amount_shipping`\n * is the figure to read — `shipping_cost.amount_total` carries the same number\n * but is null unless a rate was chosen, whereas `total_details.amount_shipping`\n * is always present and sits beside the two fields already read.\n *\n * `totalCents` still comes from Stripe's `amount_total` verbatim rather than\n * from our arithmetic: `itemsCents` is priced from the host's product docs, so\n * a price edit between session creation and webhook delivery would otherwise\n * make our sum, not Stripe's charge, the stored truth. With shipping passed the\n * two agree by construction — which is now an invariant a fixture can pin\n * rather than a discrepancy that papers over a missing part.\n *\n * Note this also unclamps the discount correctly: `computeOrderTotals` caps\n * `discountCents` at `itemsCents + shippingCents`, so a discount that reached\n * into shipping was previously clamped down as well.\n */\nexport interface CheckoutSessionTotalsParts {\n /** Aglyn's cut, from the session metadata rather than from Stripe. */\n feeCents?: number\n /**\n * Tax we charged as an ordinary Stripe line item (AGL-1711). It is inside\n * `amount_total` but Stripe was never told it was tax, so it is absent from\n * `total_details.amount_tax` and has to be supplied by the caller that built\n * the session. Added to Stripe's figure, not substituted for it: the two are\n * mutually exclusive by construction — `checkout.ts` adds the manual line\n * only in `manual` tax mode and sets `automatic_tax` only in `stripe` mode —\n * so summing is right in every reachable case and correct in principle if\n * both were ever charged at once.\n *\n * A manual-tax SUBSCRIPTION session is the third construction (AGL-1751):\n * there the tax is a real Stripe Tax Rate (a one-time line would bill on\n * the first invoice only), `amount_tax` carries it, and `checkout.ts`\n * writes `metadata[taxCents]` as 0 — the exclusivity this sum rests on,\n * kept from the other side.\n */\n lineItemTaxCents?: number\n /**\n * A discount applied by lowering the unit price we sent to Stripe rather\n * than as a Stripe discount (AGL-1711). Same shape as `lineItemTaxCents`:\n * invisible to `total_details.amount_discount`, so it is added to it.\n */\n pricedInDiscountCents?: number\n /**\n * The optional lines the buyer took (AGL-3635). Inside `amount_total` as\n * ordinary Stripe lines, so the parts only sum to the charge with it\n * named; recorded only when there is one.\n */\n extrasCents?: number\n}\n\nexport function computeCheckoutSessionTotals(\n lineItems: OrderLineItem[],\n session: CheckoutSessionTotalsSource | null | undefined,\n parts?: CheckoutSessionTotalsParts,\n): OrderTotals {\n const details = session?.total_details ?? {}\n const totals = computeOrderTotals(lineItems, {\n feeCents: Number(parts?.feeCents ?? 0),\n taxCents:\n Number(details?.amount_tax ?? 0) + Number(parts?.lineItemTaxCents ?? 0),\n shippingCents: Number(details?.amount_shipping ?? 0),\n discountCents:\n Number(details?.amount_discount ?? 0) +\n Number(parts?.pricedInDiscountCents ?? 0),\n })\n const amountTotal = Number(session?.amount_total ?? NaN)\n const extrasCents = wholeCents(parts?.extrasCents)\n return {\n ...totals,\n ...(extrasCents > 0 ? { extrasCents } : {}),\n totalCents: Number.isFinite(amountTotal) ? amountTotal : totals.totalCents + extrasCents,\n }\n}\n\n/** Non-negative integer cents from an untyped metadata value; 0 otherwise. */\nfunction metadataCents(value: unknown): number {\n const cents = Math.round(Number(value ?? 0))\n return Number.isFinite(cents) && cents > 0 ? cents : 0\n}\n\n/** The buy-now Checkout Session as the webhook sees it — metadata included. */\nexport interface BuyNowSessionSource extends CheckoutSessionTotalsSource {\n metadata?: Record<string, unknown> | null\n}\n\n/** What the host's product doc contributes to the line-item snapshot. */\nexport interface BuyNowProductSnapshot {\n /** Product name at purchase time. */\n name: string\n /** Joined variant options, e.g. `Large / Blue`. */\n variantLabel?: string\n sku?: string\n productType?: ProductType\n supplierId?: string\n}\n\n/**\n * The stored line items and totals for a `commerce-order` (buy-now) session\n * (AGL-1711).\n *\n * ## What was wrong\n *\n * The webhook built the whole order from one number — it read\n * `object.amount_total` into `amountCents`, wrote a single line item with\n * `quantity` literal 1 and `unitAmountCents` set to that `amountCents`, and\n * passed `computeOrderTotals` nothing but `feeCents`.\n *\n * `amount_total` is the ENTIRE charge, so the merchant's record said the\n * product's unit price was the whole session total, that one unit was sold, and\n * that tax and discount were zero. A 3 × $100 purchase recorded as 1 × $300.\n *\n * The dangerous property, and why this outlived AGL-1698: it is not internally\n * inconsistent. `itemsCents` equalled `totalCents`, so the parts summed and any\n * arithmetic check passed while every individual component was wrong. The\n * useful test is not \"do the parts sum\" but \"does each part match what Stripe\n * actually charged\", which is what the fixtures assert component by component.\n *\n * ## Why metadata, when AGL-1698's lesson was to read Stripe's own fields\n *\n * Two of the four parts are genuinely absent from Stripe's decomposition, and\n * `checkout.ts` is what hid them:\n *\n * - **Tax.** In `manual` mode the tax is appended as `line_items[1]`, a normal\n * product line labelled e.g. \"Tax (8.25%)\". Stripe does not know it is tax,\n * so `total_details.amount_tax` is 0 while the money sits in `amount_total`.\n * (In `stripe` mode `automatic_tax` makes `amount_tax` real — and it was not\n * read either, so that tax was lost as well.)\n * - **Discount.** A host coupon is applied by lowering the unit price we send,\n * not as a Stripe discount, so `total_details.amount_discount` is 0.\n *\n * So `checkout.ts` now records both figures, plus the list unit price, in the\n * session metadata as it computes them. This is NOT the reconstruction AGL-1698\n * warned against: the guard there is against pricing from the host's product\n * docs at webhook time, where an edit between session creation and delivery\n * makes our sum disagree with the actual charge. Metadata is a snapshot of the\n * numbers we handed Stripe, frozen at session creation, so it cannot drift.\n * Everything Stripe does know — `amount_total`, `amount_tax`, `amount_shipping`,\n * `amount_discount` — still comes from Stripe, via the shared\n * `computeCheckoutSessionTotals` rather than a parallel decomposition.\n *\n * Shipping is read even though `checkout.ts` declares no `shipping_options`\n * today (AGL-1720): the read is not conditioned on that, so the figure lands on\n * its own the moment buy-now starts charging shipping.\n *\n * ## Sessions created before this change\n *\n * They carry `quantity` (always sent) but none of the three new keys, so the\n * unit price falls back to what Stripe charged per unit, derived from\n * `amount_total` with tax, shipping and discount taken back out. Those orders\n * still reconcile against `amount_total` and their quantity is now right; what\n * cannot be recovered is a priced-in coupon, which stays folded into the unit\n * price exactly as it is today. Deriving from the product doc instead would\n * make `itemsCents` gross with no matching `discountCents`, breaking the sum.\n *\n * ## Worked example — the spec fixture\n *\n * Three $100 units, a 10% host coupon, manual destination tax at 8.25%:\n *\n * listUnit 10000 metadata `unitAmountCents`\n * × quantity 3 metadata `quantity`\n * = itemsCents 30000\n * - discountCents 3000 metadata `discountCents` (priced into the\n * 9000 unit price sent to Stripe)\n * + taxCents 2228 metadata `taxCents` (Stripe `line_items[1]`;\n * `total_details.amount_tax` is 0)\n * + shippingCents 0 Stripe `total_details.amount_shipping`\n * = totalCents 29228 Stripe `amount_total`, verbatim\n *\n * The pre-fix record for the same purchase: `1 × 29228`, tax 0, discount 0 —\n * which also sums to 29228.\n *\n * ## Also the subscription sale record (AGL-1732)\n *\n * `checkout.ts` builds a subscription session with the same function and the\n * same metadata snapshot — only `mode` differs — so the initial charge of a\n * storefront subscription decomposes identically. The webhook's\n * `commerce-subscription` branch stores the result on the subscription\n * document rather than as an order; the arithmetic is the same either way,\n * which is why it lives here and is not duplicated there.\n */\nexport function computeBuyNowOrder(\n session: BuyNowSessionSource | null | undefined,\n snapshot: BuyNowProductSnapshot,\n): { lineItems: OrderLineItem[]; totals: OrderTotals } {\n const metadata = (session?.metadata ?? {}) as Record<string, unknown>\n const details = session?.total_details ?? {}\n const rawQuantity = Math.round(Number(metadata.quantity ?? 1))\n const quantity =\n Number.isFinite(rawQuantity) && rawQuantity > 0 ? rawQuantity : 1\n const lineItemTaxCents = metadataCents(metadata.taxCents)\n const pricedInDiscountCents = metadataCents(metadata.discountCents)\n // What Stripe charged for the goods themselves, used only when the session\n // predates the `unitAmountCents` metadata: strip every non-goods component\n // back out of the total and add the discount back in.\n const goodsCents =\n Number(session?.amount_total ?? 0) -\n Number(details?.amount_tax ?? 0) -\n Number(details?.amount_shipping ?? 0) -\n lineItemTaxCents +\n Number(details?.amount_discount ?? 0) +\n pricedInDiscountCents\n const unitAmountCents =\n metadataCents(metadata.unitAmountCents) ||\n Math.max(0, Math.round(goodsCents / quantity))\n const variantId = String(metadata.variantId ?? '')\n const lineItems: OrderLineItem[] = [\n {\n productId: String(metadata.productId ?? ''),\n ...(variantId ? { variantId } : {}),\n name: snapshot.name,\n ...(snapshot.variantLabel ? { variantLabel: snapshot.variantLabel } : {}),\n ...(snapshot.sku ? { sku: snapshot.sku } : {}),\n ...(snapshot.productType ? { productType: snapshot.productType } : {}),\n ...(snapshot.supplierId ? { supplierId: snapshot.supplierId } : {}),\n quantity,\n unitAmountCents,\n },\n ]\n return {\n lineItems,\n totals: computeCheckoutSessionTotals(lineItems, session, {\n feeCents: metadataCents(metadata.feeCents),\n lineItemTaxCents,\n pricedInDiscountCents,\n }),\n }\n}\n\n/** One line on a Stripe invoice, structurally typed like the session above. */\nexport interface SubscriptionInvoiceLine {\n /** The line total in cents, after any line-level discount, excluding tax. */\n amount?: unknown\n quantity?: unknown\n description?: unknown\n /** True on the credit/charge pair a mid-cycle plan switch generates. */\n proration?: unknown\n price?: {\n unit_amount?: unknown\n recurring?: { interval?: unknown } | null\n } | null\n}\n\n/**\n * The paid-invoice fields a renewal's totals are built from (AGL-1743).\n *\n * ## An invoice is not a Checkout Session\n *\n * This was checked field by field rather than assumed, and the two objects\n * agree on almost nothing:\n *\n * - **There is no `total_details` on an invoice at all.** That object is a\n * Checkout Session field. `computeCheckoutSessionTotals` cannot be handed an\n * invoice — every part would read 0 and the whole renewal would decompose to\n * `amount_total` with nothing in it, which is exactly the AGL-1711 failure\n * shape (internally consistent, individually wrong).\n * - **Tax** is `tax` (a scalar) on the API versions this repo pins, and\n * `total_taxes[]` on newer ones, where the scalar was removed. Both are read,\n * the scalar preferred, because a webhook endpoint's version is dashboard\n * configuration this repo cannot see.\n * - **Discount** is `total_discount_amounts[]`, an array of per-discount\n * amounts, not a single `amount_discount`.\n * - **Shipping** is `shipping_cost.amount_total` — note AGL-1698 preferred\n * `total_details.amount_shipping` over exactly this field ON A SESSION,\n * because there it is null unless a rate was chosen. On an invoice it is the\n * only form there is.\n * - **The fee** is a real field here: a subscription carries\n * `application_fee_percent`, so every invoice reports the resulting\n * `application_fee_amount`. The session path has to read it out of metadata.\n * - **What was bought** is `lines.data[]` rather than the session's metadata\n * snapshot — `checkout.ts` puts only `type`, `hostId` and `productId` on\n * `subscription_data[metadata]`, so the quantity and unit price of a renewal\n * have to come from the invoice itself.\n *\n * The arithmetic is still the shared one: the fields above are mapped onto the\n * session shape and handed to `computeCheckoutSessionTotals`, so a renewal and\n * a sale clamp, sum and reconcile identically.\n */\nexport interface SubscriptionInvoiceSource {\n /** What actually arrived — the figure this records as collected. */\n amount_paid?: unknown\n total?: unknown\n subtotal?: unknown\n /** Total tax; removed in favor of `total_taxes` on newer API versions. */\n tax?: unknown\n total_taxes?: readonly { amount?: unknown }[] | null\n total_tax_amounts?: readonly { amount?: unknown }[] | null\n total_discount_amounts?: readonly { amount?: unknown }[] | null\n shipping_cost?: { amount_total?: unknown } | null\n application_fee_amount?: unknown\n /** Why Stripe raised it: `subscription_cycle`, `subscription_update`, … */\n billing_reason?: unknown\n lines?: { data?: readonly SubscriptionInvoiceLine[] | null } | null\n}\n\n/**\n * The base a subscription invoice's platform fee SHOULD be taken on\n * (AGL-2317): post-discount items, with tax and shipping removed.\n *\n * Every one-time door — `checkout.ts`, `cart-checkout.ts`, `draft-order.ts`,\n * `pos-order.ts` and (since AGL-2315) bookings — sends a cents amount computed\n * on items alone. The recurring door sent `application_fee_percent`, which\n * Stripe applies to the WHOLE invoice, so Aglyn took a cut of sales tax — money\n * that is the state's — and of shipping. On the issue's worked example ($30/mo\n * digital, Business 2%, an 8.25% TX rate) the one-time base is $30.00 → 60¢ and\n * the invoice total is $32.48 → 65¢.\n *\n * `total` and not a re-summed `lines.data[]`, deliberately. Stripe's own total\n * is already net of every invoice-level discount and of any proration CREDIT,\n * both of which `computeSubscriptionInvoiceOrder` drops (it floors a line at 0,\n * which is right for a stored line item and wrong for a fee base — a mid-cycle\n * downgrade credit reduces what the merchant is actually paid). Subtracting the\n * two parts Stripe reports separately leaves exactly what was charged for goods.\n *\n * Tax is read on both spellings for the reason stated on `SubscriptionInvoiceSource`\n * — the scalar was removed on newer API versions and the endpoint's version is\n * dashboard configuration this repo cannot see. Reading only one of them would\n * silently value tax at 0 and hand back the whole invoice total as the base,\n * which is the very bug this closes.\n */\nexport function subscriptionInvoiceFeeBasisCents(\n invoice: SubscriptionInvoiceSource | null | undefined,\n): number {\n const totalCents = metadataCents(invoice?.total)\n if (totalCents <= 0) return 0\n const scalarTax = Number(invoice?.tax ?? NaN)\n const taxCents =\n Number.isFinite(scalarTax) && scalarTax > 0\n ? scalarTax\n : sumAmounts(invoice?.total_taxes ?? invoice?.total_tax_amounts)\n const shippingCents = metadataCents(invoice?.shipping_cost?.amount_total)\n return Math.max(0, totalCents - Math.max(0, taxCents) - shippingCents)\n}\n\n/**\n * What the platform fee on a paid subscription invoice WOULD have been on the\n * items-only base, at the rate Stripe actually applied (AGL-2317).\n *\n * ## The rate is never named here, on purpose\n *\n * Pricing is locked for Sept 1, so this is a BASE correction and must not be\n * able to become a rate change. It therefore takes no plan, no entitlement and\n * no `resolveTransactionFeePct` call: it scales the fee Stripe already charged\n * by `base / total`. Whatever rate produced `application_fee_amount` — the\n * sale-time one, a rate AGL-2289 has since re-priced, or a staff override —\n * comes through untouched, and the only thing removed is the portion that was\n * taken on tax and shipping. A test that changed a rate could not make this\n * function agree with it.\n *\n * `Math.max(1, …)` matches `checkout.ts`: where a rate applies at all, the\n * floor is a cent, never zero. An invoice with no fee, no total, or nothing but\n * tax answers 0 and asks for no correction it cannot justify.\n */\nexport function subscriptionInvoiceItemsOnlyFeeCents(\n invoice: SubscriptionInvoiceSource | null | undefined,\n): number {\n const chargedCents = metadataCents(invoice?.application_fee_amount)\n if (chargedCents <= 0) return 0\n const totalCents = metadataCents(invoice?.total)\n // Nothing to scale against — leave what Stripe took rather than inventing a\n // reduction from a total we cannot read.\n if (totalCents <= 0) return chargedCents\n const basisCents = subscriptionInvoiceFeeBasisCents(invoice)\n if (basisCents <= 0) return 0\n // Tax-free and shipping-free invoices — the majority — land here and are\n // byte-identical to today, which is what makes this safe inside the freeze.\n if (basisCents >= totalCents) return chargedCents\n return Math.max(1, Math.round((chargedCents * basisCents) / totalCents))\n}\n\n/**\n * The recurring goods a storefront subscription's `application_fee_percent`\n * is sized against when a paid invoice re-prices it (AGL-2655).\n *\n * The percent carries Stripe's fixed 30¢ folded into a rate, so it depends\n * on the amount it will be applied to — and the amount that matters is the\n * NEXT cycle's, which nobody has yet. The best predictor is this cycle's\n * items-only basis, for a cycle that bills the full recurring price:\n * `subscription_cycle`, and `subscription_create` for the opening charge.\n * Any other reason — a `subscription_update` proration, a threshold\n * invoice — bills a fraction, and a percent sized on a fraction would be\n * applied to the following full cycle and over-recover on it. Those, and a\n * $0 opening invoice (a trial converts later), fall back to what the sale\n * recorded: the sold lines' price × quantity, the stable figure the\n * subscription bills every cycle it is not being adjusted.\n *\n * Zero when neither source knows — and the caller then leaves the rate as it\n * stands rather than sizing a percent on nothing.\n */\nexport function subscriptionRecurringBasisCents(\n invoice: SubscriptionInvoiceSource | null | undefined,\n soldLineItems:\n | readonly Pick<OrderLineItem, 'quantity' | 'unitAmountCents'>[]\n | null\n | undefined,\n): number {\n const reason = String(invoice?.billing_reason ?? '')\n const billsTheFullPrice =\n reason === 'subscription_cycle' || reason === 'subscription_create'\n const cycleBasis = billsTheFullPrice\n ? subscriptionInvoiceFeeBasisCents(invoice)\n : 0\n if (cycleBasis > 0) return cycleBasis\n return (soldLineItems ?? []).reduce((sum, line) => {\n const unit = Math.round(Number(line?.unitAmountCents ?? 0))\n const quantity = Math.round(Number(line?.quantity ?? 0))\n if (!Number.isFinite(unit) || !Number.isFinite(quantity)) return sum\n return sum + Math.max(0, unit) * Math.max(0, quantity)\n }, 0)\n}\n\n/** Sum of an `[{ amount }]` list, ignoring anything unreadable. */\nfunction sumAmounts(list: readonly { amount?: unknown }[] | null | undefined) {\n return (list ?? []).reduce((sum, entry) => {\n const amount = Number(entry?.amount ?? 0)\n return sum + (Number.isFinite(amount) ? amount : 0)\n }, 0)\n}\n\n/** `price.recurring.interval` when it is one we sell, else undefined. */\nfunction lineInterval(\n line: SubscriptionInvoiceLine | null | undefined,\n): 'month' | 'year' | undefined {\n const interval = line?.price?.recurring?.interval\n return interval === 'month' || interval === 'year' ? interval : undefined\n}\n\n/**\n * The line that describes the SUBSCRIPTION being billed.\n *\n * `lines.data[0]` is not it, for the AGL-1640 reason: a mid-cycle plan switch\n * invoices the proration against the OLD price ahead of the new plan, and an\n * invoice also carries one-off items and credits, any of which can sort first.\n * Prefer a non-proration line stating a cadence; fall back to a proration one,\n * since Stripe requires every recurring item on one subscription to share a\n * cadence; fall back to the first line so a renewal still records a name.\n */\nexport function selectSubscriptionInvoiceLine(\n invoice: SubscriptionInvoiceSource | null | undefined,\n): SubscriptionInvoiceLine | undefined {\n const lines = invoice?.lines?.data ?? []\n return (\n lines.find((line) => !line?.proration && lineInterval(line)) ??\n lines.find((line) => lineInterval(line)) ??\n lines[0]\n )\n}\n\n/**\n * The cadence a paid invoice was billed on, or undefined when it does not say.\n *\n * Never guessed — the same three-state discipline as `billingIntervalFromInvoice`\n * (AGL-1640). Storing `month` for an invoice that did not state one would make\n * the merchant's own record of what a subscriber pays wrong in the one way that\n * cannot be spotted by looking at it.\n */\nexport function subscriptionInvoiceInterval(\n invoice: SubscriptionInvoiceSource | null | undefined,\n): 'month' | 'year' | undefined {\n return lineInterval(selectSubscriptionInvoiceLine(invoice))\n}\n\n/**\n * The stored line items and totals for one paid subscription invoice\n * (AGL-1743) — a renewal, or the opening cycle.\n *\n * `snapshot` supplies the product identity, taken from what the sale already\n * recorded rather than re-derived: an invoice line knows a description and a\n * price, not a productId, a variant or a SKU. It is applied to the\n * subscription's own line only; a proration or one-off line on the same invoice\n * keeps Stripe's description and carries no product identity, because it is not\n * a sale of that product.\n *\n * `totalCents` is Stripe's `amount_paid` verbatim, for the AGL-1698 reason —\n * our sum is priced from lines we did not authorize and must never become the\n * stored truth. Where a customer credit balance covers part of an invoice the\n * parts will therefore exceed the collected total; the collected figure is the\n * one that matters and the invoice's own `total` is stored beside it.\n */\nexport function computeSubscriptionInvoiceOrder(\n invoice: SubscriptionInvoiceSource | null | undefined,\n snapshot: BuyNowProductSnapshot & { productId: string; variantId?: string },\n): { lineItems: OrderLineItem[]; totals: OrderTotals } {\n const subscriptionLine = selectSubscriptionInvoiceLine(invoice)\n const lineItems: OrderLineItem[] = []\n for (const line of invoice?.lines?.data ?? []) {\n const amount = Math.round(Number(line?.amount ?? 0))\n // A credit line (negative proration) is not a sale of anything, and\n // `computeOrderTotals` floors a line at 0 rather than subtracting it.\n // Stripe's own collected total still carries its effect.\n if (!Number.isFinite(amount) || amount <= 0) continue\n const rawQuantity = Math.round(Number(line?.quantity ?? 1))\n const quantity =\n Number.isFinite(rawQuantity) && rawQuantity > 0 ? rawQuantity : 1\n // The list unit price, except on a proration line, where `unit_amount` is\n // the full price and `amount` is only the fraction being billed.\n const listUnit = Math.round(Number(line?.price?.unit_amount ?? 0))\n const unitAmountCents =\n !line?.proration && Number.isFinite(listUnit) && listUnit > 0\n ? listUnit\n : Math.max(0, Math.round(amount / quantity))\n const isSubscriptionLine = line === subscriptionLine\n lineItems.push({\n productId: isSubscriptionLine ? snapshot.productId : '',\n ...(isSubscriptionLine && snapshot.variantId\n ? { variantId: snapshot.variantId }\n : {}),\n name: isSubscriptionLine\n ? snapshot.name\n : String(line?.description ?? 'Adjustment').slice(0, 200),\n ...(isSubscriptionLine && snapshot.variantLabel\n ? { variantLabel: snapshot.variantLabel }\n : {}),\n ...(isSubscriptionLine && snapshot.sku ? { sku: snapshot.sku } : {}),\n ...(isSubscriptionLine && snapshot.productType\n ? { productType: snapshot.productType }\n : {}),\n ...(isSubscriptionLine && snapshot.supplierId\n ? { supplierId: snapshot.supplierId }\n : {}),\n quantity,\n unitAmountCents,\n })\n }\n const scalarTax = Number(invoice?.tax ?? NaN)\n const taxCents =\n Number.isFinite(scalarTax) && scalarTax > 0\n ? scalarTax\n : // `total_taxes` replaced `total_tax_amounts`, which replaced the\n // scalar; they do not coexist, so this cannot double-count.\n sumAmounts(invoice?.total_taxes ?? invoice?.total_tax_amounts)\n return {\n lineItems,\n totals: computeCheckoutSessionTotals(\n lineItems,\n {\n amount_total: invoice?.amount_paid,\n total_details: {\n amount_tax: taxCents,\n amount_shipping: metadataCents(invoice?.shipping_cost?.amount_total),\n amount_discount: sumAmounts(invoice?.total_discount_amounts),\n },\n },\n { feeCents: metadataCents(invoice?.application_fee_amount) },\n ),\n }\n}\n\n/** Display form: `#1042`; falls back to a doc-id stub for legacy rows. */\nexport function formatOrderNumber(order: Pick<HostOrder, 'number'>, docId?: string): string {\n if (order.number != null) return `#${order.number}`\n return docId ? `#${docId.slice(-6).toUpperCase()}` : '#—'\n}\n\n/**\n * Lifts a legacy Commerce Starter order row (flat productId/amountCents)\n * into the v1 shape for display; already-shaped orders pass through.\n */\nexport function liftLegacyOrder(raw: Partial<HostOrder>): HostOrder {\n if (Array.isArray(raw.lineItems) && raw.lineItems.length > 0) {\n return { status: 'paid', ...raw } as HostOrder\n }\n const amountCents = Number(raw.amountCents ?? 0)\n return {\n ...raw,\n status: raw.status ?? 'paid',\n channel: raw.channel ?? 'online',\n lineItems: raw.productId\n ? [\n {\n productId: raw.productId,\n name: 'Product',\n quantity: 1,\n unitAmountCents: amountCents,\n },\n ]\n : [],\n totals: raw.totals ?? {\n itemsCents: amountCents,\n shippingCents: 0,\n taxCents: 0,\n discountCents: 0,\n feeCents: Number(raw.feeCents ?? 0),\n totalCents: amountCents,\n },\n }\n}\n\n/**\n * Does this order contain `productId`?\n *\n * Line items first, legacy flat `productId` as the fallback — the same test\n * `gate.ts`, `download.ts` and `reviews.ts` already apply. Only the two\n * buy-now-shaped Stripe paths ever write the flat field, so a legacy-only\n * check silently excludes every cart, POS and draft order.\n */\nexport function orderContainsProduct(\n order: Partial<HostOrder>,\n productId: string,\n): boolean {\n return (\n (order.lineItems ?? []).some((line) => line.productId === productId) ||\n order.productId === productId\n )\n}\n\n/**\n * Line indexes already covered by a recorded fulfillment (AGL-2455).\n *\n * The set the supplier callback and the console's own fulfil both have to\n * reason about, and the reason `status` alone could not: `supplier-update.ts`\n * wrote `status: 'fulfilled'` as a LITERAL, so the first supplier to post\n * tracking on a two-supplier order marked every line shipped — including lines\n * they have never seen — and the second supplier's POST met a 409 because\n * `fulfilled` cannot transition to `fulfilled`. The buyer was told their whole\n * order shipped with one carrier and one tracking number.\n */\nexport function coveredLineItemIds(order: Partial<HostOrder>): Set<number> {\n // Quantity-aware since AGL-3611: a line is covered once every unit of it is\n // on an active fulfillment, so a line shipped 2 of 5 is still open and a\n // cancelled fulfillment covers nothing.\n const covered = new Set<number>()\n for (const state of orderLineFulfillmentStates(order)) {\n if (state.quantity > 0 && state.remainingQuantity === 0) covered.add(state.lineItemId)\n }\n return covered\n}\n\n/** Distinct suppliers with at least one line on this order (AGL-2455). */\nexport function orderSupplierIds(order: Partial<HostOrder>): string[] {\n return [\n ...new Set(\n (order.lineItems ?? [])\n .map((line) => String(line.supplierId ?? ''))\n .filter(Boolean),\n ),\n ]\n}\n\n/** Line indexes belonging to one supplier (AGL-2455). */\nexport function supplierLineItemIds(\n order: Partial<HostOrder>,\n supplierId: string,\n): number[] {\n return (order.lineItems ?? [])\n .map((line, index) => ({ line, index }))\n .filter(({ line }) => String(line.supplierId ?? '') === supplierId)\n .map(({ index }) => index)\n}\n\n/**\n * The status an order reaches once `covered` lines are shipped (AGL-2455).\n *\n * COMPUTED from the fulfillment set rather than written as a literal, which is\n * the whole of this issue's third requirement. `partially_fulfilled` has existed\n * in `ORDER_TRANSITIONS` since orders shipped and nothing ever wrote it.\n *\n * An order with no lines at all reads as fulfilled — there is nothing left to\n * ship, and answering `partially_fulfilled` would strand it forever.\n */\nexport function statusAfterFulfilling(\n order: Partial<HostOrder>,\n covered: Set<number>,\n): OrderStatus {\n const lines = order.lineItems ?? []\n if (lines.length === 0) return 'fulfilled'\n // A digital or service line has nothing to ship (AGL-3611), so it never\n // holds the order open.\n return lines.every((line, index) => covered.has(index) || !lineRequiresShipping(line))\n ? 'fulfilled'\n : 'partially_fulfilled'\n}\n\n/**\n * Statuses under which an order grants NO entitlement at all (AGL-2454).\n *\n * `pending` has not paid, `cancelled` was undone, `refunded` was given back.\n * The literal list was copied into `download.ts`, `gate.ts`,\n * `membership-account.ts` and `reviews.ts`; it lives here now so a change to\n * what \"withdrawn\" means reaches all of them at once.\n */\nconst WITHDRAWN_ORDER_STATUSES: OrderStatus[] = [\n 'pending',\n 'cancelled',\n 'refunded',\n]\n\n/**\n * How much of this order has been given back (AGL-2454).\n *\n * `full` follows `status` OR the money, because the two can legitimately\n * disagree for a moment: `refund.ts` reserves `refundedCents` before it calls\n * Stripe and flips `status` after, and a lost dispute reverses money on an\n * order whose status is still `paid` until it closes. Reading only the status\n * would treat that window as \"nothing refunded\".\n */\nexport function orderRefundState(\n order: Partial<HostOrder>,\n): 'none' | 'partial' | 'full' {\n const refundedCents = Math.max(0, Number(order.refundedCents ?? 0))\n const totalCents =\n order.totals?.totalCents ?? Number(order.amountCents ?? 0)\n if (order.status === 'refunded') return 'full'\n if (!(refundedCents > 0)) return 'none'\n return totalCents > 0 && refundedCents >= totalCents ? 'full' : 'partial'\n}\n\n/**\n * Is this line's entitlement withdrawn?\n *\n * By INDEX, matching `refundedLineItemIds`. A cart can hold the same product on\n * two lines and only one may be coming back, so a product-keyed test would\n * revoke goods the buyer still owns.\n */\nexport function orderLineRefunded(\n order: Partial<HostOrder>,\n lineIndex: number,\n): boolean {\n return (order.refundedLineItemIds ?? []).includes(lineIndex)\n}\n\n/**\n * THE ONE ENTITLEMENT TEST (AGL-2454). Does this order still entitle the buyer\n * to `productId`?\n *\n * Five gates asked this question and all five asked it as\n * `!['pending','cancelled','refunded'].includes(status) && orderContainsProduct(…)`.\n * That reading is why a partial refund revoked NOTHING: `refund.ts` writes\n * `status: 'refunded'` only when the order is FULLY refunded, so a 99%-refunded\n * order stayed `paid` and every gate said yes — downloads, licence keys, gated\n * content and the right to post a verified review, all still live.\n *\n * Withdrawn now means either of two things, and both had to be expressible in\n * one place or the five gates would drift again:\n *\n * - the whole order is withdrawn by `status`, as before; or\n * - every line carrying this product was refunded BY NAME, which is the case a\n * line-scoped refund creates and the only case where an amount can honestly\n * be attributed to goods.\n *\n * `'any'` asks whether the order still entitles ANYTHING, which is what the\n * membership paywall's subscription-shaped probe needs.\n *\n * The legacy flat `productId` order (one line, no `lineItems`) is lifted by\n * `liftLegacyOrder` before it gets here, so index 0 is its only line and a\n * line-scoped refund of it behaves like a full one.\n */\nexport function orderEntitlesProduct(\n order: Partial<HostOrder>,\n productId: string | 'any',\n): boolean {\n if (WITHDRAWN_ORDER_STATUSES.includes(order.status as OrderStatus)) {\n return false\n }\n const lines = order.lineItems ?? []\n if (productId === 'any') {\n if (lines.length === 0) return true\n return lines.some((_line, index) => !orderLineRefunded(order, index))\n }\n const matching = lines\n .map((line, index) => ({ line, index }))\n .filter(({ line }) => line.productId === productId)\n if (matching.length === 0) {\n // The legacy flat shape, which `liftLegacyOrder` normally converts. Kept so\n // an unlifted row is not silently un-entitled — a false negative here locks\n // a paying buyer out of goods they bought.\n return order.productId === productId\n }\n return matching.some(({ index }) => !orderLineRefunded(order, index))\n}\n\n/**\n * Product ids whose entitlement this order has withdrawn line by line\n * (AGL-2454) — every line for the product refunded, none left standing.\n *\n * What `refund.ts` uses to decide which licence keys to RETIRE. Not \"any line\n * refunded\": a buyer who returned one of two copies still holds the product.\n */\nexport function refundedProductIds(order: Partial<HostOrder>): string[] {\n const lines = order.lineItems ?? []\n const ids = new Set<string>()\n for (const line of lines) {\n if (!ids.has(line.productId) && !orderEntitlesProduct(order, line.productId)) {\n ids.add(line.productId)\n }\n }\n return [...ids]\n}\n\n/**\n * The refund line the console and the merchant read (AGL-2454).\n *\n * Kept in the model because three surfaces show it and \"partially refunded\"\n * must not mean different things on two of them. Empty for an order with\n * nothing refunded — there is nothing to say.\n */\nexport function orderRefundSummary(order: Partial<HostOrder>): string {\n const state = orderRefundState(order)\n if (state === 'none') return ''\n const refundedCents = Math.max(0, Number(order.refundedCents ?? 0))\n const totalCents = order.totals?.totalCents ?? Number(order.amountCents ?? 0)\n if (state === 'full') return `Refunded in full ($${usd(refundedCents)})`\n const revoked = (order.refundedLineItemIds ?? []).length\n const lines = (order.lineItems ?? []).length\n const scope =\n revoked > 0\n ? `${revoked} of ${lines} line${lines === 1 ? '' : 's'} withdrawn`\n : 'no lines withdrawn — refunded by amount'\n return `Partially refunded ($${usd(refundedCents)} of $${usd(\n totalCents,\n )}) — ${scope}`\n}\n\n/** `12345` -> `123.45`, the CSV's money format. */\nfunction usd(cents: number): string {\n return (cents / 100).toFixed(2)\n}\n\n/**\n * Display labels for {@link OrderStatus}, and the MUI chip colour each one\n * carries (AGL-2136). The orders screen advertised on `/product/commerce`\n * shows the status as a COLOURED pill — `Paid`, `Fulfilled`, `Refunded` in\n * three different colours — where the console rendered every status as the\n * same default outlined chip, so the one column a merchant scans first\n * carried no signal at all.\n *\n * Kept in the model rather than in the card because three surfaces render a\n * status (the orders list, the detail dialog and the dashboard glance\n * widget) and a colour that means \"refunded\" on one of them must not mean\n * something else on the next.\n */\nexport const ORDER_STATUS_LABELS: Record<OrderStatus, string> = {\n pending: 'Pending',\n paid: 'Paid',\n partially_fulfilled: 'Partly fulfilled',\n fulfilled: 'Fulfilled',\n delivered: 'Delivered',\n cancelled: 'Canceled',\n refunded: 'Refunded',\n}\n\n/**\n * Chip colour per status. `pending` is deliberately `default` rather than\n * `warning`: an unpaid order is not a problem, it is simply not money yet,\n * and reserving the alarming colours for `cancelled`/`refunded` is what\n * makes them readable at a glance.\n */\nexport const ORDER_STATUS_COLOR: Record<\n OrderStatus,\n 'default' | 'success' | 'info' | 'warning' | 'error'\n> = {\n pending: 'default',\n paid: 'success',\n partially_fulfilled: 'warning',\n fulfilled: 'info',\n delivered: 'info',\n cancelled: 'default',\n refunded: 'error',\n}\n\n/** Display labels for {@link OrderChannel}. */\nexport const ORDER_CHANNEL_LABELS: Record<OrderChannel, string> = {\n online: 'Online',\n pos: 'POS',\n draft: 'Draft',\n subscription: 'Subscription',\n marketplace: 'Marketplace',\n}\n\n/** Human label for a channel, tolerating a legacy/unknown value. */\nexport function orderChannelLabel(channel: string | undefined): string {\n return ORDER_CHANNEL_LABELS[(channel ?? 'online') as OrderChannel] ?? channel ?? 'Online'\n}\n\n/**\n * Net cents an order actually earned: the charged total less anything\n * refunded. The three commerce surfaces each had their own inline copy of\n * this expression; a refunded order counted as revenue on whichever one\n * was written first.\n */\nexport function orderNetCents(order: Partial<HostOrder>): number {\n const gross = Number(\n (order as { totals?: { totalCents?: number } }).totals?.totalCents ??\n (order as { amountCents?: number }).amountCents ??\n 0,\n )\n const refunded = Number((order as { refundedCents?: number }).refundedCents ?? 0)\n // A register tip sits outside `totalCents` (AGL-3607), so a refund that\n // handed the tip back too must not take the tip out of the SALE's revenue a\n // second time: what comes off is capped at what the sale itself was.\n const tipCents = Number(order.totals?.tipCents ?? 0)\n return gross - (tipCents > 0 ? Math.min(refunded, gross) : refunded)\n}\n\n/** Milliseconds an order was created at, across every writer's field shape. */\nexport function orderCreatedAtMs(order: Partial<HostOrder> & {\n createdAtMs?: number\n createdAt?: { seconds?: number; toDate?: () => Date }\n}): number {\n if (typeof order.createdAtMs === 'number') return order.createdAtMs\n const seconds = order.createdAt?.seconds\n if (typeof seconds === 'number') return seconds * 1000\n const date = order.createdAt?.toDate?.()\n return date ? date.getTime() : 0\n}\n\n/** One window's totals, plus how it moved against the window before it. */\nexport interface OrderWindowSummary {\n /** Net revenue in cents over the window. */\n revenueCents: number\n /** Orders counted in the window. */\n orders: number\n /** Average order value in cents, 0 when the window is empty. */\n aovCents: number\n /**\n * Percentage change vs the immediately preceding window of the same\n * length, rounded to one decimal. `null` when the prior window is EMPTY —\n * \"+100%\" against zero is not a growth rate, it is a first sale, and the\n * tile must say nothing rather than say something false.\n */\n revenueDeltaPct: number | null\n ordersDeltaPct: number | null\n aovDeltaPct: number | null\n}\n\nconst ORDER_WINDOW_DAY_MS = 24 * 60 * 60 * 1000\n\n/**\n * Percentage change from `previous` to `current`, or `null` when there is\n * no baseline to divide by.\n */\nfunction deltaPct(current: number, previous: number): number | null {\n if (!previous) return null\n return Math.round(((current - previous) / previous) * 1000) / 10\n}\n\n/**\n * Summarises a window of orders and the window before it (AGL-2136).\n *\n * Every commerce mockup we advertise shows the money tiles carrying a\n * period-over-period delta — `Revenue $26,540 +8.1%` — and the product had\n * no prior-window computation anywhere in the repo, so no surface could\n * have rendered one.\n *\n * `pending` and `cancelled` orders are excluded, matching the analytics\n * card: neither is money. Refunds are subtracted rather than dropped, so a\n * refund inside the window pushes the delta DOWN, which is the whole point\n * of showing it.\n */\nexport function summarizeOrderWindow(\n orders: readonly (Partial<HostOrder> & {\n createdAtMs?: number\n createdAt?: { seconds?: number; toDate?: () => Date }\n })[],\n options: { nowMs: number; days?: number } ,\n): OrderWindowSummary {\n const days = options.days ?? 30\n const spanMs = days * ORDER_WINDOW_DAY_MS\n const { nowMs } = options\n let revenueCents = 0\n let count = 0\n let priorRevenueCents = 0\n let priorCount = 0\n for (const order of orders) {\n const lifted = liftLegacyOrder(order)\n if (lifted.status === 'pending' || lifted.status === 'cancelled') continue\n const age = nowMs - orderCreatedAtMs(order)\n if (age < 0) continue\n if (age < spanMs) {\n revenueCents += orderNetCents(order)\n count += 1\n } else if (age < spanMs * 2) {\n priorRevenueCents += orderNetCents(order)\n priorCount += 1\n }\n }\n const aovCents = count ? Math.round(revenueCents / count) : 0\n const priorAovCents = priorCount\n ? Math.round(priorRevenueCents / priorCount)\n : 0\n return {\n revenueCents,\n orders: count,\n aovCents,\n revenueDeltaPct: deltaPct(revenueCents, priorRevenueCents),\n ordersDeltaPct: deltaPct(count, priorCount),\n aovDeltaPct: deltaPct(aovCents, priorAovCents),\n }\n}\n\n/** Appends a timeline event immutably (webhook + console share this). */\nexport function appendOrderEvent(\n order: Pick<HostOrder, 'timeline'>,\n event: string,\n detail?: string,\n atMs = Date.now(),\n): OrderTimelineEvent[] {\n return [...(order.timeline ?? []), { atMs, event, ...(detail ? { detail } : {}) }]\n}\n"],"names":["stripeIdIsTestMode","lineRequiresShipping","orderLineFulfillmentStates","ORDER_TRANSITIONS","pending","paid","partially_fulfilled","fulfilled","delivered","cancelled","refunded","canTransitionOrder","from","to","includes","wholeCents","value","cents","Math","round","Number","isFinite","apportionCents","weights","totalCents","safe","map","weight","max","basis","reduce","sum","total","length","pot","min","exact","shares","floor","remainder","share","order","index","fraction","sort","a","b","entry","orderLineRefundCents","lineIndexes","lines","lineItems","gross","line","unitAmountCents","quantity","totals","discountCents","wanted","Set","filter","isInteger","orderIsTestMode","livemode","checkoutSessionId","$id","computeOrderTotals","parts","itemsCents","shippingCents","taxCents","feeCents","computeCheckoutSessionTotals","session","details","total_details","amount_tax","lineItemTaxCents","amount_shipping","amount_discount","pricedInDiscountCents","amountTotal","amount_total","NaN","extrasCents","metadataCents","computeBuyNowOrder","snapshot","metadata","rawQuantity","goodsCents","variantId","String","productId","name","variantLabel","sku","productType","supplierId","subscriptionInvoiceFeeBasisCents","invoice","scalarTax","tax","sumAmounts","total_taxes","total_tax_amounts","shipping_cost","subscriptionInvoiceItemsOnlyFeeCents","chargedCents","application_fee_amount","basisCents","subscriptionRecurringBasisCents","soldLineItems","reason","billing_reason","billsTheFullPrice","cycleBasis","unit","list","amount","lineInterval","interval","price","recurring","undefined","selectSubscriptionInvoiceLine","data","find","proration","subscriptionInvoiceInterval","computeSubscriptionInvoiceOrder","subscriptionLine","listUnit","unit_amount","isSubscriptionLine","push","description","slice","amount_paid","total_discount_amounts","formatOrderNumber","docId","number","toUpperCase","liftLegacyOrder","raw","Array","isArray","status","amountCents","channel","orderContainsProduct","some","coveredLineItemIds","covered","state","remainingQuantity","add","lineItemId","orderSupplierIds","Boolean","supplierLineItemIds","statusAfterFulfilling","every","has","WITHDRAWN_ORDER_STATUSES","orderRefundState","refundedCents","orderLineRefunded","lineIndex","refundedLineItemIds","orderEntitlesProduct","_line","matching","refundedProductIds","ids","orderRefundSummary","usd","revoked","scope","toFixed","ORDER_STATUS_LABELS","ORDER_STATUS_COLOR","ORDER_CHANNEL_LABELS","online","pos","draft","subscription","marketplace","orderChannelLabel","orderNetCents","tipCents","orderCreatedAtMs","createdAtMs","seconds","createdAt","date","toDate","getTime","ORDER_WINDOW_DAY_MS","deltaPct","current","previous","summarizeOrderWindow","orders","options","days","spanMs","nowMs","revenueCents","count","priorRevenueCents","priorCount","lifted","age","aovCents","priorAovCents","revenueDeltaPct","ordersDeltaPct","aovDeltaPct","appendOrderEvent","event","detail","atMs","Date","now","timeline"],"mappings":"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"}
|
|
@@ -0,0 +1,146 @@
|
|
|
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
|
+
/**
|
|
18
|
+
* The storefront payment methods a merchant chooses between (AGL-3629).
|
|
19
|
+
*
|
|
20
|
+
* ## Who decides what a shopper sees
|
|
21
|
+
*
|
|
22
|
+
* Every storefront charge is a DESTINATION charge with no `on_behalf_of`, so
|
|
23
|
+
* the PLATFORM is the merchant of record and Stripe offers the methods the
|
|
24
|
+
* platform's payment method configuration turns on ("dynamic payment
|
|
25
|
+
* methods"), filtered by currency, amount, the shopper's country and device.
|
|
26
|
+
* No session pins `payment_method_types`. A merchant therefore cannot ADD a
|
|
27
|
+
* method the platform does not offer — they can only take one away, and that
|
|
28
|
+
* is what the two levers below do:
|
|
29
|
+
*
|
|
30
|
+
* - `excluded_payment_method_types` on the Checkout Session, for every method
|
|
31
|
+
* Stripe lets a session exclude. Measured accepted at both the account
|
|
32
|
+
* default API version and the native checkout's pinned dahlia version, so
|
|
33
|
+
* it applies to the in-page checkout AND the hosted Stripe page alike.
|
|
34
|
+
* - The Payment Element's `wallets` option for Apple Pay, Google Pay and
|
|
35
|
+
* Link, which Stripe documents as NOT excludable per session. Link is also
|
|
36
|
+
* hidden on the session with `wallet_options[link][display]=never`, which
|
|
37
|
+
* reaches the hosted page; Apple Pay and Google Pay on a hosted Stripe page
|
|
38
|
+
* follow the device and cannot be hidden per merchant.
|
|
39
|
+
*
|
|
40
|
+
* ## Why toggling a method never moves a fee
|
|
41
|
+
*
|
|
42
|
+
* `application_fee_amount` is priced against the dearest method the platform
|
|
43
|
+
* could offer (`SALE_PROCESSING_PERCENT`, the BNPL rate), because the shopper
|
|
44
|
+
* picks how to pay after the fee is fixed. Every method here costs Stripe that
|
|
45
|
+
* much or less, so turning one off or on changes what a shopper may choose and
|
|
46
|
+
* never what the platform recovers.
|
|
47
|
+
*/
|
|
48
|
+
/** A method the merchant may switch off (or, for crypto, on). */
|
|
49
|
+
export type StorefrontPaymentMethodId = 'apple_pay' | 'google_pay' | 'link' | 'klarna' | 'afterpay_clearpay' | 'affirm' | 'cashapp' | 'amazon_pay' | 'crypto';
|
|
50
|
+
export type StorefrontPaymentMethodKind = 'wallet' | 'bnpl' | 'crypto';
|
|
51
|
+
/** How a toggle reaches Stripe. */
|
|
52
|
+
export type StorefrontPaymentMethodControl =
|
|
53
|
+
/** The Payment Element's `wallets` option (and, for Link, the session). */
|
|
54
|
+
'wallet'
|
|
55
|
+
/** `excluded_payment_method_types` on the Checkout Session. */
|
|
56
|
+
| 'exclude';
|
|
57
|
+
export interface StorefrontPaymentMethodSpec {
|
|
58
|
+
id: StorefrontPaymentMethodId;
|
|
59
|
+
label: string;
|
|
60
|
+
kind: StorefrontPaymentMethodKind;
|
|
61
|
+
control: StorefrontPaymentMethodControl;
|
|
62
|
+
/**
|
|
63
|
+
* On when the merchant has never chosen. Every method that was already
|
|
64
|
+
* reaching shoppers before AGL-3629 defaults on, so saving nothing changes
|
|
65
|
+
* nothing; crypto, which settles differently and cannot be disputed,
|
|
66
|
+
* defaults off and is the merchant's to opt into.
|
|
67
|
+
*/
|
|
68
|
+
defaultOn: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* The connected-account capability Stripe asks platforms to request before
|
|
71
|
+
* the method is accepted for that account. Absent for the card wallets,
|
|
72
|
+
* which ride `card_payments`.
|
|
73
|
+
*/
|
|
74
|
+
capability?: string;
|
|
75
|
+
/** The platform payment method configuration's key for this method. */
|
|
76
|
+
configurationKey: string;
|
|
77
|
+
/** What a shopper needs, in a sentence the settings card shows. */
|
|
78
|
+
requirements: string;
|
|
79
|
+
/** Amount and currency limits, as Stripe publishes them for US accounts. */
|
|
80
|
+
limits: string;
|
|
81
|
+
/** Lowest charge Stripe offers it for, in USD cents, when published. */
|
|
82
|
+
minCents?: number;
|
|
83
|
+
/** Highest charge Stripe offers it for, in USD cents, when published. */
|
|
84
|
+
maxCents?: number;
|
|
85
|
+
/** False where Stripe does not offer it in a subscription-mode session. */
|
|
86
|
+
subscriptions: boolean;
|
|
87
|
+
/** Shown on a registered domain only (Apple Pay must be; the rest benefit). */
|
|
88
|
+
needsDomain: boolean;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The catalog, in the order the settings card lists it. Limits are Stripe's
|
|
92
|
+
* published US figures (docs.stripe.com/payments/buy-now-pay-later and each
|
|
93
|
+
* method's page, read 2026-10-07); Stripe applies them itself, and they are
|
|
94
|
+
* here so a merchant can read why a method did not appear on an order.
|
|
95
|
+
*/
|
|
96
|
+
export declare const STOREFRONT_PAYMENT_METHODS: readonly StorefrontPaymentMethodSpec[];
|
|
97
|
+
/** The merchant's choices, as stored on `hosts/{hostId}/settings/store`. */
|
|
98
|
+
export type StorefrontPaymentMethodSettings = Partial<Record<StorefrontPaymentMethodId, boolean>>;
|
|
99
|
+
export declare const STOREFRONT_PAYMENT_METHOD_IDS: readonly StorefrontPaymentMethodId[];
|
|
100
|
+
export declare function storefrontPaymentMethodSpec(id: string): StorefrontPaymentMethodSpec | undefined;
|
|
101
|
+
/**
|
|
102
|
+
* Keep only booleans under known ids. The settings document is written by the
|
|
103
|
+
* console, so anything else on it — a string, a removed method, a typo — is
|
|
104
|
+
* read as "never chosen" rather than trusted.
|
|
105
|
+
*/
|
|
106
|
+
export declare function normalizeStorefrontPaymentMethodSettings(raw: unknown): StorefrontPaymentMethodSettings;
|
|
107
|
+
/** Whether the merchant offers a method: their choice, else its default. */
|
|
108
|
+
export declare function isStorefrontPaymentMethodOn(settings: StorefrontPaymentMethodSettings | null | undefined, id: StorefrontPaymentMethodId): boolean;
|
|
109
|
+
/** `auto` lets Stripe decide from the device; `never` hides the wallet. */
|
|
110
|
+
export type StorefrontWalletDisplay = 'auto' | 'never';
|
|
111
|
+
/** The Payment Element's `wallets` option, as the browser receives it. */
|
|
112
|
+
export interface StorefrontCheckoutWallets {
|
|
113
|
+
applePay: StorefrontWalletDisplay;
|
|
114
|
+
googlePay: StorefrontWalletDisplay;
|
|
115
|
+
link: StorefrontWalletDisplay;
|
|
116
|
+
}
|
|
117
|
+
export interface StorefrontPaymentMethodControls {
|
|
118
|
+
/** Sent as `excluded_payment_method_types[n]`, in catalog order. */
|
|
119
|
+
excluded: StorefrontPaymentMethodId[];
|
|
120
|
+
/** Handed to the Payment Element. */
|
|
121
|
+
wallets: StorefrontCheckoutWallets;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* What one checkout session sends Stripe for the merchant's choices.
|
|
125
|
+
*
|
|
126
|
+
* A method the merchant left on is NOT added anywhere: dynamic payment methods
|
|
127
|
+
* already offer it when the platform does and the order qualifies. Only the
|
|
128
|
+
* methods turned off are named, which is why a merchant who never opened the
|
|
129
|
+
* card produces the same session as before AGL-3629 — except crypto, which is
|
|
130
|
+
* excluded until chosen, and was never offered by the platform before either.
|
|
131
|
+
*/
|
|
132
|
+
export declare function resolveStorefrontPaymentMethodControls(settings: StorefrontPaymentMethodSettings | null | undefined): StorefrontPaymentMethodControls;
|
|
133
|
+
/**
|
|
134
|
+
* Write the controls onto a Checkout Session param set. Leaves the params
|
|
135
|
+
* untouched when the merchant hid nothing, so a store with default settings —
|
|
136
|
+
* apart from the crypto opt-in — sends what it always sent.
|
|
137
|
+
*/
|
|
138
|
+
export declare function appendStorefrontPaymentMethodParams(params: URLSearchParams, controls: StorefrontPaymentMethodControls): void;
|
|
139
|
+
/** True when the browser has to be told about a hidden wallet at all. */
|
|
140
|
+
export declare function storefrontWalletsHideAny(wallets: StorefrontCheckoutWallets): boolean;
|
|
141
|
+
/**
|
|
142
|
+
* The `wallets` a checkout response carries, as the browser reads it. Anything
|
|
143
|
+
* that is not exactly `'auto'` or `'never'` is dropped, so a malformed or old
|
|
144
|
+
* response shows every wallet rather than breaking the payment form.
|
|
145
|
+
*/
|
|
146
|
+
export declare function readStorefrontCheckoutWallets(raw: unknown): StorefrontCheckoutWallets | undefined;
|