@aglyn/plugins-commerce 1.0.0-beta.284 → 1.0.0-beta.287
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/README.md +1 -1
- package/package.json +13 -13
- package/src/lib/components/cart.js +25 -12
- package/src/lib/components/cart.js.map +1 -1
- package/src/lib/components/commerce-console-page.js +70 -13
- package/src/lib/components/commerce-console-page.js.map +1 -1
- package/src/lib/components/console/order-detail-dialog.component.js +2 -1
- package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
- package/src/lib/components/console/payment-methods-card.component.js +1 -1
- package/src/lib/components/console/payment-methods-card.component.js.map +1 -1
- package/src/lib/components/console/pos/pos-cart-panel.component.d.ts +5 -0
- package/src/lib/components/console/pos/pos-cart-panel.component.js +18 -12
- package/src/lib/components/console/pos/pos-cart-panel.component.js.map +1 -1
- package/src/lib/components/console/pos/pos-hardware-api.d.ts +76 -5
- package/src/lib/components/console/pos/pos-hardware-api.js +53 -11
- package/src/lib/components/console/pos/pos-hardware-api.js.map +1 -1
- package/src/lib/components/console/pos/pos-hardware-buyer.component.d.ts +18 -0
- package/src/lib/components/console/pos/pos-hardware-buyer.component.js +629 -0
- package/src/lib/components/console/pos/pos-hardware-buyer.component.js.map +1 -0
- package/src/lib/components/console/pos/pos-hardware-sale-api.d.ts +162 -0
- package/src/lib/components/console/pos/pos-hardware-sale-api.js +122 -0
- package/src/lib/components/console/pos/pos-hardware-sale-api.js.map +1 -0
- package/src/lib/components/console/pos/pos-hardware-sale-terms.component.d.ts +39 -0
- package/src/lib/components/console/pos/pos-hardware-sale-terms.component.js +251 -0
- package/src/lib/components/console/pos/pos-hardware-sale-terms.component.js.map +1 -0
- package/src/lib/components/console/pos/use-pos-display.js +50 -0
- package/src/lib/components/console/pos/use-pos-display.js.map +1 -1
- package/src/lib/components/console/pos-cellular-control.component.d.ts +41 -0
- package/src/lib/components/console/pos-cellular-control.component.js +211 -0
- package/src/lib/components/console/pos-cellular-control.component.js.map +1 -0
- package/src/lib/components/console/pos-devices-card.component.d.ts +9 -1
- package/src/lib/components/console/pos-devices-card.component.js +47 -15
- package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
- package/src/lib/components/console/pos-display/pos-display-api.d.ts +6 -0
- package/src/lib/components/console/pos-display/pos-display-api.js +8 -1
- package/src/lib/components/console/pos-display/pos-display-api.js.map +1 -1
- package/src/lib/components/console/pos-display-page.component.d.ts +13 -0
- package/src/lib/components/console/pos-display-page.component.js +44 -2
- package/src/lib/components/console/pos-display-page.component.js.map +1 -1
- package/src/lib/components/console/pos-hardware-card.component.d.ts +12 -4
- package/src/lib/components/console/pos-hardware-card.component.js +157 -36
- package/src/lib/components/console/pos-hardware-card.component.js.map +1 -1
- package/src/lib/components/console/pos-hardware-orders-staff-page.component.js +360 -80
- package/src/lib/components/console/pos-hardware-orders-staff-page.component.js.map +1 -1
- package/src/lib/components/console/pos-hardware-terms-page.component.d.ts +34 -0
- package/src/lib/components/console/pos-hardware-terms-page.component.js +257 -0
- package/src/lib/components/console/pos-hardware-terms-page.component.js.map +1 -0
- package/src/lib/components/console/pos-hardware-terms-sign-page.component.d.ts +27 -0
- package/src/lib/components/console/pos-hardware-terms-sign-page.component.js +104 -0
- package/src/lib/components/console/pos-hardware-terms-sign-page.component.js.map +1 -0
- package/src/lib/components/console/pos-hardware-waiting-card.component.d.ts +24 -0
- package/src/lib/components/console/pos-hardware-waiting-card.component.js +205 -0
- package/src/lib/components/console/pos-hardware-waiting-card.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js +297 -201
- package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js.map +1 -1
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.d.ts +3 -0
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js +260 -92
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js.map +1 -1
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.d.ts +9 -0
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.js +15 -6
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.js.map +1 -1
- package/src/lib/components/console/pos-ops/pos-rain-check-dialog.component.d.ts +25 -0
- package/src/lib/components/console/pos-ops/pos-rain-check-dialog.component.js +136 -0
- package/src/lib/components/console/pos-ops/pos-rain-check-dialog.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-shift-panel.component.d.ts +6 -0
- package/src/lib/components/console/pos-ops/pos-shift-panel.component.js +48 -31
- package/src/lib/components/console/pos-ops/pos-shift-panel.component.js.map +1 -1
- package/src/lib/components/console/pos-page.component.d.ts +1 -1
- package/src/lib/components/console/pos-page.component.js +63 -12
- package/src/lib/components/console/pos-page.component.js.map +1 -1
- package/src/lib/components/console/pos-reader-splash-setting.component.js +1 -1
- package/src/lib/components/console/pos-reader-splash-setting.component.js.map +1 -1
- package/src/lib/components/console/product-check-in-fields.component.d.ts +4 -2
- package/src/lib/components/console/product-check-in-fields.component.js +60 -14
- package/src/lib/components/console/product-check-in-fields.component.js.map +1 -1
- package/src/lib/components/console/product-editor-dialog.component.js +44 -1
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/product-hardware-sku-field.component.d.ts +19 -0
- package/src/lib/components/console/product-hardware-sku-field.component.js +92 -0
- package/src/lib/components/console/product-hardware-sku-field.component.js.map +1 -0
- package/src/lib/components/console/product-kiosk-offer-field.component.d.ts +22 -0
- package/src/lib/components/console/product-kiosk-offer-field.component.js +94 -0
- package/src/lib/components/console/product-kiosk-offer-field.component.js.map +1 -0
- package/src/lib/components/console/product-register-fields.component.d.ts +4 -0
- package/src/lib/components/console/product-register-fields.component.js +31 -2
- package/src/lib/components/console/product-register-fields.component.js.map +1 -1
- package/src/lib/components/console/products-hub-card.component.js +11 -2
- package/src/lib/components/console/products-hub-card.component.js.map +1 -1
- package/src/lib/components/console/register-hardware-card.component.d.ts +11 -0
- package/src/lib/components/console/register-hardware-card.component.js +56 -27
- package/src/lib/components/console/register-hardware-card.component.js.map +1 -1
- package/src/lib/components/console/reservation-guests-dialog.component.d.ts +28 -0
- package/src/lib/components/console/reservation-guests-dialog.component.js +189 -0
- package/src/lib/components/console/reservation-guests-dialog.component.js.map +1 -0
- package/src/lib/components/console/reservations-card.component.d.ts +9 -0
- package/src/lib/components/console/reservations-card.component.js +158 -23
- package/src/lib/components/console/reservations-card.component.js.map +1 -1
- package/src/lib/components/console/shippingeasy-card.component.js +1 -1
- package/src/lib/components/console/shippingeasy-card.component.js.map +1 -1
- package/src/lib/components/console/shipstation-card.component.d.ts +1 -1
- package/src/lib/components/console/shipstation-card.component.js +3 -3
- package/src/lib/components/console/shipstation-card.component.js.map +1 -1
- package/src/lib/components/product-detail.d.ts +4 -0
- package/src/lib/components/product-detail.js +40 -5
- package/src/lib/components/product-detail.js.map +1 -1
- package/src/lib/components/product-reviews.js +28 -11
- package/src/lib/components/product-reviews.js.map +1 -1
- package/src/lib/components/reservation-widget.js +28 -5
- package/src/lib/components/reservation-widget.js.map +1 -1
- package/src/lib/components/use-day-picker.d.ts +25 -0
- package/src/lib/components/use-day-picker.js +57 -0
- package/src/lib/components/use-day-picker.js.map +1 -0
- package/src/lib/constants/operator-alerts.d.ts +55 -0
- package/src/lib/constants/operator-alerts.js +156 -1
- package/src/lib/constants/operator-alerts.js.map +1 -1
- package/src/lib/declarations.console-server.js +54 -1
- package/src/lib/declarations.console-server.js.map +1 -1
- package/src/lib/declarations.d.ts +4 -2
- package/src/lib/declarations.js +11 -4
- package/src/lib/declarations.js.map +1 -1
- package/src/lib/declarations.server.js +9 -0
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/model/commerce-cart.d.ts +9 -1
- package/src/lib/model/commerce-cart.js +4 -3
- package/src/lib/model/commerce-cart.js.map +1 -1
- package/src/lib/model/commerce-check-in-product.d.ts +26 -0
- package/src/lib/model/commerce-check-in-product.js +64 -2
- package/src/lib/model/commerce-check-in-product.js.map +1 -1
- package/src/lib/model/commerce-check-in-record.d.ts +127 -0
- package/src/lib/model/commerce-check-in-record.js +36 -0
- package/src/lib/model/commerce-check-in-record.js.map +1 -0
- package/src/lib/model/commerce-check-in.d.ts +91 -87
- package/src/lib/model/commerce-check-in.js +186 -62
- package/src/lib/model/commerce-check-in.js.map +1 -1
- package/src/lib/model/commerce-kiosk-offer.d.ts +31 -0
- package/src/lib/model/commerce-kiosk-offer.js +35 -0
- package/src/lib/model/commerce-kiosk-offer.js.map +1 -0
- package/src/lib/model/commerce-orders.d.ts +76 -0
- package/src/lib/model/commerce-orders.js +6 -0
- package/src/lib/model/commerce-orders.js.map +1 -1
- package/src/lib/model/commerce-pos-day-report.d.ts +54 -1
- package/src/lib/model/commerce-pos-day-report.js +39 -2
- package/src/lib/model/commerce-pos-day-report.js.map +1 -1
- package/src/lib/model/commerce-pos-offline.d.ts +1 -1
- package/src/lib/model/commerce-pos-offline.js +6 -1
- package/src/lib/model/commerce-pos-offline.js.map +1 -1
- package/src/lib/model/commerce-pos-ops.js +3 -2
- package/src/lib/model/commerce-pos-ops.js.map +1 -1
- package/src/lib/model/commerce-pos.d.ts +36 -3
- package/src/lib/model/commerce-pos.js +14 -6
- package/src/lib/model/commerce-pos.js.map +1 -1
- package/src/lib/model/commerce-receipt.js +3 -2
- package/src/lib/model/commerce-receipt.js.map +1 -1
- package/src/lib/model/commerce-reservations.d.ts +69 -4
- package/src/lib/model/commerce-reservations.js +57 -5
- package/src/lib/model/commerce-reservations.js.map +1 -1
- package/src/lib/model/commerce.d.ts +66 -0
- package/src/lib/model/commerce.js +15 -0
- package/src/lib/model/commerce.js.map +1 -1
- package/src/lib/model/order-list-fields.d.ts +11 -0
- package/src/lib/model/order-list-fields.js +17 -1
- package/src/lib/model/order-list-fields.js.map +1 -1
- package/src/lib/model/own-store-tax-summary.d.ts +152 -0
- package/src/lib/model/own-store-tax-summary.js +288 -0
- package/src/lib/model/own-store-tax-summary.js.map +1 -0
- package/src/lib/model/plugin-permissions.d.ts +67 -0
- package/src/lib/model/plugin-permissions.js +224 -7
- package/src/lib/model/plugin-permissions.js.map +1 -1
- package/src/lib/model/pos-cellular.d.ts +223 -0
- package/src/lib/model/pos-cellular.js +293 -0
- package/src/lib/model/pos-cellular.js.map +1 -0
- package/src/lib/model/pos-hardware-in-person.d.ts +98 -0
- package/src/lib/model/pos-hardware-in-person.js +224 -0
- package/src/lib/model/pos-hardware-in-person.js.map +1 -0
- package/src/lib/model/pos-hardware-tax-summary.d.ts +166 -0
- package/src/lib/model/pos-hardware-tax-summary.js +225 -0
- package/src/lib/model/pos-hardware-tax-summary.js.map +1 -0
- package/src/lib/model/pos-hardware-terms.d.ts +74 -0
- package/src/lib/model/pos-hardware-terms.js +115 -0
- package/src/lib/model/pos-hardware-terms.js.map +1 -0
- package/src/lib/model/pos-hardware.d.ts +349 -7
- package/src/lib/model/pos-hardware.js +291 -20
- package/src/lib/model/pos-hardware.js.map +1 -1
- package/src/lib/model/product-write.d.ts +10 -0
- package/src/lib/model/product-write.js +4 -2
- package/src/lib/model/product-write.js.map +1 -1
- package/src/lib/plugin.js +16 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/billing-webhook.js +25 -1
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/cancel-order.js +7 -5
- package/src/lib/server/cancel-order.js.map +1 -1
- package/src/lib/server/cart-checkout.js +20 -8
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/cart.js +29 -0
- package/src/lib/server/cart.js.map +1 -1
- package/src/lib/server/checkout.js +42 -13
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/collection-membership.d.ts +4 -0
- package/src/lib/server/collection-membership.js +5 -10
- package/src/lib/server/collection-membership.js.map +1 -1
- package/src/lib/server/commerce-site-keys.d.ts +29 -0
- package/src/lib/server/commerce-site-keys.js +40 -0
- package/src/lib/server/commerce-site-keys.js.map +1 -0
- package/src/lib/server/draft-order.js +8 -5
- package/src/lib/server/draft-order.js.map +1 -1
- package/src/lib/server/fulfill-order.js +7 -5
- package/src/lib/server/fulfill-order.js.map +1 -1
- package/src/lib/server/gift-cards.js +8 -5
- package/src/lib/server/gift-cards.js.map +1 -1
- package/src/lib/server/local-fulfillment-status.js +9 -6
- package/src/lib/server/local-fulfillment-status.js.map +1 -1
- package/src/lib/server/member-post.js +6 -5
- package/src/lib/server/member-post.js.map +1 -1
- package/src/lib/server/membership-admin-password.d.ts +4 -2
- package/src/lib/server/membership-admin-password.js +10 -8
- package/src/lib/server/membership-admin-password.js.map +1 -1
- package/src/lib/server/membership-admin-remove.d.ts +7 -5
- package/src/lib/server/membership-admin-remove.js +9 -9
- package/src/lib/server/membership-admin-remove.js.map +1 -1
- package/src/lib/server/order-notifications.js +5 -2
- package/src/lib/server/order-notifications.js.map +1 -1
- package/src/lib/server/order-print.js +7 -5
- package/src/lib/server/order-print.js.map +1 -1
- package/src/lib/server/order-receipt-send.js +8 -5
- package/src/lib/server/order-receipt-send.js.map +1 -1
- package/src/lib/server/orders-shipping-prepare.js +9 -6
- package/src/lib/server/orders-shipping-prepare.js.map +1 -1
- package/src/lib/server/own-store-tax-return-source.d.ts +47 -0
- package/src/lib/server/own-store-tax-return-source.js +312 -0
- package/src/lib/server/own-store-tax-return-source.js.map +1 -0
- package/src/lib/server/payment-methods.js +13 -9
- package/src/lib/server/payment-methods.js.map +1 -1
- package/src/lib/server/pos-auth.d.ts +37 -6
- package/src/lib/server/pos-auth.js +40 -15
- package/src/lib/server/pos-auth.js.map +1 -1
- package/src/lib/server/pos-cellular-billing.d.ts +81 -0
- package/src/lib/server/pos-cellular-billing.js +711 -0
- package/src/lib/server/pos-cellular-billing.js.map +1 -0
- package/src/lib/server/pos-cellular-notify.d.ts +47 -0
- package/src/lib/server/pos-cellular-notify.js +150 -0
- package/src/lib/server/pos-cellular-notify.js.map +1 -0
- package/src/lib/server/pos-cellular.d.ts +88 -0
- package/src/lib/server/pos-cellular.js +339 -0
- package/src/lib/server/pos-cellular.js.map +1 -0
- package/src/lib/server/pos-check-in.d.ts +5 -1
- package/src/lib/server/pos-check-in.js +296 -28
- package/src/lib/server/pos-check-in.js.map +1 -1
- package/src/lib/server/pos-day-report.js +29 -3
- package/src/lib/server/pos-day-report.js.map +1 -1
- package/src/lib/server/pos-display.d.ts +16 -0
- package/src/lib/server/pos-display.js +129 -2
- package/src/lib/server/pos-display.js.map +1 -1
- package/src/lib/server/pos-hardware-admin.d.ts +10 -1
- package/src/lib/server/pos-hardware-admin.js +250 -84
- package/src/lib/server/pos-hardware-admin.js.map +1 -1
- package/src/lib/server/pos-hardware-attach.d.ts +105 -0
- package/src/lib/server/pos-hardware-attach.js +443 -0
- package/src/lib/server/pos-hardware-attach.js.map +1 -0
- package/src/lib/server/pos-hardware-buyer-lookup.d.ts +58 -0
- package/src/lib/server/pos-hardware-buyer-lookup.js +180 -0
- package/src/lib/server/pos-hardware-buyer-lookup.js.map +1 -0
- package/src/lib/server/pos-hardware-buyer-record.d.ts +29 -0
- package/src/lib/server/pos-hardware-buyer-record.js +86 -0
- package/src/lib/server/pos-hardware-buyer-record.js.map +1 -0
- package/src/lib/server/pos-hardware-cancel.d.ts +58 -0
- package/src/lib/server/pos-hardware-cancel.js +147 -0
- package/src/lib/server/pos-hardware-cancel.js.map +1 -0
- package/src/lib/server/pos-hardware-closure-notices.d.ts +27 -0
- package/src/lib/server/pos-hardware-closure-notices.js +85 -0
- package/src/lib/server/pos-hardware-closure-notices.js.map +1 -0
- package/src/lib/server/pos-hardware-closure.d.ts +60 -0
- package/src/lib/server/pos-hardware-closure.js +153 -0
- package/src/lib/server/pos-hardware-closure.js.map +1 -0
- package/src/lib/server/pos-hardware-in-person.d.ts +61 -0
- package/src/lib/server/pos-hardware-in-person.js +236 -0
- package/src/lib/server/pos-hardware-in-person.js.map +1 -0
- package/src/lib/server/pos-hardware-notify.d.ts +40 -2
- package/src/lib/server/pos-hardware-notify.js +195 -14
- package/src/lib/server/pos-hardware-notify.js.map +1 -1
- package/src/lib/server/pos-hardware-readers.d.ts +115 -0
- package/src/lib/server/pos-hardware-readers.js +430 -0
- package/src/lib/server/pos-hardware-readers.js.map +1 -0
- package/src/lib/server/pos-hardware-returns.d.ts +116 -0
- package/src/lib/server/pos-hardware-returns.js +502 -0
- package/src/lib/server/pos-hardware-returns.js.map +1 -0
- package/src/lib/server/pos-hardware-sale-fields.d.ts +28 -0
- package/src/lib/server/pos-hardware-sale-fields.js +44 -0
- package/src/lib/server/pos-hardware-sale-fields.js.map +1 -0
- package/src/lib/server/pos-hardware-sale.d.ts +54 -0
- package/src/lib/server/pos-hardware-sale.js +369 -0
- package/src/lib/server/pos-hardware-sale.js.map +1 -0
- package/src/lib/server/pos-hardware-tax-return-source.d.ts +99 -0
- package/src/lib/server/pos-hardware-tax-return-source.js +462 -0
- package/src/lib/server/pos-hardware-tax-return-source.js.map +1 -0
- package/src/lib/server/pos-hardware-terms.d.ts +64 -0
- package/src/lib/server/pos-hardware-terms.js +154 -0
- package/src/lib/server/pos-hardware-terms.js.map +1 -0
- package/src/lib/server/pos-hardware-user-eraser.d.ts +3 -0
- package/src/lib/server/pos-hardware-user-eraser.js +31 -7
- package/src/lib/server/pos-hardware-user-eraser.js.map +1 -1
- package/src/lib/server/pos-hardware-webhook.d.ts +5 -0
- package/src/lib/server/pos-hardware-webhook.js +51 -6
- package/src/lib/server/pos-hardware-webhook.js.map +1 -1
- package/src/lib/server/pos-hardware.d.ts +74 -1
- package/src/lib/server/pos-hardware.js +175 -10
- package/src/lib/server/pos-hardware.js.map +1 -1
- package/src/lib/server/pos-kiosk.d.ts +2 -0
- package/src/lib/server/pos-kiosk.js +17 -12
- package/src/lib/server/pos-kiosk.js.map +1 -1
- package/src/lib/server/pos-offline-card-order.d.ts +64 -0
- package/src/lib/server/pos-offline-card-order.js +102 -0
- package/src/lib/server/pos-offline-card-order.js.map +1 -0
- package/src/lib/server/pos-offline-card-reconcile.d.ts +67 -1
- package/src/lib/server/pos-offline-card-reconcile.js +215 -18
- package/src/lib/server/pos-offline-card-reconcile.js.map +1 -1
- package/src/lib/server/pos-offline-sync.d.ts +39 -0
- package/src/lib/server/pos-offline-sync.js +190 -55
- package/src/lib/server/pos-offline-sync.js.map +1 -1
- package/src/lib/server/pos-ops-gate.d.ts +15 -4
- package/src/lib/server/pos-ops-gate.js +42 -9
- package/src/lib/server/pos-ops-gate.js.map +1 -1
- package/src/lib/server/pos-ops-routes.js +24 -0
- package/src/lib/server/pos-ops-routes.js.map +1 -1
- package/src/lib/server/pos-order-refund-ids.d.ts +28 -0
- package/src/lib/server/pos-order-refund-ids.js +31 -0
- package/src/lib/server/pos-order-refund-ids.js.map +1 -0
- package/src/lib/server/pos-order.js +52 -49
- package/src/lib/server/pos-order.js.map +1 -1
- package/src/lib/server/pos-payment.js +1 -1
- package/src/lib/server/pos-payment.js.map +1 -1
- package/src/lib/server/pos-reader-splash-admin.js +30 -32
- package/src/lib/server/pos-reader-splash-admin.js.map +1 -1
- package/src/lib/server/pos-reader-splash-images-route.d.ts +17 -0
- package/src/lib/server/pos-reader-splash-images-route.js +113 -0
- package/src/lib/server/pos-reader-splash-images-route.js.map +1 -0
- package/src/lib/server/pos-reader-splash-images.d.ts +51 -0
- package/src/lib/server/pos-reader-splash-images.js +123 -0
- package/src/lib/server/pos-reader-splash-images.js.map +1 -0
- package/src/lib/server/pos-reader-splash.d.ts +68 -1
- package/src/lib/server/pos-reader-splash.js +182 -11
- package/src/lib/server/pos-reader-splash.js.map +1 -1
- package/src/lib/server/pos-readers.d.ts +29 -0
- package/src/lib/server/pos-readers.js +134 -35
- package/src/lib/server/pos-readers.js.map +1 -1
- package/src/lib/server/pos-return.d.ts +10 -0
- package/src/lib/server/pos-return.js +26 -1
- package/src/lib/server/pos-return.js.map +1 -1
- package/src/lib/server/pos-sale.js +6 -1
- package/src/lib/server/pos-sale.js.map +1 -1
- package/src/lib/server/pos-shift.js +11 -0
- package/src/lib/server/pos-shift.js.map +1 -1
- package/src/lib/server/pos-staff-pin.js +25 -20
- package/src/lib/server/pos-staff-pin.js.map +1 -1
- package/src/lib/server/pos-terminal.d.ts +14 -4
- package/src/lib/server/pos-terminal.js +34 -38
- package/src/lib/server/pos-terminal.js.map +1 -1
- package/src/lib/server/printers.d.ts +7 -3
- package/src/lib/server/printers.js +18 -12
- package/src/lib/server/printers.js.map +1 -1
- package/src/lib/server/product-drafts.d.ts +11 -1
- package/src/lib/server/product-drafts.js +12 -8
- package/src/lib/server/product-drafts.js.map +1 -1
- package/src/lib/server/product.d.ts +15 -1
- package/src/lib/server/product.js +16 -3
- package/src/lib/server/product.js.map +1 -1
- package/src/lib/server/products-write.d.ts +26 -2
- package/src/lib/server/products-write.js +19 -7
- package/src/lib/server/products-write.js.map +1 -1
- package/src/lib/server/refund.js +34 -11
- package/src/lib/server/refund.js.map +1 -1
- package/src/lib/server/reservation-availability-query.d.ts +8 -0
- package/src/lib/server/reservation-availability-query.js +11 -1
- package/src/lib/server/reservation-availability-query.js.map +1 -1
- package/src/lib/server/reservation-availability.d.ts +1 -1
- package/src/lib/server/reservation-availability.js +7 -2
- package/src/lib/server/reservation-availability.js.map +1 -1
- package/src/lib/server/reservation-guests.d.ts +32 -0
- package/src/lib/server/reservation-guests.js +285 -0
- package/src/lib/server/reservation-guests.js.map +1 -0
- package/src/lib/server/reservation-walk-in.d.ts +11 -1
- package/src/lib/server/reservation-walk-in.js +56 -12
- package/src/lib/server/reservation-walk-in.js.map +1 -1
- package/src/lib/server/reserve.js +18 -5
- package/src/lib/server/reserve.js.map +1 -1
- package/src/lib/server/returns.js +9 -6
- package/src/lib/server/returns.js.map +1 -1
- package/src/lib/server/reviews.d.ts +22 -2
- package/src/lib/server/reviews.js +88 -20
- package/src/lib/server/reviews.js.map +1 -1
- package/src/lib/server/shipping-connectors.js +10 -11
- package/src/lib/server/shipping-connectors.js.map +1 -1
- package/src/lib/server/site-page-resolver.js +20 -3
- package/src/lib/server/site-page-resolver.js.map +1 -1
- package/src/lib/server/store-day.d.ts +33 -0
- package/src/lib/server/store-day.js +46 -0
- package/src/lib/server/store-day.js.map +1 -0
- package/src/lib/server/store-setup-ai-writer.d.ts +2 -1
- package/src/lib/server/store-setup-ai-writer.js +5 -6
- package/src/lib/server/store-setup-ai-writer.js.map +1 -1
- package/src/lib/server/storefront-tax.d.ts +13 -0
- package/src/lib/server/storefront-tax.js +8 -1
- package/src/lib/server/storefront-tax.js.map +1 -1
- package/src/lib/server/tax-return-source.d.ts +32 -3
- package/src/lib/server/tax-return-source.js +99 -20
- package/src/lib/server/tax-return-source.js.map +1 -1
- package/src/lib/server.js +100 -2
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/fake-firestore.js +58 -16
- package/src/lib/testing/fake-firestore.js.map +1 -1
- package/src/lib/testing/pos-ops-memory-firestore.js +18 -4
- package/src/lib/testing/pos-ops-memory-firestore.js.map +1 -1
- package/src/lib/testing/site-register-permissions.d.ts +29 -0
- package/src/lib/testing/site-register-permissions.js +38 -0
- package/src/lib/testing/site-register-permissions.js.map +1 -0
- package/src/lib/transfer/tracking.server.js +8 -5
- package/src/lib/transfer/tracking.server.js.map +1 -1
- package/src/lib/utils/add-to-cart.d.ts +2 -0
- package/src/lib/utils/add-to-cart.js +6 -3
- package/src/lib/utils/add-to-cart.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/order-notifications.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn/server'\nimport { pluginSmsMessaging } from '@aglyn/aglyn/plugin-manager/plugin-sms-messaging'\nimport { resolveSiteTimeZone } from '@aglyn/aglyn/app-utils/collection-entry-date'\nimport {\n resolveHostToken,\n type HostTokenSource,\n} from '@aglyn/aglyn/app-utils/host-tokens'\nimport {\n firebaseAdmin,\n getOrgForHost,\n hostSendingIdentity,\n meterHostEmail,\n renderHostEmailWithTokens,\n} from '@aglyn/tenant-data-admin'\nimport {\n isEmailConfigured,\n renderTextEmailHtml,\n sendEmail,\n} from '@aglyn/shared-util-email'\nimport {\n resolvePluginOrderEmailCopies,\n withOrderEmailDataBlocks,\n} from '@aglyn/aglyn/plugin-manager/plugin-order-email-copies'\nimport * as CommerceModel from '../model'\nimport { orderViewFromData } from './api-v1/order-view'\nimport { mintDownloadToken } from './download'\nimport { orderStatusUrl } from './order-status-token'\n\n/**\n * Every message a buyer gets about their order, through ONE door (AGL-3610).\n *\n * Before this, the buyer heard about their order once — the online receipt —\n * and never again: a register sale sent nothing, a paid payment link sent\n * nothing, and shipping, delivery, refunds and cancellations were silent\n * (`fulfill-order.ts` said so on purpose). Every path that moves an order now\n * makes one call here, and this decides what that moment owes the buyer:\n *\n * - whether the store turned the moment off (`buyerNotifications` on the\n * store settings; ON unless explicitly off — see the model),\n * - which channels can carry it (email when mail is configured and the\n * order has an address; a text when the platform's SMS provider is\n * configured and the order carries the buyer's phone),\n * - and whether it was already sent.\n *\n * ## ONCE PER MOMENT, PER OCCURRENCE, PER CHANNEL\n *\n * Stripe redelivers webhooks, admins click twice, a lost response is retried.\n * Each message is CLAIMED before it is sent: a transaction reads the order's\n * `buyerNotifications` map and writes a `sending` marker for\n * `event__occurrence__channel` only if none is there. Exactly one of two\n * racing callers wins the claim; the other returns `already`. The occurrence\n * is the shipment id for `shipped` and the refund id for `refunded`, so a\n * second parcel or a second partial refund is a new message, while a\n * redelivered one is not.\n *\n * A send that fails RELEASES its claim so the next attempt can try again. A\n * process that dies between claim and send leaves the marker in `sending` and\n * the message unsent — at most once, never twice, which is the right side to\n * fail on for mail a buyer reads as \"did they charge me again?\".\n *\n * ## NEVER THROWS\n *\n * Every caller is past its point of no return — money moved, an order\n * shipped — so a mail outage must not turn into a 500 that invites the\n * merchant to do it again. Failures are logged and returned as outcomes.\n *\n * Shared with the order-processing and POS lanes (AGL-3611, AGL-3607): call\n * {@link notifyOrderBuyer} from any new path that moves an order, and\n * {@link sendOrderReceipt} for an explicit, merchant-asked send.\n */\n\nexport type OrderBuyerEvent = CommerceModel.BuyerNotificationEvent\n\nexport interface OrderRef {\n hostId: string\n orderId: string\n}\n\nexport interface NotifyOrderBuyerOptions {\n /** `shipped`: which shipment. Absent means the order's latest one. */\n fulfillmentId?: string\n /** `refunded`: the refund's own id (Stripe's), the occurrence key. */\n refundId?: string\n /** `refunded`: what THIS refund moved, in cents. */\n refundCents?: number\n /** `refunded`: the line indexes refunded by name, when any were. */\n refundLineIndexes?: readonly number[]\n /** `refunded`: whether this refund closed the order. */\n fullyRefunded?: boolean\n /**\n * The buyer's address when the order document does not carry it — a POS\n * folio sale attributed to the reservation's guest, a payment link whose\n * payer Stripe named. The order's own `customerEmail` wins when present.\n */\n email?: string | null\n /** Injected in specs. */\n firestore?: FirebaseFirestore.Firestore\n}\n\nexport type OrderBuyerChannelOutcome =\n | { channel: CommerceModel.BuyerNotificationChannel; outcome: 'sent' }\n | { channel: CommerceModel.BuyerNotificationChannel; outcome: 'already' }\n | {\n channel: CommerceModel.BuyerNotificationChannel\n outcome: 'failed'\n error: string\n }\n\nexport type NotifyOrderBuyerOutcome =\n | { outcome: 'disabled' }\n | { outcome: 'no_such_order' }\n /** The moment does not apply to this order (a counter sale \"shipping\"). */\n | { outcome: 'not_applicable' }\n /** No channel could carry it: no address, no mail or text configured. */\n | { outcome: 'no_recipient' }\n | { outcome: 'handled'; channels: OrderBuyerChannelOutcome[] }\n\ninterface StoreContext {\n firestore: FirebaseFirestore.Firestore\n hostRef: FirebaseFirestore.DocumentReference\n host: Record<string, unknown>\n settings: Record<string, unknown>\n currency: string\n}\n\nasync function readStore(\n firestore: FirebaseFirestore.Firestore,\n hostId: string,\n): Promise<StoreContext> {\n const hostRef = firestore.collection('hosts').doc(hostId)\n const [hostSnapshot, settingsSnapshot] = await Promise.all([\n hostRef.get().catch(() => null),\n hostRef.collection('settings').doc('store').get().catch(() => null),\n ])\n const settings = (settingsSnapshot?.data?.() ?? {}) as Record<string, unknown>\n return {\n firestore,\n hostRef,\n host: (hostSnapshot?.data?.() ?? {}) as Record<string, unknown>,\n settings,\n currency: String(settings['currency'] ?? 'USD') || 'USD',\n }\n}\n\nfunction lineLabel(line: CommerceModel.OrderLineItem): string {\n return `${line.quantity}× ${line.name}${\n line.variantLabel ? ` (${line.variantLabel})` : ''\n }`\n}\n\n/** The status link, or the store's home page when no link can be minted. */\nfunction statusLink(store: StoreContext, ref: OrderRef): string {\n return (\n orderStatusUrl(store.host as never, ref.hostId, ref.orderId) ??\n Aglyn.hostPublicOrigin({\n cname: typeof store.host['cname'] === 'string' ? store.host['cname'] : null,\n subdomain:\n typeof store.host['subdomain'] === 'string'\n ? store.host['subdomain']\n : null,\n }) ??\n ''\n )\n}\n\ninterface ComposedMessage {\n emailKey: string\n /** Tokens for the designed email. */\n tokens: Record<string, string>\n /** Subject when the template renders none. */\n subject: string\n /** Plain-text body when the template renders none. */\n text: string\n /** The text message, kept short: one or two segments. */\n sms: string\n /** The order timeline line recording the send, channel appended. */\n timelineDetail: string\n}\n\n/** What the moment says. `null` when it does not apply to this order. */\nexport function composeOrderBuyerMessage(input: {\n event: OrderBuyerEvent\n order: CommerceModel.HostOrder\n orderId: string\n businessName: string\n currency: string\n statusUrl: string\n receiptFooter?: string\n /** Absolute digital-delivery links for a receipt, by product id. */\n downloadLinks?: Record<string, string>\n options?: NotifyOrderBuyerOptions\n}): (ComposedMessage & { occurrence: string }) | null {\n const {\n event,\n order,\n orderId,\n businessName,\n currency,\n statusUrl,\n options = {},\n } = input\n const lines = order.lineItems ?? []\n const number = CommerceModel.formatOrderNumber(order, orderId)\n const money = (cents: number) =>\n CommerceModel.formatOrderMoney(cents, currency)\n const store = businessName || 'The store'\n const base = {\n 'order.number': number,\n 'order.statusUrl': statusUrl,\n 'order.ref': orderId,\n }\n const link = statusUrl ? `\\n\\nView your order: ${statusUrl}` : ''\n const smsLink = statusUrl ? ` ${statusUrl}` : ''\n\n if (event === 'receipt') {\n const totalCents =\n order.totals?.totalCents ?? Number(order.amountCents ?? 0)\n const licenseText = Object.entries(\n ((order as { licenseKeys?: Record<string, string[]> }).licenseKeys ??\n {}) as Record<string, string[]>,\n )\n .flatMap(([productId, keys]) => {\n const line = lines.find((item) => item.productId === productId)\n return (keys ?? []).map(\n (key) => `License key (${line?.name ?? 'product'}): ${key}`,\n )\n })\n .join('\\n')\n const downloads = Object.entries(input.downloadLinks ?? {})\n .map(([productId, url]) => {\n const line = lines.find((item) => item.productId === productId)\n return `Download ${line?.name ?? 'your file'}: ${url}`\n })\n .join('\\n')\n const linesText = lines\n .map(\n (line) =>\n `${lineLabel(line)} — ${money(line.unitAmountCents * line.quantity)}`,\n )\n .join('\\n')\n const summary = [linesText, licenseText, downloads]\n .filter(Boolean)\n .join('\\n\\n')\n const footer = String(input.receiptFooter ?? '')\n return {\n occurrence: 'order',\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.receipt,\n tokens: {\n ...base,\n 'order.summary': summary,\n 'order.total': money(totalCents),\n 'store.receiptFooter': footer,\n },\n subject: 'Receipt for your order',\n text:\n `Thanks for your purchase!\\n\\n${summary}\\n\\nTotal: ${money(totalCents)}\\n` +\n `Order ${number}` +\n (footer ? `\\n\\n${footer}` : '') +\n link,\n sms: `${store}: receipt for order ${number}, ${money(totalCents)}.${smsLink}`,\n timelineDetail: 'Receipt sent',\n }\n }\n\n if (event === 'shipped') {\n const fulfillments = order.fulfillments ?? []\n const fulfillment = options.fulfillmentId\n ? fulfillments.find((entry) => entry.id === options.fulfillmentId)\n : [...fulfillments].sort((a, b) => (b.atMs ?? 0) - (a.atMs ?? 0))[0]\n if (!fulfillment) return null\n // Collected at the counter or handed over by the store's driver\n // (AGL-3624): \"picked up\" and \"delivered\" say so, never \"shipped\".\n if (fulfillment.handover) return null\n // The units THIS shipment carried (AGL-3611): a partial shipment of a\n // line names its own count, not the line's.\n const shipped = CommerceModel.fulfillmentLineQuantities(order, fulfillment)\n .map((entry) =>\n lines[entry.lineItemId] ? { ...lines[entry.lineItemId], quantity: entry.quantity } : null,\n )\n .filter(Boolean) as CommerceModel.OrderLineItem[]\n const tracking = CommerceModel.fulfillmentTrackingUrl(fulfillment)\n const hasTracking = Boolean(fulfillment.trackingNumber)\n // Nothing physical left the building: a register sale handed over the\n // counter, or a digital-only order the merchant marked fulfilled. A\n // \"your order is on its way\" email for either is noise at best.\n const physical = shipped.some(\n (line) =>\n line.productType !== 'digital' && line.productType !== 'service',\n )\n if (!hasTracking && (order.channel === 'pos' || !physical)) return null\n // Shippable units still out, over every active shipment (AGL-3611).\n const remainingUnits = CommerceModel.remainingFulfillmentLines(order).reduce(\n (sum, entry) => sum + entry.quantity,\n 0,\n )\n const carrier = CommerceModel.carrierLabelFor(fulfillment.carrier)\n const trackingSentence = hasTracking\n ? `Tracking: ${[carrier, fulfillment.trackingNumber]\n .filter(Boolean)\n .join(' ')}${tracking ? ` — ${tracking}` : ''}`\n : ''\n const remainingSentence =\n remainingUnits > 0\n ? `${remainingUnits} more item${remainingUnits === 1 ? '' : 's'} will ship separately.`\n : ''\n const summary = shipped.map(lineLabel).join('\\n')\n return {\n occurrence: fulfillment.id,\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.shipped,\n tokens: {\n ...base,\n 'shipment.summary': summary,\n 'shipment.carrier': carrier,\n 'shipment.trackingNumber': String(fulfillment.trackingNumber ?? ''),\n 'shipment.tracking': trackingSentence,\n 'shipment.remaining': remainingSentence,\n },\n subject: `Your order ${number} has shipped`,\n text:\n `${store} shipped items from order ${number}.\\n\\n${summary}` +\n (trackingSentence ? `\\n\\n${trackingSentence}` : '') +\n (remainingSentence ? `\\n\\n${remainingSentence}` : '') +\n link,\n sms:\n `${store}: order ${number} has shipped.` +\n (tracking ? ` Track it: ${tracking}` : smsLink),\n timelineDetail: 'Shipping confirmation sent',\n }\n }\n\n if (event === 'delivered') {\n // A counter sale is \"delivered\" the moment it is handed over.\n if (order.channel === 'pos') return null\n const summary = lines.map(lineLabel).join('\\n')\n return {\n occurrence: 'order',\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.delivered,\n tokens: { ...base, 'order.summary': summary },\n subject: `Your order ${number} was delivered`,\n text:\n `${store} marked order ${number} as delivered.\\n\\n${summary}` + link,\n sms: `${store}: order ${number} was delivered.${smsLink}`,\n timelineDetail: 'Delivery confirmation sent',\n }\n }\n\n if (event === 'ready_for_pickup' || event === 'picked_up') {\n const pickup = order.pickup\n if (order.fulfillmentMethod !== 'pickup' || !pickup) return null\n const summary = lines.map(lineLabel).join('\\n')\n const where = `${pickup.locationName}${pickup.address ? `, ${pickup.address}` : ''}`\n const hours = pickup.hours ? `Pickup hours:\\n${pickup.hours}` : ''\n const instructions = pickup.instructions ?? ''\n if (event === 'picked_up') {\n return {\n occurrence: 'order',\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.picked_up,\n tokens: { ...base, 'order.summary': summary, 'pickup.location': where },\n subject: `You picked up order ${number}`,\n text: `Order ${number} was picked up from ${where}. Thanks for shopping with ${store}.\\n\\n${summary}` + link,\n sms: `${store}: order ${number} was picked up. Thank you!${smsLink}`,\n timelineDetail: 'Pickup confirmation sent',\n }\n }\n return {\n occurrence: 'order',\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.ready_for_pickup,\n tokens: {\n ...base,\n 'order.summary': summary,\n 'pickup.location': where,\n 'pickup.hours': hours,\n 'pickup.instructions': instructions,\n },\n subject: `Your order ${number} is ready for pickup`,\n text:\n `Your order ${number} is ready for pickup at ${where}.\\n\\n${summary}` +\n (hours ? `\\n\\n${hours}` : '') +\n (instructions ? `\\n\\n${instructions}` : '') +\n `\\n\\nBring your order number, ${number}.` +\n link,\n sms: `${store}: order ${number} is ready for pickup at ${pickup.locationName}.${smsLink}`,\n timelineDetail: 'Ready-for-pickup notice sent',\n }\n }\n\n if (event === 'out_for_delivery') {\n const delivery = order.localDelivery\n if (order.fulfillmentMethod !== 'local_delivery' || !delivery) return null\n const summary = lines.map(lineLabel).join('\\n')\n const window = delivery.windowLabel ?? ''\n return {\n // Each run out the door is its own message: a failed drop sent out\n // again tells the buyer again.\n occurrence: String(delivery.outForDeliveryAtMs ?? 'order'),\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.out_for_delivery,\n tokens: { ...base, 'order.summary': summary, 'delivery.window': window },\n subject: `Your order ${number} is out for delivery`,\n text:\n `${store} is on the way with order ${number}` +\n (window ? `, in your window ${window}` : '') +\n `.\\n\\n${summary}` +\n link,\n sms: `${store}: order ${number} is out for delivery${window ? ` (${window})` : ''}.${smsLink}`,\n timelineDetail: 'Out-for-delivery notice sent',\n }\n }\n\n if (event === 'refunded') {\n const refundCents = Math.max(0, Math.round(Number(options.refundCents ?? 0)))\n if (!(refundCents > 0)) return null\n const refundedLines = (options.refundLineIndexes ?? [])\n .map((index) => lines[index])\n .filter(Boolean) as CommerceModel.OrderLineItem[]\n const summary = refundedLines.map(lineLabel).join('\\n')\n const note = options.fullyRefunded\n ? 'Your order has been refunded in full.'\n : 'The rest of your order is unchanged.'\n return {\n // The refund's own id is the occurrence; without one, the running\n // total after this refund is (each settled refund raises it).\n occurrence:\n options.refundId ||\n `total-${Number(order.refundedCents ?? 0) || refundCents}`,\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.refunded,\n tokens: {\n ...base,\n 'refund.amount': money(refundCents),\n 'refund.summary': summary,\n 'refund.note': note,\n },\n subject: `Refund for your order ${number}`,\n text:\n `${store} refunded ${money(refundCents)} on order ${number}.` +\n (summary ? `\\n\\n${summary}` : '') +\n `\\n\\n${note}\\n\\nRefunds go back to the original payment method and ` +\n 'usually appear within 5 to 10 business days, depending on your bank.' +\n link,\n sms: `${store}: ${money(refundCents)} refunded on order ${number}.${smsLink}`,\n timelineDetail: `Refund confirmation sent (${money(refundCents)})`,\n }\n }\n\n // cancelled\n const summary = lines.map(lineLabel).join('\\n')\n const wasPaid = Boolean(\n order.paymentIntentId ||\n (order.channel === 'pos' && order.status !== 'pending') ||\n (order.timeline ?? []).some((entry) => entry?.event === 'paid'),\n )\n const note = wasPaid\n ? `Questions about a payment? Reply to this email or contact ${store}.`\n : ''\n return {\n occurrence: 'order',\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.cancelled,\n tokens: { ...base, 'order.summary': summary, 'cancel.note': note },\n subject: `Your order ${number} was canceled`,\n text:\n `${store} canceled order ${number}.\\n\\n${summary}` +\n (note ? `\\n\\n${note}` : '') +\n link,\n sms: `${store}: order ${number} was canceled.${smsLink}`,\n timelineDetail: 'Cancellation notice sent',\n }\n}\n\n/**\n * Claims one message in a transaction. `true` when this caller owns the send;\n * `false` when a marker is already there (sent, or being sent).\n */\nasync function claimMessage(\n firestore: FirebaseFirestore.Firestore,\n orderRef: FirebaseFirestore.DocumentReference,\n marker: string,\n channel: CommerceModel.BuyerNotificationChannel,\n): Promise<boolean> {\n return firestore.runTransaction(async (transaction) => {\n const snapshot = await transaction.get(orderRef)\n if (!snapshot.exists) return false\n const sent = (snapshot.get('buyerNotifications') ?? {}) as Record<\n string,\n unknown\n >\n if (sent[marker]) return false\n transaction.update(orderRef, {\n [`buyerNotifications.${marker}`]: {\n state: 'sending',\n channel,\n atMs: Date.now(),\n },\n })\n return true\n })\n}\n\n/** Marks a claimed message sent and records it on the order timeline. */\nasync function settleMessage(\n firestore: FirebaseFirestore.Firestore,\n orderRef: FirebaseFirestore.DocumentReference,\n marker: string | null,\n channel: CommerceModel.BuyerNotificationChannel,\n detail: string,\n): Promise<void> {\n await firestore\n .runTransaction(async (transaction) => {\n const snapshot = await transaction.get(orderRef)\n if (!snapshot.exists) return\n const order = CommerceModel.liftLegacyOrder(\n (snapshot.data() ?? {}) as never,\n )\n const atMs = Date.now()\n transaction.update(orderRef, {\n ...(marker\n ? {\n [`buyerNotifications.${marker}`]: { state: 'sent', channel, atMs },\n }\n : {}),\n timeline: CommerceModel.appendOrderEvent(\n order,\n 'buyer-notified',\n `${detail} by ${channel === 'sms' ? 'text' : 'email'}`,\n atMs,\n ),\n })\n })\n .catch((error) => {\n console.error('[order-notifications] settle failed', marker, error)\n })\n}\n\nasync function releaseMessage(\n orderRef: FirebaseFirestore.DocumentReference,\n marker: string,\n): Promise<void> {\n await orderRef\n .update({\n [`buyerNotifications.${marker}`]:\n firebaseAdmin.firestore.FieldValue.delete(),\n })\n .catch(() => undefined)\n}\n\n/** How long a buyer email waits on plugins asking to be copied on it. */\nconst EMAIL_COPIES_TIMEOUT_MS = 3_000\n\nasync function deliverEmail(\n store: StoreContext,\n ref: OrderRef,\n to: string,\n message: ComposedMessage,\n context: string,\n /** The moment and order, for plugins copied on it; absent for a merchant-asked resend. */\n about?: { event: OrderBuyerEvent; order: CommerceModel.HostOrder },\n): Promise<void> {\n const designed = await renderHostEmailWithTokens(\n store.firestore,\n ref.hostId,\n message.emailKey,\n message.tokens,\n )\n const org = await getOrgForHost(ref.hostId).catch(() => null)\n // Plugins copied on this email (AGL-3699): a review platform's invitation\n // address, with the data block it reads. Asked through core's seam, by\n // the email's key and moment; nothing answers unless a site connected one.\n const copies = about\n ? await resolvePluginOrderEmailCopies(\n {\n hostId: ref.hostId,\n recordId: ref.orderId,\n emailKey: message.emailKey,\n moment: about.event,\n recipient: to,\n order: orderViewFromData(ref.orderId, about.order as Record<string, any>),\n },\n { timeoutMs: EMAIL_COPIES_TIMEOUT_MS },\n )\n : { bcc: [] as string[], html: '', settle: async () => undefined }\n const subject = designed?.subject ?? message.subject\n const text = designed?.text || message.text\n // A data block lives in the HTML part, so a text-only email gets the same\n // HTML part `sendEmail` would have synthesized, carrying it.\n const html = copies.html\n ? withOrderEmailDataBlocks(designed?.html || renderTextEmailHtml(text, subject), copies.html)\n : designed?.html\n const result = await sendEmail({\n to,\n subject,\n text,\n ...(html ? { html } : {}),\n ...(copies.bcc.length ? { bcc: copies.bcc } : {}),\n fromName: Aglyn.resolveBrandingProfile(org?.org as never).fromName,\n sendingIdentity: await hostSendingIdentity(ref.hostId),\n audience: 'tenant',\n context,\n // Owed to the recipient by their own order: the phishing screen's soft\n // rules never hold it (AGL-3356).\n owedFor: 'order',\n })\n await copies.settle(Boolean(result?.sent))\n // Cost meter (AGL-1438), as every transactional store email.\n await meterHostEmail(ref.hostId)\n}\n\n/**\n * What a text attempt came to: `refused` names why nothing went (null when it\n * went), and `scheduledForMs` says it was held for the buyer's morning.\n */\ninterface SmsDelivery {\n refused: string | null\n scheduledForMs?: number\n}\n\n/**\n * Sends one order text. A receipt goes at once — the buyer is at the counter\n * or the checkout waiting for it. Every later moment (shipped, delivered,\n * refunded, canceled) can fire at any hour, from a carrier scan or a late\n * packing shift, so it asks the provider to keep quiet hours in the STORE's\n * zone: the best stand-in the platform has for a buyer whose own zone it was\n * never told, and right for the local and regional stores most texts come\n * from. Held texts arrive at 8 AM there.\n */\nasync function deliverSms(\n ref: OrderRef,\n to: string,\n message: ComposedMessage,\n context: string,\n quiet: { host: Record<string, unknown> } | null,\n): Promise<SmsDelivery> {\n const sms = pluginSmsMessaging()\n if (!sms?.isConfigured()) return { refused: 'Text messages are not configured' }\n let timeZone: string | null = null\n if (quiet) {\n const owner = await getOrgForHost(ref.hostId).catch(() => null)\n timeZone = resolveSiteTimeZone(\n (owner?.org ?? null) as { timeZone?: string } | null,\n quiet.host as { timeZone?: string },\n )\n }\n const outcome = await sms.send({\n to,\n body: message.sms,\n hostId: ref.hostId,\n purpose: 'transactional',\n context,\n ...(timeZone ? { quietHours: { timeZone } } : {}),\n })\n if (outcome.status !== 'sent') return { refused: outcome.status }\n return {\n refused: null,\n ...(outcome.scheduledForMs ? { scheduledForMs: outcome.scheduledForMs } : {}),\n }\n}\n\n/** Absolute download links for a receipt's digital lines. */\nfunction downloadLinksFor(\n store: StoreContext,\n ref: OrderRef,\n order: CommerceModel.HostOrder,\n): Record<string, string> {\n const origin = Aglyn.hostPublicOrigin({\n cname: typeof store.host['cname'] === 'string' ? store.host['cname'] : null,\n subdomain:\n typeof store.host['subdomain'] === 'string' ? store.host['subdomain'] : null,\n })\n const digital = (order.lineItems ?? []).filter(\n (line) => line.productType === 'digital',\n )\n if (!origin || !digital.length) return {}\n let token: string\n try {\n token = mintDownloadToken(ref.hostId, ref.orderId)\n } catch {\n return {}\n }\n return Object.fromEntries(\n digital.map((line) => [\n line.productId,\n `${origin}/api/commerce/download?hostId=${encodeURIComponent(ref.hostId)}` +\n `&orderId=${encodeURIComponent(ref.orderId)}` +\n `&productId=${encodeURIComponent(line.productId)}&token=${token}`,\n ]),\n )\n}\n\nasync function readOrder(\n firestore: FirebaseFirestore.Firestore,\n ref: OrderRef,\n): Promise<{\n orderRef: FirebaseFirestore.DocumentReference\n order: CommerceModel.HostOrder | null\n}> {\n const orderRef = firestore\n .collection('hosts')\n .doc(ref.hostId)\n .collection('orders')\n .doc(ref.orderId)\n const snapshot = await orderRef.get()\n return {\n orderRef,\n order: snapshot.exists\n ? CommerceModel.liftLegacyOrder((snapshot.data() ?? {}) as never)\n : null,\n }\n}\n\nfunction emailOf(value: unknown): string {\n const email = String(value ?? '').trim()\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email) ? email : ''\n}\n\n/**\n * Tells the buyer about one moment in their order's life, once. See the\n * module header. Never throws.\n */\nexport async function notifyOrderBuyer(\n ref: OrderRef,\n event: OrderBuyerEvent,\n options: NotifyOrderBuyerOptions = {},\n): Promise<NotifyOrderBuyerOutcome> {\n try {\n if (!ref.hostId || !ref.orderId) return { outcome: 'no_such_order' }\n const firestore = options.firestore ?? firebaseAdmin.app().firestore()\n const store = await readStore(firestore, ref.hostId)\n const toggles = store.settings['buyerNotifications']\n if (!CommerceModel.buyerNotificationEnabled(toggles, event)) {\n return { outcome: 'disabled' }\n }\n const { orderRef, order } = await readOrder(firestore, ref)\n if (!order) return { outcome: 'no_such_order' }\n const message = composeOrderBuyerMessage({\n event,\n order,\n orderId: ref.orderId,\n businessName: String(\n resolveHostToken('businessName', store.host as HostTokenSource) ?? '',\n ),\n currency: store.currency,\n statusUrl: statusLink(store, ref),\n receiptFooter: String(store.settings['receiptFooter'] ?? ''),\n downloadLinks:\n event === 'receipt' ? downloadLinksFor(store, ref, order) : undefined,\n options,\n })\n if (!message) return { outcome: 'not_applicable' }\n\n const email = emailOf(order.customerEmail) || emailOf(options.email)\n const phone = String(order.customerPhone ?? '').trim()\n const targets: {\n channel: CommerceModel.BuyerNotificationChannel\n to: string\n }[] = []\n if (email && isEmailConfigured()) targets.push({ channel: 'email', to: email })\n if (\n phone &&\n CommerceModel.buyerNotificationEnabled(toggles, 'texts') &&\n pluginSmsMessaging()?.isConfigured()\n ) {\n targets.push({ channel: 'sms', to: phone })\n }\n if (!targets.length) return { outcome: 'no_recipient' }\n\n const channels: OrderBuyerChannelOutcome[] = []\n for (const target of targets) {\n const marker = CommerceModel.buyerNotificationMarker(\n event,\n message.occurrence,\n target.channel,\n )\n const claimed = await claimMessage(\n firestore,\n orderRef,\n marker,\n target.channel,\n ).catch((error) => {\n console.error('[order-notifications] claim failed', marker, error)\n return false\n })\n if (!claimed) {\n channels.push({ channel: target.channel, outcome: 'already' })\n continue\n }\n const context = message.emailKey\n let held: number | null = null\n try {\n if (target.channel === 'email') {\n await deliverEmail(store, ref, target.to, message, context, { event, order })\n } else {\n const sent = await deliverSms(\n ref,\n target.to,\n message,\n context,\n event === 'receipt' ? null : { host: store.host },\n )\n if (sent.refused) throw new Error(sent.refused)\n held = sent.scheduledForMs ?? null\n }\n await settleMessage(\n firestore,\n orderRef,\n marker,\n target.channel,\n held\n ? `${message.timelineDetail} (held for the morning)`\n : message.timelineDetail,\n )\n channels.push({ channel: target.channel, outcome: 'sent' })\n } catch (error) {\n await releaseMessage(orderRef, marker)\n console.error('[order-notifications] send failed', {\n hostId: ref.hostId,\n orderId: ref.orderId,\n event,\n channel: target.channel,\n error,\n })\n channels.push({\n channel: target.channel,\n outcome: 'failed',\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n return { outcome: 'handled', channels }\n } catch (error) {\n console.error('[order-notifications] notify failed', ref, event, error)\n return { outcome: 'handled', channels: [] }\n }\n}\n\nexport type SendOrderReceiptOutcome =\n | { outcome: 'sent'; channel: CommerceModel.BuyerNotificationChannel }\n | { outcome: 'no_such_order' }\n | { outcome: 'not_configured' }\n | { outcome: 'invalid_recipient' }\n | { outcome: 'failed'; error: string }\n\n/**\n * Sends the receipt again, on the merchant's say-so, to the address or number\n * they gave (AGL-3610) — the order dialog's \"Resend receipt\".\n *\n * NOT idempotent, on purpose: a resend IS the second send. The route in front\n * of it is rate-limited instead, and the send is recorded on the timeline so\n * the merchant can see it went. It does not consult the store's moment\n * switches either — a merchant who turned automatic receipts off can still\n * hand one to a customer who asks.\n */\nexport async function sendOrderReceipt(\n ref: OrderRef,\n input: {\n channel: CommerceModel.BuyerNotificationChannel\n to: string\n firestore?: FirebaseFirestore.Firestore\n },\n): Promise<SendOrderReceiptOutcome> {\n try {\n const firestore = input.firestore ?? firebaseAdmin.app().firestore()\n const { orderRef, order } = await readOrder(firestore, ref)\n if (!order) return { outcome: 'no_such_order' }\n if (input.channel === 'email') {\n if (!isEmailConfigured()) return { outcome: 'not_configured' }\n if (!emailOf(input.to)) return { outcome: 'invalid_recipient' }\n } else {\n if (!pluginSmsMessaging()?.isConfigured()) {\n return { outcome: 'not_configured' }\n }\n if (!Aglyn.normalizePhone(input.to)) {\n return { outcome: 'invalid_recipient' }\n }\n }\n const store = await readStore(firestore, ref.hostId)\n const message = composeOrderBuyerMessage({\n event: 'receipt',\n order,\n orderId: ref.orderId,\n businessName: String(\n resolveHostToken('businessName', store.host as HostTokenSource) ?? '',\n ),\n currency: store.currency,\n statusUrl: statusLink(store, ref),\n receiptFooter: String(store.settings['receiptFooter'] ?? ''),\n downloadLinks: downloadLinksFor(store, ref, order),\n })\n if (!message) return { outcome: 'failed', error: 'Nothing to send' }\n if (input.channel === 'email') {\n await deliverEmail(store, ref, emailOf(input.to), message, 'order-receipt')\n } else {\n const sent = await deliverSms(ref, input.to, message, 'order-receipt', null)\n if (sent.refused) return { outcome: 'failed', error: sent.refused }\n }\n await settleMessage(firestore, orderRef, null, input.channel, 'Receipt re-sent')\n return { outcome: 'sent', channel: input.channel }\n } catch (error) {\n console.error('[order-notifications] receipt send failed', ref, error)\n return {\n outcome: 'failed',\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n/**\n * What the online receipt sends needs from this module (AGL-3610): whether\n * the store left receipts on, and the `order.number` / `order.statusUrl`\n * tokens. The cart and buy-now receipts compose their own summary (license\n * keys and download links built from the Stripe session) inside the\n * webhook's redelivery guard, so they ask here rather than route through\n * {@link notifyOrderBuyer}.\n */\nexport async function onlineReceiptExtras(\n hostId: string,\n orderId: string,\n firestore: FirebaseFirestore.Firestore = firebaseAdmin.app().firestore(),\n): Promise<{ enabled: boolean; tokens: Record<string, string> }> {\n try {\n const store = await readStore(firestore, hostId)\n const { order } = await readOrder(firestore, { hostId, orderId })\n return {\n enabled: CommerceModel.buyerNotificationEnabled(\n store.settings['buyerNotifications'],\n 'receipt',\n ),\n tokens: {\n 'order.number': CommerceModel.formatOrderNumber(order ?? {}, orderId),\n 'order.statusUrl': statusLink(store, { hostId, orderId }),\n },\n }\n } catch (error) {\n console.error('[order-notifications] receipt extras failed', error)\n return { enabled: true, tokens: { 'order.number': '', 'order.statusUrl': '' } }\n }\n}\n"],"names":["Aglyn","pluginSmsMessaging","resolveSiteTimeZone","resolveHostToken","firebaseAdmin","getOrgForHost","hostSendingIdentity","meterHostEmail","renderHostEmailWithTokens","isEmailConfigured","renderTextEmailHtml","sendEmail","resolvePluginOrderEmailCopies","withOrderEmailDataBlocks","CommerceModel","orderViewFromData","mintDownloadToken","orderStatusUrl","readStore","firestore","hostId","settings","settingsSnapshot","hostSnapshot","hostRef","collection","doc","Promise","all","get","catch","data","host","currency","String","lineLabel","line","quantity","name","variantLabel","statusLink","store","ref","orderId","hostPublicOrigin","cname","subdomain","composeOrderBuyerMessage","input","order","event","businessName","statusUrl","options","lines","lineItems","number","formatOrderNumber","money","cents","formatOrderMoney","base","link","smsLink","totalCents","totals","Number","amountCents","licenseText","Object","entries","licenseKeys","flatMap","productId","keys","find","item","map","key","join","downloads","downloadLinks","url","linesText","unitAmountCents","summary","filter","Boolean","footer","receiptFooter","occurrence","emailKey","BUYER_NOTIFICATION_EMAIL_KEYS","receipt","tokens","subject","text","sms","timelineDetail","fulfillment","fulfillments","fulfillmentId","entry","id","sort","a","b","atMs","handover","shipped","fulfillmentLineQuantities","lineItemId","tracking","fulfillmentTrackingUrl","hasTracking","trackingNumber","physical","some","productType","channel","remainingUnits","remainingFulfillmentLines","reduce","sum","carrier","carrierLabelFor","trackingSentence","remainingSentence","delivered","pickup","fulfillmentMethod","where","locationName","address","hours","instructions","picked_up","ready_for_pickup","delivery","localDelivery","window","windowLabel","outForDeliveryAtMs","out_for_delivery","refundCents","Math","max","round","refundedLines","refundLineIndexes","index","note","fullyRefunded","refundId","refundedCents","refunded","wasPaid","paymentIntentId","status","timeline","cancelled","claimMessage","orderRef","marker","runTransaction","transaction","snapshot","exists","sent","update","state","Date","now","settleMessage","detail","liftLegacyOrder","appendOrderEvent","error","console","releaseMessage","FieldValue","delete","undefined","EMAIL_COPIES_TIMEOUT_MS","deliverEmail","to","message","context","about","designed","org","copies","recordId","moment","recipient","timeoutMs","bcc","html","settle","result","length","fromName","resolveBrandingProfile","sendingIdentity","audience","owedFor","deliverSms","quiet","isConfigured","refused","timeZone","owner","outcome","send","body","purpose","quietHours","scheduledForMs","downloadLinksFor","origin","digital","token","fromEntries","encodeURIComponent","readOrder","emailOf","value","email","trim","test","notifyOrderBuyer","app","toggles","buyerNotificationEnabled","customerEmail","phone","customerPhone","targets","push","channels","target","buyerNotificationMarker","claimed","held","Error","sendOrderReceipt","normalizePhone","onlineReceiptExtras","enabled"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/order-notifications.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn/server'\nimport { withPosHardwareReceiptNotice } from '../model/pos-hardware-terms'\nimport { pluginSmsMessaging } from '@aglyn/aglyn/plugin-manager/plugin-sms-messaging'\nimport { resolveSiteTimeZone } from '@aglyn/aglyn/app-utils/collection-entry-date'\nimport {\n resolveHostToken,\n type HostTokenSource,\n} from '@aglyn/aglyn/app-utils/host-tokens'\nimport {\n firebaseAdmin,\n getOrgForHost,\n hostSendingIdentity,\n meterHostEmail,\n renderHostEmailWithTokens,\n} from '@aglyn/tenant-data-admin'\nimport {\n isEmailConfigured,\n renderTextEmailHtml,\n sendEmail,\n} from '@aglyn/shared-util-email'\nimport {\n resolvePluginOrderEmailCopies,\n withOrderEmailDataBlocks,\n} from '@aglyn/aglyn/plugin-manager/plugin-order-email-copies'\nimport * as CommerceModel from '../model'\nimport { orderViewFromData } from './api-v1/order-view'\nimport { mintDownloadToken } from './download'\nimport { orderStatusUrl } from './order-status-token'\n\n/**\n * Every message a buyer gets about their order, through ONE door (AGL-3610).\n *\n * Before this, the buyer heard about their order once — the online receipt —\n * and never again: a register sale sent nothing, a paid payment link sent\n * nothing, and shipping, delivery, refunds and cancellations were silent\n * (`fulfill-order.ts` said so on purpose). Every path that moves an order now\n * makes one call here, and this decides what that moment owes the buyer:\n *\n * - whether the store turned the moment off (`buyerNotifications` on the\n * store settings; ON unless explicitly off — see the model),\n * - which channels can carry it (email when mail is configured and the\n * order has an address; a text when the platform's SMS provider is\n * configured and the order carries the buyer's phone),\n * - and whether it was already sent.\n *\n * ## ONCE PER MOMENT, PER OCCURRENCE, PER CHANNEL\n *\n * Stripe redelivers webhooks, admins click twice, a lost response is retried.\n * Each message is CLAIMED before it is sent: a transaction reads the order's\n * `buyerNotifications` map and writes a `sending` marker for\n * `event__occurrence__channel` only if none is there. Exactly one of two\n * racing callers wins the claim; the other returns `already`. The occurrence\n * is the shipment id for `shipped` and the refund id for `refunded`, so a\n * second parcel or a second partial refund is a new message, while a\n * redelivered one is not.\n *\n * A send that fails RELEASES its claim so the next attempt can try again. A\n * process that dies between claim and send leaves the marker in `sending` and\n * the message unsent — at most once, never twice, which is the right side to\n * fail on for mail a buyer reads as \"did they charge me again?\".\n *\n * ## NEVER THROWS\n *\n * Every caller is past its point of no return — money moved, an order\n * shipped — so a mail outage must not turn into a 500 that invites the\n * merchant to do it again. Failures are logged and returned as outcomes.\n *\n * Shared with the order-processing and POS lanes (AGL-3611, AGL-3607): call\n * {@link notifyOrderBuyer} from any new path that moves an order, and\n * {@link sendOrderReceipt} for an explicit, merchant-asked send.\n */\n\nexport type OrderBuyerEvent = CommerceModel.BuyerNotificationEvent\n\nexport interface OrderRef {\n hostId: string\n orderId: string\n}\n\nexport interface NotifyOrderBuyerOptions {\n /** `shipped`: which shipment. Absent means the order's latest one. */\n fulfillmentId?: string\n /** `refunded`: the refund's own id (Stripe's), the occurrence key. */\n refundId?: string\n /** `refunded`: what THIS refund moved, in cents. */\n refundCents?: number\n /** `refunded`: the line indexes refunded by name, when any were. */\n refundLineIndexes?: readonly number[]\n /** `refunded`: whether this refund closed the order. */\n fullyRefunded?: boolean\n /**\n * The buyer's address when the order document does not carry it — a POS\n * folio sale attributed to the reservation's guest, a payment link whose\n * payer Stripe named. The order's own `customerEmail` wins when present.\n */\n email?: string | null\n /** Injected in specs. */\n firestore?: FirebaseFirestore.Firestore\n}\n\nexport type OrderBuyerChannelOutcome =\n | { channel: CommerceModel.BuyerNotificationChannel; outcome: 'sent' }\n | { channel: CommerceModel.BuyerNotificationChannel; outcome: 'already' }\n | {\n channel: CommerceModel.BuyerNotificationChannel\n outcome: 'failed'\n error: string\n }\n\nexport type NotifyOrderBuyerOutcome =\n | { outcome: 'disabled' }\n | { outcome: 'no_such_order' }\n /** The moment does not apply to this order (a counter sale \"shipping\"). */\n | { outcome: 'not_applicable' }\n /** No channel could carry it: no address, no mail or text configured. */\n | { outcome: 'no_recipient' }\n | { outcome: 'handled'; channels: OrderBuyerChannelOutcome[] }\n\ninterface StoreContext {\n firestore: FirebaseFirestore.Firestore\n hostRef: FirebaseFirestore.DocumentReference\n host: Record<string, unknown>\n settings: Record<string, unknown>\n currency: string\n}\n\nasync function readStore(\n firestore: FirebaseFirestore.Firestore,\n hostId: string,\n): Promise<StoreContext> {\n const hostRef = firestore.collection('hosts').doc(hostId)\n const [hostSnapshot, settingsSnapshot] = await Promise.all([\n hostRef.get().catch(() => null),\n hostRef.collection('settings').doc('store').get().catch(() => null),\n ])\n const settings = (settingsSnapshot?.data?.() ?? {}) as Record<string, unknown>\n return {\n firestore,\n hostRef,\n host: (hostSnapshot?.data?.() ?? {}) as Record<string, unknown>,\n settings,\n currency: String(settings['currency'] ?? 'USD') || 'USD',\n }\n}\n\nfunction lineLabel(line: CommerceModel.OrderLineItem): string {\n return `${line.quantity}× ${line.name}${\n line.variantLabel ? ` (${line.variantLabel})` : ''\n }`\n}\n\n/** The status link, or the store's home page when no link can be minted. */\nfunction statusLink(store: StoreContext, ref: OrderRef): string {\n return (\n orderStatusUrl(store.host as never, ref.hostId, ref.orderId) ??\n Aglyn.hostPublicOrigin({\n cname: typeof store.host['cname'] === 'string' ? store.host['cname'] : null,\n subdomain:\n typeof store.host['subdomain'] === 'string'\n ? store.host['subdomain']\n : null,\n }) ??\n ''\n )\n}\n\ninterface ComposedMessage {\n emailKey: string\n /** Tokens for the designed email. */\n tokens: Record<string, string>\n /** Subject when the template renders none. */\n subject: string\n /** Plain-text body when the template renders none. */\n text: string\n /** The text message, kept short: one or two segments. */\n sms: string\n /** The order timeline line recording the send, channel appended. */\n timelineDetail: string\n}\n\n/** What the moment says. `null` when it does not apply to this order. */\nexport function composeOrderBuyerMessage(input: {\n event: OrderBuyerEvent\n order: CommerceModel.HostOrder\n orderId: string\n businessName: string\n currency: string\n statusUrl: string\n receiptFooter?: string\n /** Absolute digital-delivery links for a receipt, by product id. */\n downloadLinks?: Record<string, string>\n options?: NotifyOrderBuyerOptions\n}): (ComposedMessage & { occurrence: string }) | null {\n const {\n event,\n order,\n orderId,\n businessName,\n currency,\n statusUrl,\n options = {},\n } = input\n const lines = order.lineItems ?? []\n const number = CommerceModel.formatOrderNumber(order, orderId)\n const money = (cents: number) =>\n CommerceModel.formatOrderMoney(cents, currency)\n const store = businessName || 'The store'\n const base = {\n 'order.number': number,\n 'order.statusUrl': statusUrl,\n 'order.ref': orderId,\n }\n const link = statusUrl ? `\\n\\nView your order: ${statusUrl}` : ''\n const smsLink = statusUrl ? ` ${statusUrl}` : ''\n\n if (event === 'receipt') {\n const totalCents =\n order.totals?.totalCents ?? Number(order.amountCents ?? 0)\n const licenseText = Object.entries(\n ((order as { licenseKeys?: Record<string, string[]> }).licenseKeys ??\n {}) as Record<string, string[]>,\n )\n .flatMap(([productId, keys]) => {\n const line = lines.find((item) => item.productId === productId)\n return (keys ?? []).map(\n (key) => `License key (${line?.name ?? 'product'}): ${key}`,\n )\n })\n .join('\\n')\n const downloads = Object.entries(input.downloadLinks ?? {})\n .map(([productId, url]) => {\n const line = lines.find((item) => item.productId === productId)\n return `Download ${line?.name ?? 'your file'}: ${url}`\n })\n .join('\\n')\n const linesText = lines\n .map(\n (line) =>\n `${lineLabel(line)} — ${money(line.unitAmountCents * line.quantity)}`,\n )\n .join('\\n')\n const summary = [linesText, licenseText, downloads]\n .filter(Boolean)\n .join('\\n\\n')\n const footer = String(input.receiptFooter ?? '')\n return {\n occurrence: 'order',\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.receipt,\n tokens: {\n ...base,\n 'order.summary': summary,\n 'order.total': money(totalCents),\n 'store.receiptFooter': footer,\n },\n subject: 'Receipt for your order',\n text:\n `Thanks for your purchase!\\n\\n${summary}\\n\\nTotal: ${money(totalCents)}\\n` +\n `Order ${number}` +\n (footer ? `\\n\\n${footer}` : '') +\n link,\n sms: `${store}: receipt for order ${number}, ${money(totalCents)}.${smsLink}`,\n timelineDetail: 'Receipt sent',\n }\n }\n\n if (event === 'shipped') {\n const fulfillments = order.fulfillments ?? []\n const fulfillment = options.fulfillmentId\n ? fulfillments.find((entry) => entry.id === options.fulfillmentId)\n : [...fulfillments].sort((a, b) => (b.atMs ?? 0) - (a.atMs ?? 0))[0]\n if (!fulfillment) return null\n // Collected at the counter or handed over by the store's driver\n // (AGL-3624): \"picked up\" and \"delivered\" say so, never \"shipped\".\n if (fulfillment.handover) return null\n // The units THIS shipment carried (AGL-3611): a partial shipment of a\n // line names its own count, not the line's.\n const shipped = CommerceModel.fulfillmentLineQuantities(order, fulfillment)\n .map((entry) =>\n lines[entry.lineItemId] ? { ...lines[entry.lineItemId], quantity: entry.quantity } : null,\n )\n .filter(Boolean) as CommerceModel.OrderLineItem[]\n const tracking = CommerceModel.fulfillmentTrackingUrl(fulfillment)\n const hasTracking = Boolean(fulfillment.trackingNumber)\n // Nothing physical left the building: a register sale handed over the\n // counter, or a digital-only order the merchant marked fulfilled. A\n // \"your order is on its way\" email for either is noise at best.\n const physical = shipped.some(\n (line) =>\n line.productType !== 'digital' && line.productType !== 'service',\n )\n if (!hasTracking && (order.channel === 'pos' || !physical)) return null\n // Shippable units still out, over every active shipment (AGL-3611).\n const remainingUnits = CommerceModel.remainingFulfillmentLines(order).reduce(\n (sum, entry) => sum + entry.quantity,\n 0,\n )\n const carrier = CommerceModel.carrierLabelFor(fulfillment.carrier)\n const trackingSentence = hasTracking\n ? `Tracking: ${[carrier, fulfillment.trackingNumber]\n .filter(Boolean)\n .join(' ')}${tracking ? ` — ${tracking}` : ''}`\n : ''\n const remainingSentence =\n remainingUnits > 0\n ? `${remainingUnits} more item${remainingUnits === 1 ? '' : 's'} will ship separately.`\n : ''\n const summary = shipped.map(lineLabel).join('\\n')\n return {\n occurrence: fulfillment.id,\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.shipped,\n tokens: {\n ...base,\n 'shipment.summary': summary,\n 'shipment.carrier': carrier,\n 'shipment.trackingNumber': String(fulfillment.trackingNumber ?? ''),\n 'shipment.tracking': trackingSentence,\n 'shipment.remaining': remainingSentence,\n },\n subject: `Your order ${number} has shipped`,\n text:\n `${store} shipped items from order ${number}.\\n\\n${summary}` +\n (trackingSentence ? `\\n\\n${trackingSentence}` : '') +\n (remainingSentence ? `\\n\\n${remainingSentence}` : '') +\n link,\n sms:\n `${store}: order ${number} has shipped.` +\n (tracking ? ` Track it: ${tracking}` : smsLink),\n timelineDetail: 'Shipping confirmation sent',\n }\n }\n\n if (event === 'delivered') {\n // A counter sale is \"delivered\" the moment it is handed over.\n if (order.channel === 'pos') return null\n const summary = lines.map(lineLabel).join('\\n')\n return {\n occurrence: 'order',\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.delivered,\n tokens: { ...base, 'order.summary': summary },\n subject: `Your order ${number} was delivered`,\n text:\n `${store} marked order ${number} as delivered.\\n\\n${summary}` + link,\n sms: `${store}: order ${number} was delivered.${smsLink}`,\n timelineDetail: 'Delivery confirmation sent',\n }\n }\n\n if (event === 'ready_for_pickup' || event === 'picked_up') {\n const pickup = order.pickup\n if (order.fulfillmentMethod !== 'pickup' || !pickup) return null\n const summary = lines.map(lineLabel).join('\\n')\n const where = `${pickup.locationName}${pickup.address ? `, ${pickup.address}` : ''}`\n const hours = pickup.hours ? `Pickup hours:\\n${pickup.hours}` : ''\n const instructions = pickup.instructions ?? ''\n if (event === 'picked_up') {\n return {\n occurrence: 'order',\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.picked_up,\n tokens: { ...base, 'order.summary': summary, 'pickup.location': where },\n subject: `You picked up order ${number}`,\n text: `Order ${number} was picked up from ${where}. Thanks for shopping with ${store}.\\n\\n${summary}` + link,\n sms: `${store}: order ${number} was picked up. Thank you!${smsLink}`,\n timelineDetail: 'Pickup confirmation sent',\n }\n }\n return {\n occurrence: 'order',\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.ready_for_pickup,\n tokens: {\n ...base,\n 'order.summary': summary,\n 'pickup.location': where,\n 'pickup.hours': hours,\n 'pickup.instructions': instructions,\n },\n subject: `Your order ${number} is ready for pickup`,\n text:\n `Your order ${number} is ready for pickup at ${where}.\\n\\n${summary}` +\n (hours ? `\\n\\n${hours}` : '') +\n (instructions ? `\\n\\n${instructions}` : '') +\n `\\n\\nBring your order number, ${number}.` +\n link,\n sms: `${store}: order ${number} is ready for pickup at ${pickup.locationName}.${smsLink}`,\n timelineDetail: 'Ready-for-pickup notice sent',\n }\n }\n\n if (event === 'out_for_delivery') {\n const delivery = order.localDelivery\n if (order.fulfillmentMethod !== 'local_delivery' || !delivery) return null\n const summary = lines.map(lineLabel).join('\\n')\n const window = delivery.windowLabel ?? ''\n return {\n // Each run out the door is its own message: a failed drop sent out\n // again tells the buyer again.\n occurrence: String(delivery.outForDeliveryAtMs ?? 'order'),\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.out_for_delivery,\n tokens: { ...base, 'order.summary': summary, 'delivery.window': window },\n subject: `Your order ${number} is out for delivery`,\n text:\n `${store} is on the way with order ${number}` +\n (window ? `, in your window ${window}` : '') +\n `.\\n\\n${summary}` +\n link,\n sms: `${store}: order ${number} is out for delivery${window ? ` (${window})` : ''}.${smsLink}`,\n timelineDetail: 'Out-for-delivery notice sent',\n }\n }\n\n if (event === 'refunded') {\n const refundCents = Math.max(0, Math.round(Number(options.refundCents ?? 0)))\n if (!(refundCents > 0)) return null\n const refundedLines = (options.refundLineIndexes ?? [])\n .map((index) => lines[index])\n .filter(Boolean) as CommerceModel.OrderLineItem[]\n const summary = refundedLines.map(lineLabel).join('\\n')\n const note = options.fullyRefunded\n ? 'Your order has been refunded in full.'\n : 'The rest of your order is unchanged.'\n return {\n // The refund's own id is the occurrence; without one, the running\n // total after this refund is (each settled refund raises it).\n occurrence:\n options.refundId ||\n `total-${Number(order.refundedCents ?? 0) || refundCents}`,\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.refunded,\n tokens: {\n ...base,\n 'refund.amount': money(refundCents),\n 'refund.summary': summary,\n 'refund.note': note,\n },\n subject: `Refund for your order ${number}`,\n text:\n `${store} refunded ${money(refundCents)} on order ${number}.` +\n (summary ? `\\n\\n${summary}` : '') +\n `\\n\\n${note}\\n\\nRefunds go back to the original payment method and ` +\n 'usually appear within 5 to 10 business days, depending on your bank.' +\n link,\n sms: `${store}: ${money(refundCents)} refunded on order ${number}.${smsLink}`,\n timelineDetail: `Refund confirmation sent (${money(refundCents)})`,\n }\n }\n\n // cancelled\n const summary = lines.map(lineLabel).join('\\n')\n const wasPaid = Boolean(\n order.paymentIntentId ||\n (order.channel === 'pos' && order.status !== 'pending') ||\n (order.timeline ?? []).some((entry) => entry?.event === 'paid'),\n )\n const note = wasPaid\n ? `Questions about a payment? Reply to this email or contact ${store}.`\n : ''\n return {\n occurrence: 'order',\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.cancelled,\n tokens: { ...base, 'order.summary': summary, 'cancel.note': note },\n subject: `Your order ${number} was canceled`,\n text:\n `${store} canceled order ${number}.\\n\\n${summary}` +\n (note ? `\\n\\n${note}` : '') +\n link,\n sms: `${store}: order ${number} was canceled.${smsLink}`,\n timelineDetail: 'Cancellation notice sent',\n }\n}\n\n/**\n * Claims one message in a transaction. `true` when this caller owns the send;\n * `false` when a marker is already there (sent, or being sent).\n */\nasync function claimMessage(\n firestore: FirebaseFirestore.Firestore,\n orderRef: FirebaseFirestore.DocumentReference,\n marker: string,\n channel: CommerceModel.BuyerNotificationChannel,\n): Promise<boolean> {\n return firestore.runTransaction(async (transaction) => {\n const snapshot = await transaction.get(orderRef)\n if (!snapshot.exists) return false\n const sent = (snapshot.get('buyerNotifications') ?? {}) as Record<\n string,\n unknown\n >\n if (sent[marker]) return false\n transaction.update(orderRef, {\n [`buyerNotifications.${marker}`]: {\n state: 'sending',\n channel,\n atMs: Date.now(),\n },\n })\n return true\n })\n}\n\n/** Marks a claimed message sent and records it on the order timeline. */\nasync function settleMessage(\n firestore: FirebaseFirestore.Firestore,\n orderRef: FirebaseFirestore.DocumentReference,\n marker: string | null,\n channel: CommerceModel.BuyerNotificationChannel,\n detail: string,\n): Promise<void> {\n await firestore\n .runTransaction(async (transaction) => {\n const snapshot = await transaction.get(orderRef)\n if (!snapshot.exists) return\n const order = CommerceModel.liftLegacyOrder(\n (snapshot.data() ?? {}) as never,\n )\n const atMs = Date.now()\n transaction.update(orderRef, {\n ...(marker\n ? {\n [`buyerNotifications.${marker}`]: { state: 'sent', channel, atMs },\n }\n : {}),\n timeline: CommerceModel.appendOrderEvent(\n order,\n 'buyer-notified',\n `${detail} by ${channel === 'sms' ? 'text' : 'email'}`,\n atMs,\n ),\n })\n })\n .catch((error) => {\n console.error('[order-notifications] settle failed', marker, error)\n })\n}\n\nasync function releaseMessage(\n orderRef: FirebaseFirestore.DocumentReference,\n marker: string,\n): Promise<void> {\n await orderRef\n .update({\n [`buyerNotifications.${marker}`]:\n firebaseAdmin.firestore.FieldValue.delete(),\n })\n .catch(() => undefined)\n}\n\n/** How long a buyer email waits on plugins asking to be copied on it. */\nconst EMAIL_COPIES_TIMEOUT_MS = 3_000\n\nasync function deliverEmail(\n store: StoreContext,\n ref: OrderRef,\n to: string,\n message: ComposedMessage,\n context: string,\n /** The moment and order, for plugins copied on it; absent for a merchant-asked resend. */\n about?: { event: OrderBuyerEvent; order: CommerceModel.HostOrder },\n): Promise<void> {\n const designed = await renderHostEmailWithTokens(\n store.firestore,\n ref.hostId,\n message.emailKey,\n message.tokens,\n )\n const org = await getOrgForHost(ref.hostId).catch(() => null)\n // Plugins copied on this email (AGL-3699): a review platform's invitation\n // address, with the data block it reads. Asked through core's seam, by\n // the email's key and moment; nothing answers unless a site connected one.\n const copies = about\n ? await resolvePluginOrderEmailCopies(\n {\n hostId: ref.hostId,\n recordId: ref.orderId,\n emailKey: message.emailKey,\n moment: about.event,\n recipient: to,\n order: orderViewFromData(ref.orderId, about.order as Record<string, any>),\n },\n { timeoutMs: EMAIL_COPIES_TIMEOUT_MS },\n )\n : { bcc: [] as string[], html: '', settle: async () => undefined }\n const subject = designed?.subject ?? message.subject\n const text = designed?.text || message.text\n // A data block lives in the HTML part, so a text-only email gets the same\n // HTML part `sendEmail` would have synthesized, carrying it.\n const html = copies.html\n ? withOrderEmailDataBlocks(designed?.html || renderTextEmailHtml(text, subject), copies.html)\n : designed?.html\n const result = await sendEmail({\n to,\n subject,\n text,\n ...(html ? { html } : {}),\n ...(copies.bcc.length ? { bcc: copies.bcc } : {}),\n fromName: Aglyn.resolveBrandingProfile(org?.org as never).fromName,\n sendingIdentity: await hostSendingIdentity(ref.hostId),\n audience: 'tenant',\n context,\n // Owed to the recipient by their own order: the phishing screen's soft\n // rules never hold it (AGL-3356).\n owedFor: 'order',\n })\n await copies.settle(Boolean(result?.sent))\n // Cost meter (AGL-1438), as every transactional store email.\n await meterHostEmail(ref.hostId)\n}\n\n/**\n * What a text attempt came to: `refused` names why nothing went (null when it\n * went), and `scheduledForMs` says it was held for the buyer's morning.\n */\ninterface SmsDelivery {\n refused: string | null\n scheduledForMs?: number\n}\n\n/**\n * Sends one order text. A receipt goes at once — the buyer is at the counter\n * or the checkout waiting for it. Every later moment (shipped, delivered,\n * refunded, canceled) can fire at any hour, from a carrier scan or a late\n * packing shift, so it asks the provider to keep quiet hours in the STORE's\n * zone: the best stand-in the platform has for a buyer whose own zone it was\n * never told, and right for the local and regional stores most texts come\n * from. Held texts arrive at 8 AM there.\n */\nasync function deliverSms(\n ref: OrderRef,\n to: string,\n message: ComposedMessage,\n context: string,\n quiet: { host: Record<string, unknown> } | null,\n): Promise<SmsDelivery> {\n const sms = pluginSmsMessaging()\n if (!sms?.isConfigured()) return { refused: 'Text messages are not configured' }\n let timeZone: string | null = null\n if (quiet) {\n const owner = await getOrgForHost(ref.hostId).catch(() => null)\n timeZone = resolveSiteTimeZone(\n (owner?.org ?? null) as { timeZone?: string } | null,\n quiet.host as { timeZone?: string },\n )\n }\n const outcome = await sms.send({\n to,\n body: message.sms,\n hostId: ref.hostId,\n purpose: 'transactional',\n context,\n ...(timeZone ? { quietHours: { timeZone } } : {}),\n })\n if (outcome.status !== 'sent') return { refused: outcome.status }\n return {\n refused: null,\n ...(outcome.scheduledForMs ? { scheduledForMs: outcome.scheduledForMs } : {}),\n }\n}\n\n/** Absolute download links for a receipt's digital lines. */\nfunction downloadLinksFor(\n store: StoreContext,\n ref: OrderRef,\n order: CommerceModel.HostOrder,\n): Record<string, string> {\n const origin = Aglyn.hostPublicOrigin({\n cname: typeof store.host['cname'] === 'string' ? store.host['cname'] : null,\n subdomain:\n typeof store.host['subdomain'] === 'string' ? store.host['subdomain'] : null,\n })\n const digital = (order.lineItems ?? []).filter(\n (line) => line.productType === 'digital',\n )\n if (!origin || !digital.length) return {}\n let token: string\n try {\n token = mintDownloadToken(ref.hostId, ref.orderId)\n } catch {\n return {}\n }\n return Object.fromEntries(\n digital.map((line) => [\n line.productId,\n `${origin}/api/commerce/download?hostId=${encodeURIComponent(ref.hostId)}` +\n `&orderId=${encodeURIComponent(ref.orderId)}` +\n `&productId=${encodeURIComponent(line.productId)}&token=${token}`,\n ]),\n )\n}\n\nasync function readOrder(\n firestore: FirebaseFirestore.Firestore,\n ref: OrderRef,\n): Promise<{\n orderRef: FirebaseFirestore.DocumentReference\n order: CommerceModel.HostOrder | null\n}> {\n const orderRef = firestore\n .collection('hosts')\n .doc(ref.hostId)\n .collection('orders')\n .doc(ref.orderId)\n const snapshot = await orderRef.get()\n return {\n orderRef,\n order: snapshot.exists\n ? CommerceModel.liftLegacyOrder((snapshot.data() ?? {}) as never)\n : null,\n }\n}\n\nfunction emailOf(value: unknown): string {\n const email = String(value ?? '').trim()\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email) ? email : ''\n}\n\n/**\n * Tells the buyer about one moment in their order's life, once. See the\n * module header. Never throws.\n */\nexport async function notifyOrderBuyer(\n ref: OrderRef,\n event: OrderBuyerEvent,\n options: NotifyOrderBuyerOptions = {},\n): Promise<NotifyOrderBuyerOutcome> {\n try {\n if (!ref.hostId || !ref.orderId) return { outcome: 'no_such_order' }\n const firestore = options.firestore ?? firebaseAdmin.app().firestore()\n const store = await readStore(firestore, ref.hostId)\n const toggles = store.settings['buyerNotifications']\n if (!CommerceModel.buyerNotificationEnabled(toggles, event)) {\n return { outcome: 'disabled' }\n }\n const { orderRef, order } = await readOrder(firestore, ref)\n if (!order) return { outcome: 'no_such_order' }\n const message = composeOrderBuyerMessage({\n event,\n order,\n orderId: ref.orderId,\n businessName: String(\n resolveHostToken('businessName', store.host as HostTokenSource) ?? '',\n ),\n currency: store.currency,\n statusUrl: statusLink(store, ref),\n // A sale of card reader hardware states the terms it is sold under (AGL-4038).\n receiptFooter: withPosHardwareReceiptNotice(String(store.settings['receiptFooter'] ?? ''), order),\n downloadLinks:\n event === 'receipt' ? downloadLinksFor(store, ref, order) : undefined,\n options,\n })\n if (!message) return { outcome: 'not_applicable' }\n\n const email = emailOf(order.customerEmail) || emailOf(options.email)\n const phone = String(order.customerPhone ?? '').trim()\n const targets: {\n channel: CommerceModel.BuyerNotificationChannel\n to: string\n }[] = []\n if (email && isEmailConfigured()) targets.push({ channel: 'email', to: email })\n if (\n phone &&\n CommerceModel.buyerNotificationEnabled(toggles, 'texts') &&\n pluginSmsMessaging()?.isConfigured()\n ) {\n targets.push({ channel: 'sms', to: phone })\n }\n if (!targets.length) return { outcome: 'no_recipient' }\n\n const channels: OrderBuyerChannelOutcome[] = []\n for (const target of targets) {\n const marker = CommerceModel.buyerNotificationMarker(\n event,\n message.occurrence,\n target.channel,\n )\n const claimed = await claimMessage(\n firestore,\n orderRef,\n marker,\n target.channel,\n ).catch((error) => {\n console.error('[order-notifications] claim failed', marker, error)\n return false\n })\n if (!claimed) {\n channels.push({ channel: target.channel, outcome: 'already' })\n continue\n }\n const context = message.emailKey\n let held: number | null = null\n try {\n if (target.channel === 'email') {\n await deliverEmail(store, ref, target.to, message, context, { event, order })\n } else {\n const sent = await deliverSms(\n ref,\n target.to,\n message,\n context,\n event === 'receipt' ? null : { host: store.host },\n )\n if (sent.refused) throw new Error(sent.refused)\n held = sent.scheduledForMs ?? null\n }\n await settleMessage(\n firestore,\n orderRef,\n marker,\n target.channel,\n held\n ? `${message.timelineDetail} (held for the morning)`\n : message.timelineDetail,\n )\n channels.push({ channel: target.channel, outcome: 'sent' })\n } catch (error) {\n await releaseMessage(orderRef, marker)\n console.error('[order-notifications] send failed', {\n hostId: ref.hostId,\n orderId: ref.orderId,\n event,\n channel: target.channel,\n error,\n })\n channels.push({\n channel: target.channel,\n outcome: 'failed',\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n return { outcome: 'handled', channels }\n } catch (error) {\n console.error('[order-notifications] notify failed', ref, event, error)\n return { outcome: 'handled', channels: [] }\n }\n}\n\nexport type SendOrderReceiptOutcome =\n | { outcome: 'sent'; channel: CommerceModel.BuyerNotificationChannel }\n | { outcome: 'no_such_order' }\n | { outcome: 'not_configured' }\n | { outcome: 'invalid_recipient' }\n | { outcome: 'failed'; error: string }\n\n/**\n * Sends the receipt again, on the merchant's say-so, to the address or number\n * they gave (AGL-3610) — the order dialog's \"Resend receipt\".\n *\n * NOT idempotent, on purpose: a resend IS the second send. The route in front\n * of it is rate-limited instead, and the send is recorded on the timeline so\n * the merchant can see it went. It does not consult the store's moment\n * switches either — a merchant who turned automatic receipts off can still\n * hand one to a customer who asks.\n */\nexport async function sendOrderReceipt(\n ref: OrderRef,\n input: {\n channel: CommerceModel.BuyerNotificationChannel\n to: string\n firestore?: FirebaseFirestore.Firestore\n },\n): Promise<SendOrderReceiptOutcome> {\n try {\n const firestore = input.firestore ?? firebaseAdmin.app().firestore()\n const { orderRef, order } = await readOrder(firestore, ref)\n if (!order) return { outcome: 'no_such_order' }\n if (input.channel === 'email') {\n if (!isEmailConfigured()) return { outcome: 'not_configured' }\n if (!emailOf(input.to)) return { outcome: 'invalid_recipient' }\n } else {\n if (!pluginSmsMessaging()?.isConfigured()) {\n return { outcome: 'not_configured' }\n }\n if (!Aglyn.normalizePhone(input.to)) {\n return { outcome: 'invalid_recipient' }\n }\n }\n const store = await readStore(firestore, ref.hostId)\n const message = composeOrderBuyerMessage({\n event: 'receipt',\n order,\n orderId: ref.orderId,\n businessName: String(\n resolveHostToken('businessName', store.host as HostTokenSource) ?? '',\n ),\n currency: store.currency,\n statusUrl: statusLink(store, ref),\n // A sale of card reader hardware states the terms it is sold under (AGL-4038).\n receiptFooter: withPosHardwareReceiptNotice(String(store.settings['receiptFooter'] ?? ''), order),\n downloadLinks: downloadLinksFor(store, ref, order),\n })\n if (!message) return { outcome: 'failed', error: 'Nothing to send' }\n if (input.channel === 'email') {\n await deliverEmail(store, ref, emailOf(input.to), message, 'order-receipt')\n } else {\n const sent = await deliverSms(ref, input.to, message, 'order-receipt', null)\n if (sent.refused) return { outcome: 'failed', error: sent.refused }\n }\n await settleMessage(firestore, orderRef, null, input.channel, 'Receipt re-sent')\n return { outcome: 'sent', channel: input.channel }\n } catch (error) {\n console.error('[order-notifications] receipt send failed', ref, error)\n return {\n outcome: 'failed',\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n/**\n * What the online receipt sends needs from this module (AGL-3610): whether\n * the store left receipts on, and the `order.number` / `order.statusUrl`\n * tokens. The cart and buy-now receipts compose their own summary (license\n * keys and download links built from the Stripe session) inside the\n * webhook's redelivery guard, so they ask here rather than route through\n * {@link notifyOrderBuyer}.\n */\nexport async function onlineReceiptExtras(\n hostId: string,\n orderId: string,\n firestore: FirebaseFirestore.Firestore = firebaseAdmin.app().firestore(),\n): Promise<{ enabled: boolean; tokens: Record<string, string> }> {\n try {\n const store = await readStore(firestore, hostId)\n const { order } = await readOrder(firestore, { hostId, orderId })\n return {\n enabled: CommerceModel.buyerNotificationEnabled(\n store.settings['buyerNotifications'],\n 'receipt',\n ),\n tokens: {\n 'order.number': CommerceModel.formatOrderNumber(order ?? {}, orderId),\n 'order.statusUrl': statusLink(store, { hostId, orderId }),\n },\n }\n } catch (error) {\n console.error('[order-notifications] receipt extras failed', error)\n return { enabled: true, tokens: { 'order.number': '', 'order.statusUrl': '' } }\n }\n}\n"],"names":["Aglyn","withPosHardwareReceiptNotice","pluginSmsMessaging","resolveSiteTimeZone","resolveHostToken","firebaseAdmin","getOrgForHost","hostSendingIdentity","meterHostEmail","renderHostEmailWithTokens","isEmailConfigured","renderTextEmailHtml","sendEmail","resolvePluginOrderEmailCopies","withOrderEmailDataBlocks","CommerceModel","orderViewFromData","mintDownloadToken","orderStatusUrl","readStore","firestore","hostId","settings","settingsSnapshot","hostSnapshot","hostRef","collection","doc","Promise","all","get","catch","data","host","currency","String","lineLabel","line","quantity","name","variantLabel","statusLink","store","ref","orderId","hostPublicOrigin","cname","subdomain","composeOrderBuyerMessage","input","order","event","businessName","statusUrl","options","lines","lineItems","number","formatOrderNumber","money","cents","formatOrderMoney","base","link","smsLink","totalCents","totals","Number","amountCents","licenseText","Object","entries","licenseKeys","flatMap","productId","keys","find","item","map","key","join","downloads","downloadLinks","url","linesText","unitAmountCents","summary","filter","Boolean","footer","receiptFooter","occurrence","emailKey","BUYER_NOTIFICATION_EMAIL_KEYS","receipt","tokens","subject","text","sms","timelineDetail","fulfillment","fulfillments","fulfillmentId","entry","id","sort","a","b","atMs","handover","shipped","fulfillmentLineQuantities","lineItemId","tracking","fulfillmentTrackingUrl","hasTracking","trackingNumber","physical","some","productType","channel","remainingUnits","remainingFulfillmentLines","reduce","sum","carrier","carrierLabelFor","trackingSentence","remainingSentence","delivered","pickup","fulfillmentMethod","where","locationName","address","hours","instructions","picked_up","ready_for_pickup","delivery","localDelivery","window","windowLabel","outForDeliveryAtMs","out_for_delivery","refundCents","Math","max","round","refundedLines","refundLineIndexes","index","note","fullyRefunded","refundId","refundedCents","refunded","wasPaid","paymentIntentId","status","timeline","cancelled","claimMessage","orderRef","marker","runTransaction","transaction","snapshot","exists","sent","update","state","Date","now","settleMessage","detail","liftLegacyOrder","appendOrderEvent","error","console","releaseMessage","FieldValue","delete","undefined","EMAIL_COPIES_TIMEOUT_MS","deliverEmail","to","message","context","about","designed","org","copies","recordId","moment","recipient","timeoutMs","bcc","html","settle","result","length","fromName","resolveBrandingProfile","sendingIdentity","audience","owedFor","deliverSms","quiet","isConfigured","refused","timeZone","owner","outcome","send","body","purpose","quietHours","scheduledForMs","downloadLinksFor","origin","digital","token","fromEntries","encodeURIComponent","readOrder","emailOf","value","email","trim","test","notifyOrderBuyer","app","toggles","buyerNotificationEnabled","customerEmail","phone","customerPhone","targets","push","channels","target","buyerNotificationMarker","claimed","held","Error","sendOrderReceipt","normalizePhone","onlineReceiptExtras","enabled"],"mappings":"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"}
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
|
-
*/ import {
|
|
16
|
+
*/ import { holdsCommerceSiteKey } from "./commerce-site-keys.js";
|
|
17
|
+
import { commerceSitePermissionRefusal } from "../model/plugin-permissions.js";
|
|
18
|
+
import { isRefusedIdToken } from "@aglyn/tenant-data-admin/server/id-token-refusal";
|
|
17
19
|
import { firebaseAdmin } from "@aglyn/tenant-data-admin";
|
|
18
20
|
import { formatOrderNumber, liftLegacyOrder } from "../model/index.js";
|
|
19
21
|
import { buildOrderInvoiceHtml, invoiceStoreFrom } from "../utils/order-invoice.js";
|
|
@@ -63,7 +65,7 @@ export function createOrderPrintHandler(options = {}) {
|
|
|
63
65
|
error: 'Unauthenticated'
|
|
64
66
|
});
|
|
65
67
|
try {
|
|
66
|
-
var
|
|
68
|
+
var _stored_data, _ref;
|
|
67
69
|
let decoded;
|
|
68
70
|
try {
|
|
69
71
|
decoded = await verifyIdToken(token);
|
|
@@ -80,10 +82,10 @@ export function createOrderPrintHandler(options = {}) {
|
|
|
80
82
|
if (!host.exists) return res.status(404).json({
|
|
81
83
|
error: 'Unknown site'
|
|
82
84
|
});
|
|
83
|
-
|
|
84
|
-
if (decoded['staff'] !== true &&
|
|
85
|
+
// "Manage orders" (`commerce.orders`, AGL-4047), read as the rules would read it.
|
|
86
|
+
if (decoded['staff'] !== true && !holdsCommerceSiteKey(host, decoded.uid, 'commerce.orders')) {
|
|
85
87
|
return res.status(403).json({
|
|
86
|
-
error: '
|
|
88
|
+
error: commerceSitePermissionRefusal('commerce.orders')
|
|
87
89
|
});
|
|
88
90
|
}
|
|
89
91
|
const [stored, store] = await Promise.all([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/order-print.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { isRefusedIdToken } from '@aglyn/tenant-data-admin/server/id-token-refusal'\nimport { type PluginApiHandler } from '@aglyn/aglyn/server'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { formatOrderNumber, liftLegacyOrder, type HostOrder } from '../model'\nimport { buildOrderInvoiceHtml, invoiceStoreFrom } from '../utils/order-invoice'\nimport { buildPackingSlipHtml } from '../utils/order-packing-slip'\nimport { validId } from './products-write'\n\n/*\n * `GET /api/commerce/order-print?hostId&orderId&kind` (AGL-3841): the order\n * dialog's \"Packing slip\" and \"Invoice\" for the native apps, which print them\n * through the device's own print sheet.\n *\n * The console builds both pages in the browser from the order it is showing\n * (`utils/order-packing-slip.ts`, `utils/order-invoice.ts`) and prints them\n * from a popup. This route runs the same two builders over the stored order,\n * the site and its store settings, and answers the markup with the title the\n * popup has, so an app prints the console's own page rather than a copy of\n * its layout.\n *\n * Gate: who may read the order (`canReadHostSensitive` in the rules: the\n * site's `admin` and `editor`), or staff. A read, so a frozen site still\n * prints.\n */\n\nexport type OrderPrintKind = 'invoice' | 'packing-slip'\n\nexport const ORDER_PRINT_KINDS: readonly OrderPrintKind[] = ['invoice', 'packing-slip']\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport interface OrderPrintDeps {\n firestore?: () => Firestore\n verifyIdToken?: (token: string) => Promise<Record<string, unknown> & { uid: string }>\n}\n\n/** The page and its title for one order: what the console's popup is given. */\nexport function orderPrintPage(\n kind: OrderPrintKind,\n input: {\n order: HostOrder\n orderId: string\n host?: Record<string, unknown> | null\n store?: Record<string, unknown> | null\n },\n): { title: string; html: string } {\n const number = formatOrderNumber(input.order, input.orderId)\n if (kind === 'packing-slip') {\n return { title: `Packing slip ${number}`, html: buildPackingSlipHtml(input.order, input.orderId) }\n }\n return {\n title: `Invoice ${number}`,\n html: buildOrderInvoiceHtml({\n order: input.order,\n orderId: input.orderId,\n store: invoiceStoreFrom(input.host, input.store),\n }),\n }\n}\n\nexport function createOrderPrintHandler(options: OrderPrintDeps = {}): PluginApiHandler {\n return async (req, res) => {\n if (req.method !== 'GET') return res.status(405).json({ error: 'Method not allowed' })\n const firestore = options.firestore ?? (() => firebaseAdmin.app().firestore() as unknown as Firestore)\n const verifyIdToken =\n options.verifyIdToken ?? ((token: string) => firebaseAdmin.app().auth().verifyIdToken(token) as never)\n const hostId = req.query['hostId']\n const orderId = req.query['orderId']\n const kind = String(req.query['kind'] ?? '') as OrderPrintKind\n if (!validId(hostId) || !validId(orderId)) return res.status(400).json({ error: 'Missing site or order' })\n if (!ORDER_PRINT_KINDS.includes(kind)) return res.status(400).json({ error: 'kind must be invoice or packing-slip' })\n const authorization = String(req.headers.authorization ?? '')\n const token = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : ''\n if (!token) return res.status(401).json({ error: 'Unauthenticated' })\n try {\n let decoded: Record<string, unknown> & { uid: string }\n try {\n decoded = await verifyIdToken(token)\n } catch (error) {\n // Only a refused token is the caller's; an Auth outage is a 500 (AGL-2852).\n if (!isRefusedIdToken(error)) throw error\n return res.status(401).json({ error: 'Sign in again to continue' })\n }\n const db = firestore()\n const hostRef = db.collection('hosts').doc(hostId)\n const host = await hostRef.get()\n if (!host.exists) return res.status(404).json({ error: 'Unknown site' })\n
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/order-print.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { holdsCommerceSiteKey } from './commerce-site-keys'\nimport { commerceSitePermissionRefusal } from '../model/plugin-permissions'\nimport { isRefusedIdToken } from '@aglyn/tenant-data-admin/server/id-token-refusal'\nimport { type PluginApiHandler } from '@aglyn/aglyn/server'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { formatOrderNumber, liftLegacyOrder, type HostOrder } from '../model'\nimport { buildOrderInvoiceHtml, invoiceStoreFrom } from '../utils/order-invoice'\nimport { buildPackingSlipHtml } from '../utils/order-packing-slip'\nimport { validId } from './products-write'\n\n/*\n * `GET /api/commerce/order-print?hostId&orderId&kind` (AGL-3841): the order\n * dialog's \"Packing slip\" and \"Invoice\" for the native apps, which print them\n * through the device's own print sheet.\n *\n * The console builds both pages in the browser from the order it is showing\n * (`utils/order-packing-slip.ts`, `utils/order-invoice.ts`) and prints them\n * from a popup. This route runs the same two builders over the stored order,\n * the site and its store settings, and answers the markup with the title the\n * popup has, so an app prints the console's own page rather than a copy of\n * its layout.\n *\n * Gate: who may read the order (`canReadHostSensitive` in the rules: the\n * site's `admin` and `editor`), or staff. A read, so a frozen site still\n * prints.\n */\n\nexport type OrderPrintKind = 'invoice' | 'packing-slip'\n\nexport const ORDER_PRINT_KINDS: readonly OrderPrintKind[] = ['invoice', 'packing-slip']\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport interface OrderPrintDeps {\n firestore?: () => Firestore\n verifyIdToken?: (token: string) => Promise<Record<string, unknown> & { uid: string }>\n}\n\n/** The page and its title for one order: what the console's popup is given. */\nexport function orderPrintPage(\n kind: OrderPrintKind,\n input: {\n order: HostOrder\n orderId: string\n host?: Record<string, unknown> | null\n store?: Record<string, unknown> | null\n },\n): { title: string; html: string } {\n const number = formatOrderNumber(input.order, input.orderId)\n if (kind === 'packing-slip') {\n return { title: `Packing slip ${number}`, html: buildPackingSlipHtml(input.order, input.orderId) }\n }\n return {\n title: `Invoice ${number}`,\n html: buildOrderInvoiceHtml({\n order: input.order,\n orderId: input.orderId,\n store: invoiceStoreFrom(input.host, input.store),\n }),\n }\n}\n\nexport function createOrderPrintHandler(options: OrderPrintDeps = {}): PluginApiHandler {\n return async (req, res) => {\n if (req.method !== 'GET') return res.status(405).json({ error: 'Method not allowed' })\n const firestore = options.firestore ?? (() => firebaseAdmin.app().firestore() as unknown as Firestore)\n const verifyIdToken =\n options.verifyIdToken ?? ((token: string) => firebaseAdmin.app().auth().verifyIdToken(token) as never)\n const hostId = req.query['hostId']\n const orderId = req.query['orderId']\n const kind = String(req.query['kind'] ?? '') as OrderPrintKind\n if (!validId(hostId) || !validId(orderId)) return res.status(400).json({ error: 'Missing site or order' })\n if (!ORDER_PRINT_KINDS.includes(kind)) return res.status(400).json({ error: 'kind must be invoice or packing-slip' })\n const authorization = String(req.headers.authorization ?? '')\n const token = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : ''\n if (!token) return res.status(401).json({ error: 'Unauthenticated' })\n try {\n let decoded: Record<string, unknown> & { uid: string }\n try {\n decoded = await verifyIdToken(token)\n } catch (error) {\n // Only a refused token is the caller's; an Auth outage is a 500 (AGL-2852).\n if (!isRefusedIdToken(error)) throw error\n return res.status(401).json({ error: 'Sign in again to continue' })\n }\n const db = firestore()\n const hostRef = db.collection('hosts').doc(hostId)\n const host = await hostRef.get()\n if (!host.exists) return res.status(404).json({ error: 'Unknown site' })\n // \"Manage orders\" (`commerce.orders`, AGL-4047), read as the rules would read it.\n if (decoded['staff'] !== true && !holdsCommerceSiteKey(host, decoded.uid, 'commerce.orders')) {\n return res.status(403).json({ error: commerceSitePermissionRefusal('commerce.orders') })\n }\n const [stored, store] = await Promise.all([\n hostRef.collection('orders').doc(orderId).get(),\n kind === 'invoice' ? hostRef.collection('settings').doc('store').get() : Promise.resolve(null),\n ])\n if (!stored.exists) return res.status(404).json({ error: 'This order no longer exists' })\n const order = liftLegacyOrder((stored.data() ?? {}) as never)\n return res.status(200).json(\n orderPrintPage(kind, {\n order,\n orderId,\n host: host.data() as Record<string, unknown>,\n store: (store?.data() ?? null) as Record<string, unknown> | null,\n }),\n )\n } catch (error) {\n console.error('[commerce/order-print]', error)\n return res.status(500).json({ error: 'The page could not be made' })\n }\n }\n}\n\nexport const orderPrintHandler = createOrderPrintHandler()\n"],"names":["holdsCommerceSiteKey","commerceSitePermissionRefusal","isRefusedIdToken","firebaseAdmin","formatOrderNumber","liftLegacyOrder","buildOrderInvoiceHtml","invoiceStoreFrom","buildPackingSlipHtml","validId","ORDER_PRINT_KINDS","orderPrintPage","kind","input","number","order","orderId","title","html","store","host","createOrderPrintHandler","options","req","res","method","status","json","error","firestore","app","verifyIdToken","token","auth","hostId","query","String","includes","authorization","headers","startsWith","slice","length","stored","decoded","db","hostRef","collection","doc","get","exists","uid","Promise","all","resolve","data","console","orderPrintHandler"],"mappings":"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"}
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
|
-
*/ import {
|
|
16
|
+
*/ import { holdsCommerceSiteKey } from "./commerce-site-keys.js";
|
|
17
|
+
import { commerceSitePermissionRefusal } from "../model/plugin-permissions.js";
|
|
18
|
+
import { pluginSmsAvailable } from "@aglyn/aglyn/plugin-manager/plugin-sms-messaging";
|
|
17
19
|
import { consumeRateLimit, firebaseAdmin } from "@aglyn/tenant-data-admin";
|
|
18
20
|
import { isEmailConfigured } from "@aglyn/shared-util-email";
|
|
19
21
|
import { sendOrderReceipt } from "./order-notifications.js";
|
|
@@ -55,7 +57,7 @@ import { sendOrderReceipt } from "./order-notifications.js";
|
|
|
55
57
|
});
|
|
56
58
|
}
|
|
57
59
|
try {
|
|
58
|
-
var
|
|
60
|
+
var _body_orderId, _body_channel, _body_to;
|
|
59
61
|
const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
|
|
60
62
|
const hostSnapshot = await firebaseAdmin.app().firestore().collection('hosts').doc(hostId).get();
|
|
61
63
|
if (!hostSnapshot.exists) {
|
|
@@ -63,10 +65,11 @@ import { sendOrderReceipt } from "./order-notifications.js";
|
|
|
63
65
|
error: 'Unknown site'
|
|
64
66
|
});
|
|
65
67
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
+
// "Manage orders" (`commerce.orders`, AGL-4047), whose preset is the site's Admin
|
|
69
|
+
// and Editor this asked for, read as the rules would read it.
|
|
70
|
+
if (!holdsCommerceSiteKey(hostSnapshot, decoded.uid, 'commerce.orders')) {
|
|
68
71
|
return res.status(403).json({
|
|
69
|
-
error: '
|
|
72
|
+
error: commerceSitePermissionRefusal('commerce.orders')
|
|
70
73
|
});
|
|
71
74
|
}
|
|
72
75
|
if (req.method === 'GET') {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/order-receipt-send.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { type PluginApiHandler } from '@aglyn/aglyn/server'\nimport { pluginSmsAvailable } from '@aglyn/aglyn/plugin-manager/plugin-sms-messaging'\nimport { consumeRateLimit, firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { isEmailConfigured } from '@aglyn/shared-util-email'\nimport { sendOrderReceipt } from './order-notifications'\n\n/** Receipts one member may re-send per minute, across every order. */\nexport const RECEIPT_RESENDS_PER_MINUTE_PER_MEMBER = 10\n/** Receipts one order may be re-sent per hour, by anybody. */\nexport const RECEIPT_RESENDS_PER_HOUR_PER_ORDER = 5\n\n/**\n * \"Resend receipt\" from the order dialog (AGL-3610): the console route\n * `commerce/order-receipt-send`.\n *\n * GET answers which channels can carry a receipt right now —\n * `{ email, sms }` — so the dialog never OFFERS a text the platform cannot\n * send (no SMS provider configured) or an email it cannot (no mail rail).\n *\n * POST `{ hostId, orderId, channel, to }` sends the order's receipt to the\n * address or number the merchant typed, through `sendOrderReceipt`.\n *\n * Role gate: `admin` and `editor`, the roles that already see and act on the\n * order in the dialog (fulfil, cancel). Two rate limits, both durable: a\n * member's resends per minute, and an order's resends per hour by anybody —\n * the second is what stops the dialog becoming a way to mail one stranger the\n * same receipt fifty times.\n */\nexport const orderReceiptSendHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'GET' && req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n const body =\n req.method === 'POST'\n ? typeof req.body === 'string'\n ? JSON.parse(req.body || '{}')\n : (req.body ?? {})\n : {}\n const hostId = String(\n (req.method === 'GET' ? req.query.hostId : body.hostId) ?? '',\n )\n if (!hostId || /^__.*__$/.test(hostId)) {\n return res.status(400).json({ error: 'Missing hostId' })\n }\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const hostSnapshot = await firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(hostId)\n .get()\n if (!hostSnapshot.exists) {\n return res.status(404).json({ error: 'Unknown site' })\n }\n const memberRole = (hostSnapshot.get('memberRoles') ?? {})[decoded.uid]\n if (memberRole !== 'admin' && memberRole !== 'editor') {\n return res.status(403).json({ error: 'Not permitted' })\n }\n if (req.method === 'GET') {\n return res\n .status(200)\n .json({ email: isEmailConfigured(), sms: pluginSmsAvailable() })\n }\n\n const orderId = String(body.orderId ?? '')\n const channel = String(body.channel ?? 'email')\n const to = String(body.to ?? '').trim().slice(0, 200)\n if (!orderId || /^__.*__$/.test(orderId)) {\n return res.status(400).json({ error: 'Missing orderId' })\n }\n if (channel !== 'email' && channel !== 'sms') {\n return res.status(400).json({ error: 'channel must be email or sms' })\n }\n if (!to) {\n return res.status(400).json({\n error: channel === 'sms' ? 'Enter a phone number' : 'Enter an email address',\n })\n }\n const [member, order] = await Promise.all([\n consumeRateLimit(`commerce-receipt-send:uid:${decoded.uid}`, {\n limit: RECEIPT_RESENDS_PER_MINUTE_PER_MEMBER,\n windowMs: 60_000,\n }),\n consumeRateLimit(`commerce-receipt-send:order:${hostId}:${orderId}`, {\n limit: RECEIPT_RESENDS_PER_HOUR_PER_ORDER,\n windowMs: 60 * 60_000,\n }),\n ])\n if (!member.allowed || !order.allowed) {\n const resetMs = Math.max(\n member.allowed ? 0 : member.resetMs,\n order.allowed ? 0 : order.resetMs,\n )\n res.setHeader(\n 'Retry-After',\n String(Math.max(1, Math.ceil((resetMs - Date.now()) / 1000))),\n )\n return res.status(429).json({\n error: order.allowed\n ? 'Too many receipts sent — wait a minute and try again.'\n : 'This receipt was re-sent several times in the last hour — try again later.',\n })\n }\n const outcome = await sendOrderReceipt(\n { hostId, orderId },\n { channel, to },\n )\n switch (outcome.outcome) {\n case 'sent':\n return res.status(200).json({ ok: true, channel })\n case 'no_such_order':\n return res.status(404).json({ error: 'Unknown order' })\n case 'not_configured':\n return res.status(409).json({\n error:\n channel === 'sms'\n ? 'Text messages are not set up on this platform.'\n : 'Email is not set up on this platform.',\n })\n case 'invalid_recipient':\n return res.status(400).json({\n error:\n channel === 'sms'\n ? 'That phone number could not be read.'\n : 'That email address could not be read.',\n })\n default:\n return res.status(502).json({\n error:\n outcome.error === 'suppressed'\n ? 'That number has opted out of texts.'\n : 'The receipt could not be sent. Try again.',\n })\n }\n } catch (error) {\n console.error('orderReceiptSend failed', error)\n return res.status(500).json({ error: 'The receipt could not be sent.' })\n }\n}\n"],"names":["pluginSmsAvailable","consumeRateLimit","firebaseAdmin","isEmailConfigured","sendOrderReceipt","RECEIPT_RESENDS_PER_MINUTE_PER_MEMBER","RECEIPT_RESENDS_PER_HOUR_PER_ORDER","orderReceiptSendHandler","req","res","method","status","json","error","authorization","String","headers","idToken","startsWith","slice","length","undefined","body","JSON","parse","hostId","query","test","hostSnapshot","decoded","app","auth","verifyIdToken","firestore","collection","doc","get","exists","memberRole","uid","email","sms","orderId","channel","to","trim","member","order","Promise","all","limit","windowMs","allowed","resetMs","Math","max","setHeader","ceil","Date","now","outcome","ok","console"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/order-receipt-send.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { holdsCommerceSiteKey } from './commerce-site-keys'\nimport { commerceSitePermissionRefusal } from '../model/plugin-permissions'\nimport { type PluginApiHandler } from '@aglyn/aglyn/server'\nimport { pluginSmsAvailable } from '@aglyn/aglyn/plugin-manager/plugin-sms-messaging'\nimport { consumeRateLimit, firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { isEmailConfigured } from '@aglyn/shared-util-email'\nimport { sendOrderReceipt } from './order-notifications'\n\n/** Receipts one member may re-send per minute, across every order. */\nexport const RECEIPT_RESENDS_PER_MINUTE_PER_MEMBER = 10\n/** Receipts one order may be re-sent per hour, by anybody. */\nexport const RECEIPT_RESENDS_PER_HOUR_PER_ORDER = 5\n\n/**\n * \"Resend receipt\" from the order dialog (AGL-3610): the console route\n * `commerce/order-receipt-send`.\n *\n * GET answers which channels can carry a receipt right now —\n * `{ email, sms }` — so the dialog never OFFERS a text the platform cannot\n * send (no SMS provider configured) or an email it cannot (no mail rail).\n *\n * POST `{ hostId, orderId, channel, to }` sends the order's receipt to the\n * address or number the merchant typed, through `sendOrderReceipt`.\n *\n * Role gate: `admin` and `editor`, the roles that already see and act on the\n * order in the dialog (fulfil, cancel). Two rate limits, both durable: a\n * member's resends per minute, and an order's resends per hour by anybody —\n * the second is what stops the dialog becoming a way to mail one stranger the\n * same receipt fifty times.\n */\nexport const orderReceiptSendHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'GET' && req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n const body =\n req.method === 'POST'\n ? typeof req.body === 'string'\n ? JSON.parse(req.body || '{}')\n : (req.body ?? {})\n : {}\n const hostId = String(\n (req.method === 'GET' ? req.query.hostId : body.hostId) ?? '',\n )\n if (!hostId || /^__.*__$/.test(hostId)) {\n return res.status(400).json({ error: 'Missing hostId' })\n }\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const hostSnapshot = await firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(hostId)\n .get()\n if (!hostSnapshot.exists) {\n return res.status(404).json({ error: 'Unknown site' })\n }\n // \"Manage orders\" (`commerce.orders`, AGL-4047), whose preset is the site's Admin\n // and Editor this asked for, read as the rules would read it.\n if (!holdsCommerceSiteKey(hostSnapshot, decoded.uid, 'commerce.orders')) {\n return res.status(403).json({ error: commerceSitePermissionRefusal('commerce.orders') })\n }\n if (req.method === 'GET') {\n return res\n .status(200)\n .json({ email: isEmailConfigured(), sms: pluginSmsAvailable() })\n }\n\n const orderId = String(body.orderId ?? '')\n const channel = String(body.channel ?? 'email')\n const to = String(body.to ?? '').trim().slice(0, 200)\n if (!orderId || /^__.*__$/.test(orderId)) {\n return res.status(400).json({ error: 'Missing orderId' })\n }\n if (channel !== 'email' && channel !== 'sms') {\n return res.status(400).json({ error: 'channel must be email or sms' })\n }\n if (!to) {\n return res.status(400).json({\n error: channel === 'sms' ? 'Enter a phone number' : 'Enter an email address',\n })\n }\n const [member, order] = await Promise.all([\n consumeRateLimit(`commerce-receipt-send:uid:${decoded.uid}`, {\n limit: RECEIPT_RESENDS_PER_MINUTE_PER_MEMBER,\n windowMs: 60_000,\n }),\n consumeRateLimit(`commerce-receipt-send:order:${hostId}:${orderId}`, {\n limit: RECEIPT_RESENDS_PER_HOUR_PER_ORDER,\n windowMs: 60 * 60_000,\n }),\n ])\n if (!member.allowed || !order.allowed) {\n const resetMs = Math.max(\n member.allowed ? 0 : member.resetMs,\n order.allowed ? 0 : order.resetMs,\n )\n res.setHeader(\n 'Retry-After',\n String(Math.max(1, Math.ceil((resetMs - Date.now()) / 1000))),\n )\n return res.status(429).json({\n error: order.allowed\n ? 'Too many receipts sent — wait a minute and try again.'\n : 'This receipt was re-sent several times in the last hour — try again later.',\n })\n }\n const outcome = await sendOrderReceipt(\n { hostId, orderId },\n { channel, to },\n )\n switch (outcome.outcome) {\n case 'sent':\n return res.status(200).json({ ok: true, channel })\n case 'no_such_order':\n return res.status(404).json({ error: 'Unknown order' })\n case 'not_configured':\n return res.status(409).json({\n error:\n channel === 'sms'\n ? 'Text messages are not set up on this platform.'\n : 'Email is not set up on this platform.',\n })\n case 'invalid_recipient':\n return res.status(400).json({\n error:\n channel === 'sms'\n ? 'That phone number could not be read.'\n : 'That email address could not be read.',\n })\n default:\n return res.status(502).json({\n error:\n outcome.error === 'suppressed'\n ? 'That number has opted out of texts.'\n : 'The receipt could not be sent. Try again.',\n })\n }\n } catch (error) {\n console.error('orderReceiptSend failed', error)\n return res.status(500).json({ error: 'The receipt could not be sent.' })\n }\n}\n"],"names":["holdsCommerceSiteKey","commerceSitePermissionRefusal","pluginSmsAvailable","consumeRateLimit","firebaseAdmin","isEmailConfigured","sendOrderReceipt","RECEIPT_RESENDS_PER_MINUTE_PER_MEMBER","RECEIPT_RESENDS_PER_HOUR_PER_ORDER","orderReceiptSendHandler","req","res","method","status","json","error","authorization","String","headers","idToken","startsWith","slice","length","undefined","body","JSON","parse","hostId","query","test","decoded","app","auth","verifyIdToken","hostSnapshot","firestore","collection","doc","get","exists","uid","email","sms","orderId","channel","to","trim","member","order","Promise","all","limit","windowMs","allowed","resetMs","Math","max","setHeader","ceil","Date","now","outcome","ok","console"],"mappings":"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"}
|
|
@@ -14,7 +14,9 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
14
14
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
|
-
*/ import
|
|
17
|
+
*/ import { holdsCommerceSiteKey } from "./commerce-site-keys.js";
|
|
18
|
+
import { commerceSitePermissionRefusal } from "../model/plugin-permissions.js";
|
|
19
|
+
import * as Aglyn from "@aglyn/aglyn/server";
|
|
18
20
|
import { firebaseAdmin, getOrgForHost } from "@aglyn/tenant-data-admin";
|
|
19
21
|
import * as CommerceModel from "../model/index.js";
|
|
20
22
|
/*
|
|
@@ -89,16 +91,17 @@ export const ordersShippingPrepareHandler = async (req, res)=>{
|
|
|
89
91
|
error: 'Missing hostId'
|
|
90
92
|
});
|
|
91
93
|
try {
|
|
92
|
-
var _hostSnapshot_get;
|
|
93
94
|
const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
|
|
94
95
|
const hostSnapshot = await firebaseAdmin.app().firestore().collection('hosts').doc(hostId).get();
|
|
95
96
|
if (!hostSnapshot.exists) return res.status(404).json({
|
|
96
97
|
error: 'Unknown site'
|
|
97
98
|
});
|
|
98
|
-
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
// "Manage orders" (`commerce.orders`, AGL-4047).
|
|
100
|
+
if (!holdsCommerceSiteKey(hostSnapshot, decoded.uid, 'commerce.orders')) {
|
|
101
|
+
return res.status(403).json({
|
|
102
|
+
error: commerceSitePermissionRefusal('commerce.orders')
|
|
103
|
+
});
|
|
104
|
+
}
|
|
102
105
|
const owner = await getOrgForHost(hostId);
|
|
103
106
|
if (!Aglyn.checkEntitlement(owner == null ? void 0 : owner.org, 'commerce')) {
|
|
104
107
|
return res.status(403).json({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/orders-shipping-prepare.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn/server'\nimport { firebaseAdmin, getOrgForHost } from '@aglyn/tenant-data-admin'\nimport * as CommerceModel from '../model'\n\n/*\n * `commerce/orders-shipping-prepare` (AGL-3613): POST `{ hostId }` stamps the\n * site's open orders written before AGL-3613 with what the shipping export\n * and a shipping tool's feed ask (`requiresShipping`, `updatedAtMs`), so the\n * indexed query finds them. The console calls it before an export for\n * shipping. Bounded to OPEN orders — the ones a shipping tool still has to\n * see — and a no-op for an order already stamped. The site's admins and\n * editors, the order book's roles.\n */\n\n/** Statuses a shipping tool still has to see an order in. */\nconst OPEN_STATUSES: readonly CommerceModel.OrderStatus[] = ['pending', 'paid', 'partially_fulfilled']\n\n/** The most open orders one `prepare` stamps; a store with more is stamped over several calls. */\nexport const PREPARE_MAX_ORDERS = 2000\n\n/**\n * Stamps the site's open orders that predate the stamp. Answers how many it\n * stamped. `updatedAtMs` is the order's creation, not now: stamping is not\n * a change ShipStation needs to hear about.\n */\nexport async function prepareOpenOrders(hostId: string): Promise<number> {\n const firestore = firebaseAdmin.app().firestore()\n const orders = firestore.collection('hosts').doc(hostId).collection('orders')\n const snapshot = await orders.where('status', 'in', [...OPEN_STATUSES]).limit(PREPARE_MAX_ORDERS).get()\n let stamped = 0\n let batch = firestore.batch()\n let inBatch = 0\n for (const doc of snapshot.docs) {\n const data = (doc.data() ?? {}) as Partial<CommerceModel.HostOrder> & { requiresShipping?: unknown; updatedAtMs?: unknown; createdAtMs?: unknown }\n if (typeof data.requiresShipping === 'boolean' && typeof data.updatedAtMs === 'number') continue\n const createdAtMs = Number(data.createdAtMs)\n batch.update(doc.ref, {\n ...(typeof data.requiresShipping === 'boolean' ? {} : { requiresShipping: CommerceModel.orderRequiresShipping(data) }),\n ...(typeof data.updatedAtMs === 'number'\n ? {}\n : { updatedAtMs: Number.isFinite(createdAtMs) && createdAtMs > 0 ? createdAtMs : Date.now() }),\n })\n stamped += 1\n inBatch += 1\n if (inBatch === 400) {\n await batch.commit()\n batch = firestore.batch()\n inBatch = 0\n }\n }\n if (inBatch) await batch.commit()\n return stamped\n}\n\nexport const ordersShippingPrepareHandler: Aglyn.PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n let body: Record<string, unknown>\n try {\n body = typeof req.body === 'string' ? JSON.parse(req.body) : (req.body ?? {})\n } catch {\n return res.status(400).json({ error: 'The request could not be read' })\n }\n const hostId = String(body['hostId'] ?? '')\n if (!/^[A-Za-z0-9_-]{1,128}$/.test(hostId)) return res.status(400).json({ error: 'Missing hostId' })\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const hostSnapshot = await firebaseAdmin.app().firestore().collection('hosts').doc(hostId).get()\n if (!hostSnapshot.exists) return res.status(404).json({ error: 'Unknown site' })\n
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/orders-shipping-prepare.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { holdsCommerceSiteKey } from './commerce-site-keys'\nimport { commerceSitePermissionRefusal } from '../model/plugin-permissions'\nimport * as Aglyn from '@aglyn/aglyn/server'\nimport { firebaseAdmin, getOrgForHost } from '@aglyn/tenant-data-admin'\nimport * as CommerceModel from '../model'\n\n/*\n * `commerce/orders-shipping-prepare` (AGL-3613): POST `{ hostId }` stamps the\n * site's open orders written before AGL-3613 with what the shipping export\n * and a shipping tool's feed ask (`requiresShipping`, `updatedAtMs`), so the\n * indexed query finds them. The console calls it before an export for\n * shipping. Bounded to OPEN orders — the ones a shipping tool still has to\n * see — and a no-op for an order already stamped. The site's admins and\n * editors, the order book's roles.\n */\n\n/** Statuses a shipping tool still has to see an order in. */\nconst OPEN_STATUSES: readonly CommerceModel.OrderStatus[] = ['pending', 'paid', 'partially_fulfilled']\n\n/** The most open orders one `prepare` stamps; a store with more is stamped over several calls. */\nexport const PREPARE_MAX_ORDERS = 2000\n\n/**\n * Stamps the site's open orders that predate the stamp. Answers how many it\n * stamped. `updatedAtMs` is the order's creation, not now: stamping is not\n * a change ShipStation needs to hear about.\n */\nexport async function prepareOpenOrders(hostId: string): Promise<number> {\n const firestore = firebaseAdmin.app().firestore()\n const orders = firestore.collection('hosts').doc(hostId).collection('orders')\n const snapshot = await orders.where('status', 'in', [...OPEN_STATUSES]).limit(PREPARE_MAX_ORDERS).get()\n let stamped = 0\n let batch = firestore.batch()\n let inBatch = 0\n for (const doc of snapshot.docs) {\n const data = (doc.data() ?? {}) as Partial<CommerceModel.HostOrder> & { requiresShipping?: unknown; updatedAtMs?: unknown; createdAtMs?: unknown }\n if (typeof data.requiresShipping === 'boolean' && typeof data.updatedAtMs === 'number') continue\n const createdAtMs = Number(data.createdAtMs)\n batch.update(doc.ref, {\n ...(typeof data.requiresShipping === 'boolean' ? {} : { requiresShipping: CommerceModel.orderRequiresShipping(data) }),\n ...(typeof data.updatedAtMs === 'number'\n ? {}\n : { updatedAtMs: Number.isFinite(createdAtMs) && createdAtMs > 0 ? createdAtMs : Date.now() }),\n })\n stamped += 1\n inBatch += 1\n if (inBatch === 400) {\n await batch.commit()\n batch = firestore.batch()\n inBatch = 0\n }\n }\n if (inBatch) await batch.commit()\n return stamped\n}\n\nexport const ordersShippingPrepareHandler: Aglyn.PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n let body: Record<string, unknown>\n try {\n body = typeof req.body === 'string' ? JSON.parse(req.body) : (req.body ?? {})\n } catch {\n return res.status(400).json({ error: 'The request could not be read' })\n }\n const hostId = String(body['hostId'] ?? '')\n if (!/^[A-Za-z0-9_-]{1,128}$/.test(hostId)) return res.status(400).json({ error: 'Missing hostId' })\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const hostSnapshot = await firebaseAdmin.app().firestore().collection('hosts').doc(hostId).get()\n if (!hostSnapshot.exists) return res.status(404).json({ error: 'Unknown site' })\n // \"Manage orders\" (`commerce.orders`, AGL-4047).\n if (!holdsCommerceSiteKey(hostSnapshot, decoded.uid, 'commerce.orders')) {\n return res.status(403).json({ error: commerceSitePermissionRefusal('commerce.orders') })\n }\n const owner = await getOrgForHost(hostId)\n if (!Aglyn.checkEntitlement(owner?.org as never, 'commerce')) {\n return res.status(403).json({ error: 'Selling is not enabled' })\n }\n const stamped = await prepareOpenOrders(hostId)\n return res.status(200).json({ ok: true, stamped })\n } catch (error) {\n console.error('orders-shipping-prepare failed', hostId, error)\n return res.status(500).json({ error: 'That did not work. Try again.' })\n }\n}\n"],"names":["holdsCommerceSiteKey","commerceSitePermissionRefusal","Aglyn","firebaseAdmin","getOrgForHost","CommerceModel","OPEN_STATUSES","PREPARE_MAX_ORDERS","prepareOpenOrders","hostId","firestore","app","orders","collection","doc","snapshot","where","limit","get","stamped","batch","inBatch","docs","data","requiresShipping","updatedAtMs","createdAtMs","Number","update","ref","orderRequiresShipping","isFinite","Date","now","commit","ordersShippingPrepareHandler","req","res","body","method","status","json","error","authorization","String","headers","idToken","startsWith","slice","length","undefined","JSON","parse","test","decoded","auth","verifyIdToken","hostSnapshot","exists","uid","owner","checkEntitlement","org","ok","console"],"mappings":"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"}
|