@aglyn/plugins-commerce 1.0.0-beta.285 → 1.0.0-beta.288
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/order-webhooks-card.component.js +1 -1
- package/src/lib/components/console/order-webhooks-card.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 +15 -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 +74 -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/model/workspace-site-permissions.d.ts +26 -0
- package/src/lib/model/workspace-site-permissions.js +81 -0
- package/src/lib/model/workspace-site-permissions.js.map +1 -0
- 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 +48 -0
- package/src/lib/server/commerce-site-keys.js +61 -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/order-webhooks.js +8 -8
- package/src/lib/server/order-webhooks.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 +28 -9
- package/src/lib/server/pos-ops-gate.js +50 -19
- 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-refund-authority.d.ts +3 -1
- package/src/lib/server/pos-refund-authority.js +3 -2
- package/src/lib/server/pos-refund-authority.js.map +1 -1
- package/src/lib/server/pos-return.d.ts +10 -0
- package/src/lib/server/pos-return.js +29 -7
- 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 +29 -24
- 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 +55 -42
- 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/model/commerce-pos.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * THE POS TENDER LEDGER (AGL-3607).\n *\n * A register sale used to be settled by exactly one tender and recorded none:\n * whether a POS order was paid in cash, by the QR link or to a room was\n * inferred afterwards from which other fields happened to be set. A sale now\n * carries `payments[]`, one entry per tender, and takes payments until the\n * balance due is zero. Only then is it `paid`.\n *\n * Every figure here is integer cents. `amountCents` is what a payment put\n * toward the SALE; a tip rides beside it in `tipCents` and never counts toward\n * the balance, the platform's take or the store's revenue. Tips belong to the\n * merchant and their staff.\n *\n * Pure and framework-free, so the register, the customer display and the\n * server read one answer.\n *=========================================*/\n\n/** How one payment toward a register sale was taken. */\nexport type OrderPaymentMethod =\n | 'cash'\n /** A Stripe Terminal smart reader (tap, insert or swipe). */\n | 'card_present'\n /** A card typed into the console by staff (card not present). */\n | 'card_keyed'\n /** The Stripe payment page the customer opens from a QR code. */\n | 'card_link'\n | 'gift_card'\n /** Charged to a checked-in reservation's folio. */\n | 'folio'\n /**\n * Store credit another plugin keeps — rewards, referral credit — taken\n * through core's checkout-credits seam (AGL-3640). Which plugin and which\n * account ride on the payment as `creditProviderId` and `creditReference`.\n */\n | 'credit'\n\nexport type OrderPaymentStatus =\n /** Started and not finished: reserves its amount against the balance. */\n | 'pending'\n | 'succeeded'\n /** Declined or errored; may be retried, reserves nothing. */\n | 'failed'\n | 'canceled'\n /** Taken and then handed back when the open sale was voided. */\n | 'reversed'\n\nexport const ORDER_PAYMENT_METHODS: readonly OrderPaymentMethod[] = [\n 'cash',\n 'card_present',\n 'card_keyed',\n 'card_link',\n 'gift_card',\n 'folio',\n 'credit',\n]\n\n/** One payment toward a register sale, on `HostOrder.payments`. */\nexport interface OrderPayment {\n /** Stable id, derived from the register's attempt key so a retry finds it. */\n id: string\n method: OrderPaymentMethod\n /** Cents put toward the sale. Never includes the tip. */\n amountCents: number\n /** Gratuity on top of `amountCents`, the merchant's and not revenue. */\n tipCents?: number\n status: OrderPaymentStatus\n /** When the payment was started. */\n atMs: number\n /** When it reached `succeeded`. */\n settledAtMs?: number\n /** Stripe PaymentIntent for `card_present` / `card_keyed` / a paid link. */\n paymentIntentId?: string\n /** Stripe Checkout Session for `card_link`. */\n checkoutSessionId?: string\n /** The payment page a `card_link` payment shows as a QR. */\n checkoutUrl?: string\n /** The Terminal reader a `card_present` payment runs on. */\n readerId?: string\n /** The gift card's code (its document id) for `gift_card`. */\n giftCardId?: string\n /** For `credit`: the provider that paid it (`{pluginId}.{key}`, AGL-3640). */\n creditProviderId?: string\n /** For `credit`: the provider's own handle on the account it came from. */\n creditReference?: string\n /** For `credit`: what the provider calls it, for the register and receipt. */\n creditLabel?: string\n cardBrand?: string\n last4?: string\n /** Cash handed over for a `cash` payment, and the change given back. */\n cashTenderedCents?: number\n changeCents?: number\n /** The stay a `folio` payment was charged to. */\n reservationId?: string\n /** The folio entry's `atMs`, so a void can remove exactly that entry. */\n folioAtMs?: number\n /**\n * The platform's take attributed to this payment (excludes the tip). For a\n * card payment it is netted from the payout with Stripe's processing cost\n * added, and recorded in `feeCents`; otherwise it accrues to the invoice.\n */\n takeFeeCents?: number\n /** The `application_fee_amount` a card payment carried, or 0. */\n feeCents?: number\n /** Why a `failed` payment failed, in words the cashier can read out. */\n failureMessage?: string\n /** Whether Stripe moved real money; absent for tenders Stripe never saw. */\n livemode?: boolean\n /** The console user who took the payment. */\n cashierId?: string\n}\n\n/** The tenders whose money moves through Stripe. */\nexport const POS_CARD_METHODS: ReadonlySet<OrderPaymentMethod> = new Set([\n 'card_present',\n 'card_keyed',\n 'card_link',\n])\n\nexport function isCardPaymentMethod(method: OrderPaymentMethod): boolean {\n return POS_CARD_METHODS.has(method)\n}\n\n/*==========================================\n * WHICH TENDERS CARRY THE PLATFORM FEE (AGL-4036).\n *\n * The take used to follow the sale whatever paid it (AGL-2111): a cash sale\n * owed the same percentage as a card sale and had it added to the monthly\n * invoice. In-person rivals charge nothing on cash, and an event register that\n * bills its operator for the cash in their own apron is not one they will\n * choose, so three tenders now carry none:\n *\n * - cash: no payment service touches it, and the merchant keeps all of it;\n * - gift card: the fee was taken when the card was SOLD, so charging it again\n * when the card is spent would charge the same money twice;\n * - store credit (`credit`): rewards or referral value the merchant gave\n * away, so no money changes hands and an invoiced fee on it would only\n * penalize running a rewards program.\n *\n * Everything else still carries it. Card payments net it from their payout,\n * as always. A room charge (`folio`) is new revenue booked to a stay, so its\n * share goes on the invoice.\n *\n * One predicate, so the register, the completion, the offline sync and the\n * docs cannot disagree about which tender is which.\n *=========================================*/\n\n/** The tenders that carry no platform fee (AGL-4036). */\nexport const POS_FEE_FREE_METHODS: ReadonlySet<OrderPaymentMethod> = new Set([\n 'cash',\n 'gift_card',\n 'credit',\n])\n\n/** Whether a payment by `method` carries its share of the platform's take. */\nexport function paymentCarriesPlatformFee(method: OrderPaymentMethod): boolean {\n return !POS_FEE_FREE_METHODS.has(method)\n}\n\n/**\n * The take a sale owes when ONE tender pays all of it: the whole take, or\n * nothing for a fee-free tender.\n */\nexport function posTenderTakeCents(\n method: OrderPaymentMethod,\n takeFeeCents: number,\n): number {\n return paymentCarriesPlatformFee(method) ? wholeCents(takeFeeCents) : 0\n}\n\n/** Whole non-negative cents from an untrusted value. */\nfunction wholeCents(value: unknown): number {\n const number = Math.round(Number(value))\n return Number.isFinite(number) && number > 0 ? number : 0\n}\n\n/**\n * The order's payments, read through one door.\n *\n * Orders written before the ledger existed carry no `payments`, so one is\n * INFERRED from the fields the single-tender register wrote: a folio sale has\n * `reservationId`, a QR sale has `checkoutSessionId`, and anything else on the\n * POS channel was cash. A pending legacy sale is the QR link still waiting.\n * Online orders paid through Stripe Checkout read as one `card_link` payment.\n * Nothing is written back: this is a reader, never a migration.\n */\nexport function orderPayments(order: {\n payments?: OrderPayment[] | null\n channel?: string\n status?: string\n totals?: { totalCents?: number; tipCents?: number } | null\n amountCents?: number\n reservationId?: string\n checkoutSessionId?: string\n paymentIntentId?: string\n createdAtMs?: number\n}): OrderPayment[] {\n if (Array.isArray(order?.payments)) {\n return order.payments.filter(\n (payment): payment is OrderPayment =>\n Boolean(payment) && typeof payment === 'object' && Boolean(payment.id),\n )\n }\n const totalCents = wholeCents(order?.totals?.totalCents ?? order?.amountCents)\n if (!(totalCents > 0)) return []\n const settled = order?.status !== 'pending' && order?.status !== 'cancelled'\n const atMs = Number(order?.createdAtMs) || 0\n const method: OrderPaymentMethod = order?.reservationId\n ? 'folio'\n : order?.checkoutSessionId || order?.paymentIntentId || order?.channel !== 'pos'\n ? 'card_link'\n : 'cash'\n return [\n {\n id: 'legacy',\n method,\n amountCents: totalCents,\n status: settled ? 'succeeded' : order?.status === 'cancelled' ? 'canceled' : 'pending',\n atMs,\n ...(order?.paymentIntentId ? { paymentIntentId: order.paymentIntentId } : {}),\n ...(order?.checkoutSessionId ? { checkoutSessionId: order.checkoutSessionId } : {}),\n ...(order?.reservationId ? { reservationId: order.reservationId } : {}),\n },\n ]\n}\n\n/** Cents put toward the sale by payments that succeeded. */\nexport function posSettledCents(payments: readonly OrderPayment[]): number {\n return payments\n .filter((payment) => payment.status === 'succeeded')\n .reduce((sum, payment) => sum + wholeCents(payment.amountCents), 0)\n}\n\n/**\n * Cents spoken for: succeeded payments plus the ones still in flight. A\n * reader waiting for a tap has claimed its share of the balance, so a second\n * tender cannot be started for the same money.\n */\nexport function posReservedCents(payments: readonly OrderPayment[]): number {\n return payments\n .filter((payment) => payment.status === 'succeeded' || payment.status === 'pending')\n .reduce((sum, payment) => sum + wholeCents(payment.amountCents), 0)\n}\n\n/** What the customer still owes, by settled payments. */\nexport function posBalanceDueCents(\n totalCents: number,\n payments: readonly OrderPayment[],\n): number {\n return Math.max(0, wholeCents(totalCents) - posSettledCents(payments))\n}\n\n/** What a NEW payment may still take: the balance less what is in flight. */\nexport function posTenderableCents(\n totalCents: number,\n payments: readonly OrderPayment[],\n): number {\n return Math.max(0, wholeCents(totalCents) - posReservedCents(payments))\n}\n\n/** Tips taken across the sale's settled payments. */\nexport function posTipCents(payments: readonly OrderPayment[]): number {\n return payments\n .filter((payment) => payment.status === 'succeeded')\n .reduce((sum, payment) => sum + wholeCents(payment.tipCents), 0)\n}\n\n/**\n * The share of the sale's take a payment of `amountCents` carries.\n *\n * Proportional to the part of the SALE it pays, never to the tip. Card\n * payments carry their share in `application_fee_amount`; whatever the card\n * shares leave is accrued to the invoice at completion\n * ({@link posInvoiceTakeCents}), so the take is exact however the sale was\n * split and however each share rounded.\n */\nexport function posTakeShareCents(input: {\n takeFeeCents: number\n totalCents: number\n amountCents: number\n}): number {\n const take = wholeCents(input.takeFeeCents)\n const total = wholeCents(input.totalCents)\n const amount = Math.min(wholeCents(input.amountCents), total)\n if (!(take > 0) || !(total > 0) || !(amount > 0)) return 0\n return Math.min(take, Math.round((take * amount) / total))\n}\n\n/**\n * {@link posTakeShareCents} for a payment by `method`: its share of the take,\n * or 0 for a tender that carries no platform fee (AGL-4036).\n */\nexport function posPaymentTakeShareCents(input: {\n method: OrderPaymentMethod\n takeFeeCents: number\n totalCents: number\n amountCents: number\n}): number {\n if (!paymentCarriesPlatformFee(input.method)) return 0\n return posTakeShareCents(input)\n}\n\n/**\n * The part of the sale's take its tenders owe (AGL-4036): the whole take when\n * every settled payment carries the fee, and otherwise the share for the part\n * of the sale the fee-carrying payments paid. Cash and gift-card parts owe\n * nothing, so a sale paid all in cash owes 0.\n *\n * Worked from the payments' METHODS, not from the `takeFeeCents` each one\n * recorded, so a sale that was opened before this rule and finished after it\n * is charged by the rule it finished under.\n */\nexport function posOwedTakeCents(input: {\n takeFeeCents: number\n totalCents: number\n payments: readonly OrderPayment[]\n}): number {\n const take = wholeCents(input.takeFeeCents)\n const settled = input.payments.filter((payment) => payment.status === 'succeeded')\n if (settled.every((payment) => paymentCarriesPlatformFee(payment.method))) return take\n const carryingCents = settled\n .filter((payment) => paymentCarriesPlatformFee(payment.method))\n .reduce((sum, payment) => sum + wholeCents(payment.amountCents), 0)\n return posTakeShareCents({\n takeFeeCents: take,\n totalCents: input.totalCents,\n amountCents: carryingCents,\n })\n}\n\n/**\n * What of the sale's take was NOT netted from a card payout, and therefore\n * goes on the merchant's invoice: the take the tenders owe\n * ({@link posOwedTakeCents}) less every succeeded card payment's share. Never\n * negative, and 0 for a sale paid only by card, cash, gift card and store credit.\n */\nexport function posInvoiceTakeCents(input: {\n takeFeeCents: number\n totalCents: number\n payments: readonly OrderPayment[]\n}): number {\n const netted = input.payments\n .filter(\n (payment) =>\n payment.status === 'succeeded' && isCardPaymentMethod(payment.method),\n )\n .reduce((sum, payment) => sum + wholeCents(payment.takeFeeCents), 0)\n return Math.max(0, posOwedTakeCents(input) - netted)\n}\n\n/**\n * How the sale's fee reaches the platform, from the payments that carried it.\n * A cash, gift-card or store-credit part carries none (AGL-4036), so a card + cash sale is\n * `payout`, not `split`.\n */\nexport function posFeeCollection(\n payments: readonly OrderPayment[],\n): 'payout' | 'invoice' | 'split' {\n const settled = payments.filter(\n (payment) =>\n payment.status === 'succeeded' && paymentCarriesPlatformFee(payment.method),\n )\n const card = settled.some((payment) => isCardPaymentMethod(payment.method))\n const other = settled.some((payment) => !isCardPaymentMethod(payment.method))\n if (card && other) return 'split'\n return card ? 'payout' : 'invoice'\n}\n\n/** A one-line description of a payment for the register, receipt and timeline. */\nexport function describeOrderPayment(payment: OrderPayment): string {\n const dollars = (cents: number) => `$${(cents / 100).toFixed(2)}`\n const label =\n (payment.method === 'credit' && payment.creditLabel) || ORDER_PAYMENT_METHOD_LABELS[payment.method] || 'Payment'\n const card =\n payment.cardBrand || payment.last4\n ? ` (${[payment.cardBrand, payment.last4 ? `•••• ${payment.last4}` : '']\n .filter(Boolean)\n .join(' ')})`\n : ''\n const tip = wholeCents(payment.tipCents)\n ? ` + ${dollars(wholeCents(payment.tipCents))} tip`\n : ''\n return `${label}${card} ${dollars(wholeCents(payment.amountCents))}${tip}`\n}\n\nexport const ORDER_PAYMENT_METHOD_LABELS: Record<OrderPaymentMethod, string> = {\n cash: 'Cash',\n card_present: 'Card reader',\n card_keyed: 'Typed card',\n card_link: 'Card (QR)',\n gift_card: 'Gift card',\n folio: 'Charged to room',\n credit: 'Store credit',\n}\n\n/*==========================================\n * TIPS (AGL-3607).\n *=========================================*/\n\n/** The presets a merchant starts with. */\nexport const POS_TIP_PERCENTAGES_DEFAULT: readonly number[] = [15, 18, 20, 25]\n\n/**\n * The merchant's tip presets from the plugin setting, a comma list such as\n * `15, 18, 20, 25`. Whole percentages from 1 to 100, at most four, in the order\n * given; anything unreadable falls back to the defaults rather than showing\n * the customer an empty choice.\n */\nexport function posTipPercentages(raw: unknown): number[] {\n const parsed = String(raw ?? '')\n .split(/[,\\s]+/)\n .map((part) => Math.round(Number(part)))\n .filter((value) => Number.isFinite(value) && value >= 1 && value <= 100)\n const unique = [...new Set(parsed)].slice(0, 4)\n return unique.length ? unique : [...POS_TIP_PERCENTAGES_DEFAULT]\n}\n\n/** A percentage tip on `baseCents`, rounded to the cent. */\nexport function posTipFromPercent(baseCents: number, percent: number): number {\n const base = wholeCents(baseCents)\n const pct = Number(percent)\n if (!(base > 0) || !Number.isFinite(pct) || pct <= 0) return 0\n return Math.round((base * pct) / 100)\n}\n\n/** The largest tip the register accepts on one payment: the payment itself. */\nexport function posTipProblem(tipCents: number, amountCents: number): string | null {\n if (!Number.isInteger(tipCents) || tipCents < 0) return 'Enter a tip of $0 or more.'\n if (tipCents > wholeCents(amountCents)) {\n return 'A tip can be at most the amount being paid. Check the amount and try again.'\n }\n return null\n}\n\n/*==========================================\n * THE CUSTOMER DISPLAY (AGL-3608).\n *\n * A paired screen facing the customer reads one document per register,\n * `hosts/{hostId}/registers/{registerId}/display/state`, through the\n * `commerce/pos-display` route and its display token. The register writes\n * the prompt; the customer's answer comes back in `response`.\n *=========================================*/\n\nexport type PosDisplayMode =\n | 'idle'\n /** The live basket, mirrored as the cashier builds it. */\n | 'cart'\n /** The customer picks a tip. */\n | 'tip'\n /** The customer picks how they want their receipt. */\n | 'receipt'\n /** A card payment is waiting on the reader. */\n | 'processing'\n | 'thanks'\n\nexport type PosReceiptChannel = 'email' | 'sms' | 'print' | 'none'\n\nexport interface PosDisplayLine {\n name: string\n variantLabel?: string\n quantity: number\n amountCents: number\n}\n\nexport interface PosDisplayCart {\n lines: PosDisplayLine[]\n itemsCents: number\n discountCents: number\n taxCents: number\n totalCents: number\n /** Taken so far, and what is still due, once payments start. */\n paidCents?: number\n dueCents?: number\n tipCents?: number\n}\n\n/** The customer's answer to the current prompt. */\nexport interface PosDisplayResponse {\n /** The prompt this answers; a stale answer is refused. */\n promptId: string\n tipCents?: number\n tipPercent?: number\n /** `none` when the customer chose no tip. */\n tipChoice?: 'percent' | 'custom' | 'none'\n receiptChannel?: PosReceiptChannel\n email?: string\n phone?: string\n marketingOptIn?: boolean\n atMs: number\n}\n\nexport interface PosDisplayState {\n mode: PosDisplayMode\n updatedAtMs: number\n /**\n * The ISO currency every amount on the screen is in (`usd`). Stamped by\n * the server from the store's own currency, never taken from the register.\n */\n currency?: string\n /** Changes every time the register asks something new. */\n promptId?: string\n cart?: PosDisplayCart\n tip?: {\n /** What a percentage is taken of: the amount being paid now. */\n baseCents: number\n percentages: number[]\n allowCustom: boolean\n }\n receipt?: {\n channels: PosReceiptChannel[]\n /** Whether to offer the store's marketing opt-in under the email field. */\n offerMarketing: boolean\n }\n processing?: { message: string }\n response?: PosDisplayResponse\n}\n\n/** Most lines a display state carries; a longer basket shows its newest. */\nexport const POS_DISPLAY_MAX_LINES = 60\n\n/** A pairing code lives this long before the register must show a new one. */\nexport const POS_DISPLAY_PAIRING_TTL_MS = 10 * 60 * 1000\n\n/** A thank-you screen returns to idle on the display after this long. */\nexport const POS_DISPLAY_THANKS_MS = 8_000\n\nconst EMAIL_PATTERN = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\n/** An email address as the display may store it, or '' when it is not one. */\nexport function posDisplayEmail(value: unknown): string {\n const email = String(value ?? '').trim().toLowerCase().slice(0, 254)\n return EMAIL_PATTERN.test(email) ? email : ''\n}\n\n/** A phone number as digits with an optional leading +, or '' when too short. */\nexport function posDisplayPhone(value: unknown): string {\n const raw = String(value ?? '').trim()\n const digits = raw.replace(/[^0-9]/g, '')\n if (digits.length < 7 || digits.length > 15) return ''\n return `${raw.startsWith('+') ? '+' : ''}${digits}`\n}\n\n/**\n * The display state as the REGISTER may send it: clamped, typed and stripped\n * of anything the customer screen has no business holding. The register is\n * staff and trusted for intent, not for shape.\n */\nexport function sanitizePosDisplayState(\n raw: unknown,\n nowMs: number,\n): PosDisplayState {\n const input = (raw && typeof raw === 'object' ? raw : {}) as Record<string, any>\n const modes: PosDisplayMode[] = ['idle', 'cart', 'tip', 'receipt', 'processing', 'thanks']\n const mode = modes.includes(input['mode']) ? (input['mode'] as PosDisplayMode) : 'idle'\n const state: PosDisplayState = { mode, updatedAtMs: nowMs }\n const promptId = String(input['promptId'] ?? '').slice(0, 64)\n if (promptId) state.promptId = promptId\n const cart = input['cart']\n if (cart && typeof cart === 'object' && mode !== 'idle') {\n const lines = (Array.isArray(cart.lines) ? cart.lines : [])\n .slice(-POS_DISPLAY_MAX_LINES)\n .map((line: any) => ({\n name: String(line?.name ?? '').slice(0, 120),\n ...(line?.variantLabel\n ? { variantLabel: String(line.variantLabel).slice(0, 80) }\n : {}),\n quantity: Math.max(1, Math.min(999, Math.round(Number(line?.quantity) || 1))),\n amountCents: wholeCents(line?.amountCents),\n }))\n state.cart = {\n lines,\n itemsCents: wholeCents(cart.itemsCents),\n discountCents: wholeCents(cart.discountCents),\n taxCents: wholeCents(cart.taxCents),\n totalCents: wholeCents(cart.totalCents),\n ...(cart.paidCents != null ? { paidCents: wholeCents(cart.paidCents) } : {}),\n ...(cart.dueCents != null ? { dueCents: wholeCents(cart.dueCents) } : {}),\n ...(cart.tipCents != null ? { tipCents: wholeCents(cart.tipCents) } : {}),\n }\n }\n if (mode === 'tip') {\n const tip = input['tip'] ?? {}\n state.tip = {\n baseCents: wholeCents(tip.baseCents),\n percentages: posTipPercentages(\n Array.isArray(tip.percentages) ? tip.percentages.join(',') : tip.percentages,\n ),\n allowCustom: tip.allowCustom !== false,\n }\n }\n if (mode === 'receipt') {\n const receipt = input['receipt'] ?? {}\n const allowed: PosReceiptChannel[] = ['email', 'sms', 'print', 'none']\n const channels = (Array.isArray(receipt.channels) ? receipt.channels : [])\n .filter((channel: unknown): channel is PosReceiptChannel =>\n allowed.includes(channel as PosReceiptChannel),\n )\n state.receipt = {\n channels: channels.length ? [...new Set<PosReceiptChannel>(channels)] : ['print', 'none'],\n offerMarketing: receipt.offerMarketing === true,\n }\n }\n if (mode === 'processing') {\n state.processing = {\n message:\n String(input['processing']?.message ?? '').slice(0, 160) ||\n 'Tap, insert or swipe your card on the reader.',\n }\n }\n return state\n}\n\n/**\n * The customer's answer as the DISPLAY may send it, checked against the\n * prompt it claims to answer. Returns null when it answers nothing the\n * register asked: a stale screen, a replay, or a mode that takes no answer.\n */\nexport function sanitizePosDisplayResponse(\n state: PosDisplayState | null | undefined,\n raw: unknown,\n nowMs: number,\n): PosDisplayResponse | null {\n if (!state || !state.promptId) return null\n const input = (raw && typeof raw === 'object' ? raw : {}) as Record<string, any>\n if (String(input['promptId'] ?? '') !== state.promptId) return null\n const response: PosDisplayResponse = { promptId: state.promptId, atMs: nowMs }\n if (state.mode === 'tip' && state.tip) {\n const choice = input['tipChoice']\n if (choice === 'none') {\n response.tipChoice = 'none'\n response.tipCents = 0\n } else if (choice === 'percent') {\n const percent = Math.round(Number(input['tipPercent']))\n if (!state.tip.percentages.includes(percent)) return null\n response.tipChoice = 'percent'\n response.tipPercent = percent\n response.tipCents = posTipFromPercent(state.tip.baseCents, percent)\n } else if (choice === 'custom' && state.tip.allowCustom) {\n const tipCents = Math.round(Number(input['tipCents']))\n if (posTipProblem(tipCents, state.tip.baseCents)) return null\n response.tipChoice = 'custom'\n response.tipCents = tipCents\n } else {\n return null\n }\n return response\n }\n if (state.mode === 'receipt' && state.receipt) {\n const channel = input['receiptChannel'] as PosReceiptChannel\n if (!state.receipt.channels.includes(channel)) return null\n response.receiptChannel = channel\n if (channel === 'email') {\n const email = posDisplayEmail(input['email'])\n if (!email) return null\n response.email = email\n if (state.receipt.offerMarketing && input['marketingOptIn'] === true) {\n response.marketingOptIn = true\n }\n }\n if (channel === 'sms') {\n const phone = posDisplayPhone(input['phone'])\n if (!phone) return null\n response.phone = phone\n }\n return response\n }\n return null\n}\n\n/**\n * The state as the DISPLAY reads it. The customer's own typed address is\n * never echoed back to the screen: once answered, the prompt is done and the\n * screen shows what comes next, so nothing a later customer could read off\n * an unattended tablet is ever sent to it.\n */\nexport function posDisplayPublicState(\n state: PosDisplayState | null | undefined,\n nowMs: number,\n): Omit<PosDisplayState, 'response'> & { answered: boolean } {\n if (!state) return { mode: 'idle', updatedAtMs: nowMs, answered: false }\n const { response, ...rest } = state\n if (rest.mode === 'thanks' && nowMs - rest.updatedAtMs > POS_DISPLAY_THANKS_MS) {\n return { mode: 'idle', updatedAtMs: rest.updatedAtMs, answered: false }\n }\n return {\n ...rest,\n answered: Boolean(response && response.promptId === rest.promptId),\n }\n}\n"],"names":["ORDER_PAYMENT_METHODS","POS_CARD_METHODS","Set","isCardPaymentMethod","method","has","POS_FEE_FREE_METHODS","paymentCarriesPlatformFee","posTenderTakeCents","takeFeeCents","wholeCents","value","number","Math","round","Number","isFinite","orderPayments","order","Array","isArray","payments","filter","payment","Boolean","id","totalCents","totals","amountCents","settled","status","atMs","createdAtMs","reservationId","checkoutSessionId","paymentIntentId","channel","posSettledCents","reduce","sum","posReservedCents","posBalanceDueCents","max","posTenderableCents","posTipCents","tipCents","posTakeShareCents","input","take","total","amount","min","posPaymentTakeShareCents","posOwedTakeCents","every","carryingCents","posInvoiceTakeCents","netted","posFeeCollection","card","some","other","describeOrderPayment","dollars","cents","toFixed","label","creditLabel","ORDER_PAYMENT_METHOD_LABELS","cardBrand","last4","join","tip","cash","card_present","card_keyed","card_link","gift_card","folio","credit","POS_TIP_PERCENTAGES_DEFAULT","posTipPercentages","raw","parsed","String","split","map","part","unique","slice","length","posTipFromPercent","baseCents","percent","base","pct","posTipProblem","isInteger","POS_DISPLAY_MAX_LINES","POS_DISPLAY_PAIRING_TTL_MS","POS_DISPLAY_THANKS_MS","EMAIL_PATTERN","posDisplayEmail","email","trim","toLowerCase","test","posDisplayPhone","digits","replace","startsWith","sanitizePosDisplayState","nowMs","modes","mode","includes","state","updatedAtMs","promptId","cart","lines","line","name","variantLabel","quantity","itemsCents","discountCents","taxCents","paidCents","dueCents","percentages","allowCustom","receipt","allowed","channels","offerMarketing","processing","message","sanitizePosDisplayResponse","response","choice","tipChoice","tipPercent","receiptChannel","marketingOptIn","phone","posDisplayPublicState","answered","rest"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-pos.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * THE POS TENDER LEDGER (AGL-3607).\n *\n * A register sale used to be settled by exactly one tender and recorded none:\n * whether a POS order was paid in cash, by the QR link or to a room was\n * inferred afterwards from which other fields happened to be set. A sale now\n * carries `payments[]`, one entry per tender, and takes payments until the\n * balance due is zero. Only then is it `paid`.\n *\n * Every figure here is integer cents. `amountCents` is what a payment put\n * toward the SALE; a tip rides beside it in `tipCents` and never counts toward\n * the balance, the platform's take or the store's revenue. Tips belong to the\n * merchant and their staff.\n *\n * Pure and framework-free, so the register, the customer display and the\n * server read one answer.\n *=========================================*/\n\n/** How one payment toward a register sale was taken. */\nexport type OrderPaymentMethod =\n | 'cash'\n /** A Stripe Terminal smart reader (tap, insert or swipe). */\n | 'card_present'\n /** A card typed into the console by staff (card not present). */\n | 'card_keyed'\n /** The Stripe payment page the customer opens from a QR code. */\n | 'card_link'\n | 'gift_card'\n /** Charged to a checked-in reservation's folio. */\n | 'folio'\n /**\n * Store credit another plugin keeps — rewards, referral credit — taken\n * through core's checkout-credits seam (AGL-3640). Which plugin and which\n * account ride on the payment as `creditProviderId` and `creditReference`.\n */\n | 'credit'\n\nexport type OrderPaymentStatus =\n /** Started and not finished: reserves its amount against the balance. */\n | 'pending'\n | 'succeeded'\n /** Declined or errored; may be retried, reserves nothing. */\n | 'failed'\n | 'canceled'\n /** Taken and then handed back when the open sale was voided. */\n | 'reversed'\n\nexport const ORDER_PAYMENT_METHODS: readonly OrderPaymentMethod[] = [\n 'cash',\n 'card_present',\n 'card_keyed',\n 'card_link',\n 'gift_card',\n 'folio',\n 'credit',\n]\n\n/** One payment toward a register sale, on `HostOrder.payments`. */\nexport interface OrderPayment {\n /** Stable id, derived from the register's attempt key so a retry finds it. */\n id: string\n method: OrderPaymentMethod\n /** Cents put toward the sale. Never includes the tip. */\n amountCents: number\n /** Gratuity on top of `amountCents`, the merchant's and not revenue. */\n tipCents?: number\n status: OrderPaymentStatus\n /** When the payment was started. */\n atMs: number\n /** When it reached `succeeded`. */\n settledAtMs?: number\n /** Stripe PaymentIntent for `card_present` / `card_keyed` / a paid link. */\n paymentIntentId?: string\n /** Stripe Checkout Session for `card_link`. */\n checkoutSessionId?: string\n /** The payment page a `card_link` payment shows as a QR. */\n checkoutUrl?: string\n /** The Terminal reader a `card_present` payment runs on. */\n readerId?: string\n /** The gift card's code (its document id) for `gift_card`. */\n giftCardId?: string\n /** For `credit`: the provider that paid it (`{pluginId}.{key}`, AGL-3640). */\n creditProviderId?: string\n /** For `credit`: the provider's own handle on the account it came from. */\n creditReference?: string\n /** For `credit`: what the provider calls it, for the register and receipt. */\n creditLabel?: string\n cardBrand?: string\n last4?: string\n /** Cash handed over for a `cash` payment, and the change given back. */\n cashTenderedCents?: number\n changeCents?: number\n /** The stay a `folio` payment was charged to. */\n reservationId?: string\n /** The folio entry's `atMs`, so a void can remove exactly that entry. */\n folioAtMs?: number\n /**\n * The platform's take attributed to this payment (excludes the tip). For a\n * card payment it is netted from the payout with Stripe's processing cost\n * added, and recorded in `feeCents`; otherwise it accrues to the invoice.\n */\n takeFeeCents?: number\n /** The `application_fee_amount` a card payment carried, or 0. */\n feeCents?: number\n /** Why a `failed` payment failed, in words the cashier can read out. */\n failureMessage?: string\n /** Whether Stripe moved real money; absent for tenders Stripe never saw. */\n livemode?: boolean\n /** The console user who took the payment. */\n cashierId?: string\n}\n\n/** The tenders whose money moves through Stripe. */\nexport const POS_CARD_METHODS: ReadonlySet<OrderPaymentMethod> = new Set([\n 'card_present',\n 'card_keyed',\n 'card_link',\n])\n\nexport function isCardPaymentMethod(method: OrderPaymentMethod): boolean {\n return POS_CARD_METHODS.has(method)\n}\n\n/*==========================================\n * WHICH TENDERS CARRY THE PLATFORM FEE (AGL-4036).\n *\n * The take used to follow the sale whatever paid it (AGL-2111): a cash sale\n * owed the same percentage as a card sale and had it added to the monthly\n * invoice. In-person rivals charge nothing on cash, and an event register that\n * bills its operator for the cash in their own apron is not one they will\n * choose, so three tenders now carry none:\n *\n * - cash: no payment service touches it, and the merchant keeps all of it;\n * - gift card: the fee was taken when the card was SOLD, so charging it again\n * when the card is spent would charge the same money twice;\n * - store credit (`credit`): rewards or referral value the merchant gave\n * away, so no money changes hands and an invoiced fee on it would only\n * penalize running a rewards program.\n *\n * Everything else still carries it. Card payments net it from their payout,\n * as always. A room charge (`folio`) is new revenue booked to a stay, so its\n * share goes on the invoice.\n *\n * One predicate, so the register, the completion, the offline sync and the\n * docs cannot disagree about which tender is which.\n *=========================================*/\n\n/** The tenders that carry no platform fee (AGL-4036). */\nexport const POS_FEE_FREE_METHODS: ReadonlySet<OrderPaymentMethod> = new Set([\n 'cash',\n 'gift_card',\n 'credit',\n])\n\n/** Whether a payment by `method` carries its share of the platform's take. */\nexport function paymentCarriesPlatformFee(method: OrderPaymentMethod): boolean {\n return !POS_FEE_FREE_METHODS.has(method)\n}\n\n/**\n * The take a sale owes when ONE tender pays all of it: the whole take, or\n * nothing for a fee-free tender.\n */\nexport function posTenderTakeCents(\n method: OrderPaymentMethod,\n takeFeeCents: number,\n): number {\n return paymentCarriesPlatformFee(method) ? wholeCents(takeFeeCents) : 0\n}\n\n/** Whole non-negative cents from an untrusted value. */\nfunction wholeCents(value: unknown): number {\n const number = Math.round(Number(value))\n return Number.isFinite(number) && number > 0 ? number : 0\n}\n\n/**\n * The order's payments, read through one door.\n *\n * Orders written before the ledger existed carry no `payments`, so one is\n * INFERRED from the fields the single-tender register wrote: a folio sale has\n * `reservationId`, a QR sale has `checkoutSessionId`, and anything else on the\n * POS channel was cash. A pending legacy sale is the QR link still waiting.\n * Online orders paid through Stripe Checkout read as one `card_link` payment.\n * Nothing is written back: this is a reader, never a migration.\n */\nexport function orderPayments(order: {\n payments?: OrderPayment[] | null\n channel?: string\n status?: string\n totals?: { totalCents?: number; tipCents?: number } | null\n amountCents?: number\n reservationId?: string\n checkoutSessionId?: string\n paymentIntentId?: string\n createdAtMs?: number\n}): OrderPayment[] {\n if (Array.isArray(order?.payments)) {\n return order.payments.filter(\n (payment): payment is OrderPayment =>\n Boolean(payment) && typeof payment === 'object' && Boolean(payment.id),\n )\n }\n const totalCents = wholeCents(order?.totals?.totalCents ?? order?.amountCents)\n if (!(totalCents > 0)) return []\n const settled = order?.status !== 'pending' && order?.status !== 'cancelled'\n const atMs = Number(order?.createdAtMs) || 0\n const method: OrderPaymentMethod = order?.reservationId\n ? 'folio'\n : order?.checkoutSessionId || order?.paymentIntentId || order?.channel !== 'pos'\n ? 'card_link'\n : 'cash'\n return [\n {\n id: 'legacy',\n method,\n amountCents: totalCents,\n status: settled ? 'succeeded' : order?.status === 'cancelled' ? 'canceled' : 'pending',\n atMs,\n ...(order?.paymentIntentId ? { paymentIntentId: order.paymentIntentId } : {}),\n ...(order?.checkoutSessionId ? { checkoutSessionId: order.checkoutSessionId } : {}),\n ...(order?.reservationId ? { reservationId: order.reservationId } : {}),\n },\n ]\n}\n\n/** Cents put toward the sale by payments that succeeded. */\nexport function posSettledCents(payments: readonly OrderPayment[]): number {\n return payments\n .filter((payment) => payment.status === 'succeeded')\n .reduce((sum, payment) => sum + wholeCents(payment.amountCents), 0)\n}\n\n/**\n * Cents spoken for: succeeded payments plus the ones still in flight. A\n * reader waiting for a tap has claimed its share of the balance, so a second\n * tender cannot be started for the same money.\n */\nexport function posReservedCents(payments: readonly OrderPayment[]): number {\n return payments\n .filter((payment) => payment.status === 'succeeded' || payment.status === 'pending')\n .reduce((sum, payment) => sum + wholeCents(payment.amountCents), 0)\n}\n\n/** What the customer still owes, by settled payments. */\nexport function posBalanceDueCents(\n totalCents: number,\n payments: readonly OrderPayment[],\n): number {\n return Math.max(0, wholeCents(totalCents) - posSettledCents(payments))\n}\n\n/** What a NEW payment may still take: the balance less what is in flight. */\nexport function posTenderableCents(\n totalCents: number,\n payments: readonly OrderPayment[],\n): number {\n return Math.max(0, wholeCents(totalCents) - posReservedCents(payments))\n}\n\n/** Tips taken across the sale's settled payments. */\nexport function posTipCents(payments: readonly OrderPayment[]): number {\n return payments\n .filter((payment) => payment.status === 'succeeded')\n .reduce((sum, payment) => sum + wholeCents(payment.tipCents), 0)\n}\n\n/**\n * The share of the sale's take a payment of `amountCents` carries.\n *\n * Proportional to the part of the SALE it pays, never to the tip. Card\n * payments carry their share in `application_fee_amount`; whatever the card\n * shares leave is accrued to the invoice at completion\n * ({@link posInvoiceTakeCents}), so the take is exact however the sale was\n * split and however each share rounded.\n */\nexport function posTakeShareCents(input: {\n takeFeeCents: number\n totalCents: number\n amountCents: number\n}): number {\n const take = wholeCents(input.takeFeeCents)\n const total = wholeCents(input.totalCents)\n const amount = Math.min(wholeCents(input.amountCents), total)\n if (!(take > 0) || !(total > 0) || !(amount > 0)) return 0\n return Math.min(take, Math.round((take * amount) / total))\n}\n\n/**\n * {@link posTakeShareCents} for a payment by `method`: its share of the take,\n * or 0 for a tender that carries no platform fee (AGL-4036).\n */\nexport function posPaymentTakeShareCents(input: {\n method: OrderPaymentMethod\n takeFeeCents: number\n totalCents: number\n amountCents: number\n}): number {\n if (!paymentCarriesPlatformFee(input.method)) return 0\n return posTakeShareCents(input)\n}\n\n/**\n * The part of the sale's take its tenders owe (AGL-4036): the whole take when\n * every settled payment carries the fee, and otherwise the share for the part\n * of the sale the fee-carrying payments paid. Cash and gift-card parts owe\n * nothing, so a sale paid all in cash owes 0.\n *\n * Worked from the payments' METHODS, not from the `takeFeeCents` each one\n * recorded, so a sale that was opened before this rule and finished after it\n * is charged by the rule it finished under.\n */\nexport function posOwedTakeCents(input: {\n takeFeeCents: number\n totalCents: number\n payments: readonly OrderPayment[]\n}): number {\n const take = wholeCents(input.takeFeeCents)\n const settled = input.payments.filter((payment) => payment.status === 'succeeded')\n if (settled.every((payment) => paymentCarriesPlatformFee(payment.method))) return take\n const carryingCents = settled\n .filter((payment) => paymentCarriesPlatformFee(payment.method))\n .reduce((sum, payment) => sum + wholeCents(payment.amountCents), 0)\n return posTakeShareCents({\n takeFeeCents: take,\n totalCents: input.totalCents,\n amountCents: carryingCents,\n })\n}\n\n/**\n * What of the sale's take was NOT netted from a card payout, and therefore\n * goes on the merchant's invoice: the take the tenders owe\n * ({@link posOwedTakeCents}) less every succeeded card payment's share. Never\n * negative, and 0 for a sale paid only by card, cash, gift card and store credit.\n */\nexport function posInvoiceTakeCents(input: {\n takeFeeCents: number\n totalCents: number\n payments: readonly OrderPayment[]\n}): number {\n const netted = input.payments\n .filter(\n (payment) =>\n payment.status === 'succeeded' && isCardPaymentMethod(payment.method),\n )\n .reduce((sum, payment) => sum + wholeCents(payment.takeFeeCents), 0)\n return Math.max(0, posOwedTakeCents(input) - netted)\n}\n\n/**\n * How the sale's fee reaches the platform, from the payments that carried it.\n * A cash, gift-card or store-credit part carries none (AGL-4036), so a card + cash sale is\n * `payout`, not `split`.\n */\nexport function posFeeCollection(\n payments: readonly OrderPayment[],\n): 'payout' | 'invoice' | 'split' {\n const settled = payments.filter(\n (payment) =>\n payment.status === 'succeeded' && paymentCarriesPlatformFee(payment.method),\n )\n const card = settled.some((payment) => isCardPaymentMethod(payment.method))\n const other = settled.some((payment) => !isCardPaymentMethod(payment.method))\n if (card && other) return 'split'\n return card ? 'payout' : 'invoice'\n}\n\n/** A one-line description of a payment for the register, receipt and timeline. */\nexport function describeOrderPayment(payment: OrderPayment): string {\n const dollars = (cents: number) => `$${(cents / 100).toFixed(2)}`\n const label =\n (payment.method === 'credit' && payment.creditLabel) || ORDER_PAYMENT_METHOD_LABELS[payment.method] || 'Payment'\n const card =\n payment.cardBrand || payment.last4\n ? ` (${[payment.cardBrand, payment.last4 ? `•••• ${payment.last4}` : '']\n .filter(Boolean)\n .join(' ')})`\n : ''\n const tip = wholeCents(payment.tipCents)\n ? ` + ${dollars(wholeCents(payment.tipCents))} tip`\n : ''\n return `${label}${card} ${dollars(wholeCents(payment.amountCents))}${tip}`\n}\n\nexport const ORDER_PAYMENT_METHOD_LABELS: Record<OrderPaymentMethod, string> = {\n cash: 'Cash',\n card_present: 'Card reader',\n card_keyed: 'Typed card',\n card_link: 'Card (QR)',\n gift_card: 'Gift card',\n folio: 'Charged to room',\n credit: 'Store credit',\n}\n\n/*==========================================\n * TIPS (AGL-3607).\n *=========================================*/\n\n/** The presets a merchant starts with. */\nexport const POS_TIP_PERCENTAGES_DEFAULT: readonly number[] = [15, 18, 20, 25]\n\n/**\n * The merchant's tip presets from the plugin setting, a comma list such as\n * `15, 18, 20, 25`. Whole percentages from 1 to 100, at most four, in the order\n * given; anything unreadable falls back to the defaults rather than showing\n * the customer an empty choice.\n */\nexport function posTipPercentages(raw: unknown): number[] {\n const parsed = String(raw ?? '')\n .split(/[,\\s]+/)\n .map((part) => Math.round(Number(part)))\n .filter((value) => Number.isFinite(value) && value >= 1 && value <= 100)\n const unique = [...new Set(parsed)].slice(0, 4)\n return unique.length ? unique : [...POS_TIP_PERCENTAGES_DEFAULT]\n}\n\n/** A percentage tip on `baseCents`, rounded to the cent. */\nexport function posTipFromPercent(baseCents: number, percent: number): number {\n const base = wholeCents(baseCents)\n const pct = Number(percent)\n if (!(base > 0) || !Number.isFinite(pct) || pct <= 0) return 0\n return Math.round((base * pct) / 100)\n}\n\n/** The largest tip the register accepts on one payment: the payment itself. */\nexport function posTipProblem(tipCents: number, amountCents: number): string | null {\n if (!Number.isInteger(tipCents) || tipCents < 0) return 'Enter a tip of $0 or more.'\n if (tipCents > wholeCents(amountCents)) {\n return 'A tip can be at most the amount being paid. Check the amount and try again.'\n }\n return null\n}\n\n/*==========================================\n * THE CUSTOMER DISPLAY (AGL-3608).\n *\n * A paired screen facing the customer reads one document per register,\n * `hosts/{hostId}/registers/{registerId}/display/state`, through the\n * `commerce/pos-display` route and its display token. The register writes\n * the prompt; the customer's answer comes back in `response`.\n *=========================================*/\n\nexport type PosDisplayMode =\n | 'idle'\n /** The live basket, mirrored as the cashier builds it. */\n | 'cart'\n /** The customer picks a tip. */\n | 'tip'\n /** The customer picks how they want their receipt. */\n | 'receipt'\n /** A card payment is waiting on the reader. */\n | 'processing'\n | 'thanks'\n /**\n * Another plugin's screen (AGL-4042), asked for through core's customer\n * display seam (`plugin-customer-display`): the display opens that\n * plugin's public page, which sends it back here when the customer is done.\n */\n | 'screen'\n\nexport type PosReceiptChannel = 'email' | 'sms' | 'print' | 'none'\n\nexport interface PosDisplayLine {\n name: string\n variantLabel?: string\n quantity: number\n amountCents: number\n}\n\nexport interface PosDisplayCart {\n lines: PosDisplayLine[]\n itemsCents: number\n discountCents: number\n taxCents: number\n totalCents: number\n /** Taken so far, and what is still due, once payments start. */\n paidCents?: number\n dueCents?: number\n tipCents?: number\n}\n\n/** The customer's answer to the current prompt. */\nexport interface PosDisplayResponse {\n /** The prompt this answers; a stale answer is refused. */\n promptId: string\n tipCents?: number\n tipPercent?: number\n /** `none` when the customer chose no tip. */\n tipChoice?: 'percent' | 'custom' | 'none'\n receiptChannel?: PosReceiptChannel\n email?: string\n phone?: string\n marketingOptIn?: boolean\n atMs: number\n}\n\nexport interface PosDisplayState {\n mode: PosDisplayMode\n updatedAtMs: number\n /**\n * The ISO currency every amount on the screen is in (`usd`). Stamped by\n * the server from the store's own currency, never taken from the register.\n */\n currency?: string\n /** Changes every time the register asks something new. */\n promptId?: string\n cart?: PosDisplayCart\n tip?: {\n /** What a percentage is taken of: the amount being paid now. */\n baseCents: number\n percentages: number[]\n allowCustom: boolean\n }\n receipt?: {\n channels: PosReceiptChannel[]\n /** Whether to offer the store's marketing opt-in under the email field. */\n offerMarketing: boolean\n }\n processing?: { message: string }\n /** The plugin page a `screen` state opens, as core's seam shapes it. */\n screen?: PosDisplayScreen\n /**\n * The staff member (uid) who put {@link screen} on the display (AGL-4042),\n * stamped by the server from the session that pushed it, never taken from\n * the register's body. Core's display verifier names them to the screen's\n * plugin, whose record of what the customer did there names who showed it.\n * Never sent to the display.\n */\n screenShownBy?: string\n response?: PosDisplayResponse\n}\n\n/**\n * A plugin's screen on the display (AGL-4042): the shape of core's\n * `CustomerDisplayScreen`, restated so this module imports nothing. The\n * server checks it with core's own sanitizer before it is stored.\n */\nexport interface PosDisplayScreen {\n pluginId: string\n path: string\n params: Record<string, string>\n}\n\n/** Most lines a display state carries; a longer basket shows its newest. */\nexport const POS_DISPLAY_MAX_LINES = 60\n\n/** A pairing code lives this long before the register must show a new one. */\nexport const POS_DISPLAY_PAIRING_TTL_MS = 10 * 60 * 1000\n\n/** A thank-you screen returns to idle on the display after this long. */\nexport const POS_DISPLAY_THANKS_MS = 8_000\n\nconst EMAIL_PATTERN = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\n/** An email address as the display may store it, or '' when it is not one. */\nexport function posDisplayEmail(value: unknown): string {\n const email = String(value ?? '').trim().toLowerCase().slice(0, 254)\n return EMAIL_PATTERN.test(email) ? email : ''\n}\n\n/** A phone number as digits with an optional leading +, or '' when too short. */\nexport function posDisplayPhone(value: unknown): string {\n const raw = String(value ?? '').trim()\n const digits = raw.replace(/[^0-9]/g, '')\n if (digits.length < 7 || digits.length > 15) return ''\n return `${raw.startsWith('+') ? '+' : ''}${digits}`\n}\n\n/**\n * The display state as the REGISTER may send it: clamped, typed and stripped\n * of anything the customer screen has no business holding. The register is\n * staff and trusted for intent, not for shape.\n */\nexport function sanitizePosDisplayState(\n raw: unknown,\n nowMs: number,\n options: {\n /**\n * Checks a `screen` state's plugin page (core's\n * `sanitizeCustomerDisplayScreen`). Without it, or when it refuses, a\n * `screen` state is idle: nothing opens on the display unchecked.\n */\n screen?: (raw: unknown) => PosDisplayScreen | null\n } = {},\n): PosDisplayState {\n const input = (raw && typeof raw === 'object' ? raw : {}) as Record<string, any>\n const modes: PosDisplayMode[] = ['idle', 'cart', 'tip', 'receipt', 'processing', 'thanks', 'screen']\n const asked = modes.includes(input['mode']) ? (input['mode'] as PosDisplayMode) : 'idle'\n const promptId = String(input['promptId'] ?? '').slice(0, 64)\n // A screen opens once per prompt, so one without a prompt, or one the\n // checker refuses, opens nothing.\n const screen = asked === 'screen' && promptId && options.screen ? options.screen(input['screen']) : null\n const mode: PosDisplayMode = asked === 'screen' && !screen ? 'idle' : asked\n const state: PosDisplayState = { mode, updatedAtMs: nowMs }\n if (promptId) state.promptId = promptId\n if (mode === 'screen' && screen) state.screen = screen\n const cart = input['cart']\n if (cart && typeof cart === 'object' && mode !== 'idle') {\n const lines = (Array.isArray(cart.lines) ? cart.lines : [])\n .slice(-POS_DISPLAY_MAX_LINES)\n .map((line: any) => ({\n name: String(line?.name ?? '').slice(0, 120),\n ...(line?.variantLabel\n ? { variantLabel: String(line.variantLabel).slice(0, 80) }\n : {}),\n quantity: Math.max(1, Math.min(999, Math.round(Number(line?.quantity) || 1))),\n amountCents: wholeCents(line?.amountCents),\n }))\n state.cart = {\n lines,\n itemsCents: wholeCents(cart.itemsCents),\n discountCents: wholeCents(cart.discountCents),\n taxCents: wholeCents(cart.taxCents),\n totalCents: wholeCents(cart.totalCents),\n ...(cart.paidCents != null ? { paidCents: wholeCents(cart.paidCents) } : {}),\n ...(cart.dueCents != null ? { dueCents: wholeCents(cart.dueCents) } : {}),\n ...(cart.tipCents != null ? { tipCents: wholeCents(cart.tipCents) } : {}),\n }\n }\n if (mode === 'tip') {\n const tip = input['tip'] ?? {}\n state.tip = {\n baseCents: wholeCents(tip.baseCents),\n percentages: posTipPercentages(\n Array.isArray(tip.percentages) ? tip.percentages.join(',') : tip.percentages,\n ),\n allowCustom: tip.allowCustom !== false,\n }\n }\n if (mode === 'receipt') {\n const receipt = input['receipt'] ?? {}\n const allowed: PosReceiptChannel[] = ['email', 'sms', 'print', 'none']\n const channels = (Array.isArray(receipt.channels) ? receipt.channels : [])\n .filter((channel: unknown): channel is PosReceiptChannel =>\n allowed.includes(channel as PosReceiptChannel),\n )\n state.receipt = {\n channels: channels.length ? [...new Set<PosReceiptChannel>(channels)] : ['print', 'none'],\n offerMarketing: receipt.offerMarketing === true,\n }\n }\n if (mode === 'processing') {\n state.processing = {\n message:\n String(input['processing']?.message ?? '').slice(0, 160) ||\n 'Tap, insert or swipe your card on the reader.',\n }\n }\n return state\n}\n\n/**\n * The customer's answer as the DISPLAY may send it, checked against the\n * prompt it claims to answer. Returns null when it answers nothing the\n * register asked: a stale screen, a replay, or a mode that takes no answer.\n */\nexport function sanitizePosDisplayResponse(\n state: PosDisplayState | null | undefined,\n raw: unknown,\n nowMs: number,\n): PosDisplayResponse | null {\n if (!state || !state.promptId) return null\n const input = (raw && typeof raw === 'object' ? raw : {}) as Record<string, any>\n if (String(input['promptId'] ?? '') !== state.promptId) return null\n const response: PosDisplayResponse = { promptId: state.promptId, atMs: nowMs }\n if (state.mode === 'tip' && state.tip) {\n const choice = input['tipChoice']\n if (choice === 'none') {\n response.tipChoice = 'none'\n response.tipCents = 0\n } else if (choice === 'percent') {\n const percent = Math.round(Number(input['tipPercent']))\n if (!state.tip.percentages.includes(percent)) return null\n response.tipChoice = 'percent'\n response.tipPercent = percent\n response.tipCents = posTipFromPercent(state.tip.baseCents, percent)\n } else if (choice === 'custom' && state.tip.allowCustom) {\n const tipCents = Math.round(Number(input['tipCents']))\n if (posTipProblem(tipCents, state.tip.baseCents)) return null\n response.tipChoice = 'custom'\n response.tipCents = tipCents\n } else {\n return null\n }\n return response\n }\n if (state.mode === 'receipt' && state.receipt) {\n const channel = input['receiptChannel'] as PosReceiptChannel\n if (!state.receipt.channels.includes(channel)) return null\n response.receiptChannel = channel\n if (channel === 'email') {\n const email = posDisplayEmail(input['email'])\n if (!email) return null\n response.email = email\n if (state.receipt.offerMarketing && input['marketingOptIn'] === true) {\n response.marketingOptIn = true\n }\n }\n if (channel === 'sms') {\n const phone = posDisplayPhone(input['phone'])\n if (!phone) return null\n response.phone = phone\n }\n return response\n }\n return null\n}\n\n/**\n * The state as the DISPLAY reads it. The customer's own typed address is\n * never echoed back to the screen: once answered, the prompt is done and the\n * screen shows what comes next, so nothing a later customer could read off\n * an unattended tablet is ever sent to it.\n */\nexport function posDisplayPublicState(\n state: PosDisplayState | null | undefined,\n nowMs: number,\n): Omit<PosDisplayState, 'response' | 'screenShownBy'> & { answered: boolean } {\n if (!state) return { mode: 'idle', updatedAtMs: nowMs, answered: false }\n // Who showed a plugin's screen is the server's to know, not the display's.\n const { response, screenShownBy: _shownBy, ...rest } = state\n if (rest.mode === 'thanks' && nowMs - rest.updatedAtMs > POS_DISPLAY_THANKS_MS) {\n return { mode: 'idle', updatedAtMs: rest.updatedAtMs, answered: false }\n }\n return {\n ...rest,\n answered: Boolean(response && response.promptId === rest.promptId),\n }\n}\n"],"names":["ORDER_PAYMENT_METHODS","POS_CARD_METHODS","Set","isCardPaymentMethod","method","has","POS_FEE_FREE_METHODS","paymentCarriesPlatformFee","posTenderTakeCents","takeFeeCents","wholeCents","value","number","Math","round","Number","isFinite","orderPayments","order","Array","isArray","payments","filter","payment","Boolean","id","totalCents","totals","amountCents","settled","status","atMs","createdAtMs","reservationId","checkoutSessionId","paymentIntentId","channel","posSettledCents","reduce","sum","posReservedCents","posBalanceDueCents","max","posTenderableCents","posTipCents","tipCents","posTakeShareCents","input","take","total","amount","min","posPaymentTakeShareCents","posOwedTakeCents","every","carryingCents","posInvoiceTakeCents","netted","posFeeCollection","card","some","other","describeOrderPayment","dollars","cents","toFixed","label","creditLabel","ORDER_PAYMENT_METHOD_LABELS","cardBrand","last4","join","tip","cash","card_present","card_keyed","card_link","gift_card","folio","credit","POS_TIP_PERCENTAGES_DEFAULT","posTipPercentages","raw","parsed","String","split","map","part","unique","slice","length","posTipFromPercent","baseCents","percent","base","pct","posTipProblem","isInteger","POS_DISPLAY_MAX_LINES","POS_DISPLAY_PAIRING_TTL_MS","POS_DISPLAY_THANKS_MS","EMAIL_PATTERN","posDisplayEmail","email","trim","toLowerCase","test","posDisplayPhone","digits","replace","startsWith","sanitizePosDisplayState","nowMs","options","modes","asked","includes","promptId","screen","mode","state","updatedAtMs","cart","lines","line","name","variantLabel","quantity","itemsCents","discountCents","taxCents","paidCents","dueCents","percentages","allowCustom","receipt","allowed","channels","offerMarketing","processing","message","sanitizePosDisplayResponse","response","choice","tipChoice","tipPercent","receiptChannel","marketingOptIn","phone","posDisplayPublicState","answered","screenShownBy","_shownBy","rest"],"mappings":"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"}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
17
|
+
import { withPosHardwareReceiptNotice } from "./pos-hardware-terms.js";
|
|
17
18
|
/** A money amount for print: `$12.50`, `-€3.00`. Locale is fixed so receipts read the same everywhere. */ export function formatReceiptMoney(cents, currency) {
|
|
18
19
|
const code = String(currency || 'usd').toUpperCase();
|
|
19
20
|
const safe = Number.isFinite(cents) ? Math.round(cents) : 0;
|
|
@@ -168,8 +169,8 @@ const whole = (value)=>{
|
|
|
168
169
|
changeCents
|
|
169
170
|
} : {}, whole(order.refundedCents) ? {
|
|
170
171
|
refundedCents: whole(order.refundedCents)
|
|
171
|
-
} : {}, context.footer ? {
|
|
172
|
-
footer: context.footer
|
|
172
|
+
} : {}, withPosHardwareReceiptNotice(context.footer, order) ? {
|
|
173
|
+
footer: withPosHardwareReceiptNotice(context.footer, order)
|
|
173
174
|
} : {}, {
|
|
174
175
|
barcode: orderNumber
|
|
175
176
|
}, context.banner ? {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-receipt.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 { HostOrder } from './commerce-orders'\n\n/**\n * A receipt as DATA (AGL-3619), shared by every surface that prints one: the\n * cloud printer renderers (Star bytes, Epson ePOS-Print XML, plain text) and\n * the 80mm thermal receipt the register shows and prints from the browser\n * (AGL-3609). One model, so a reprint from the console, an auto-print on a\n * sale and the on-screen receipt cannot disagree about what was sold.\n *\n * Amounts are integer minor units in `currency`, and every label is already\n * the words to print: the renderers lay text out, they do not decide it.\n */\nexport interface ReceiptLine {\n name: string\n /** A variant label or SKU, printed under the name. */\n detail?: string\n quantity: number\n unitCents: number\n totalCents: number\n}\n\nexport interface ReceiptTender {\n /** e.g. `Cash`, `Visa •••• 4242`, `Gift card`. */\n label: string\n amountCents: number\n}\n\nexport interface ReceiptData {\n storeName: string\n /** Address, phone or website lines under the store name. */\n storeLines?: string[]\n /** The printable order number, e.g. `1042`. */\n orderNumber: string\n orderId?: string\n createdAtMs: number\n /** IANA zone the timestamp is printed in; UTC when absent. */\n timeZone?: string\n registerName?: string\n cashierName?: string\n /** ISO 4217, any case. */\n currency: string\n lines: ReceiptLine[]\n subtotalCents: number\n discountCents?: number\n shippingCents?: number\n taxCents?: number\n tipCents?: number\n totalCents: number\n tenders?: ReceiptTender[]\n changeCents?: number\n refundedCents?: number\n /** Printed small at the bottom: a returns policy, a thank-you. */\n footer?: string\n /** Code128 under the totals so a return can scan the order back up. Defaults to the order number. */\n barcode?: string\n /** `REPRINT` / `COPY` banner. */\n banner?: string\n}\n\n/** A money amount for print: `$12.50`, `-€3.00`. Locale is fixed so receipts read the same everywhere. */\nexport function formatReceiptMoney(cents: number, currency: string): string {\n const code = String(currency || 'usd').toUpperCase()\n const safe = Number.isFinite(cents) ? Math.round(cents) : 0\n try {\n const digits = new Intl.NumberFormat('en-US', { style: 'currency', currency: code })\n .resolvedOptions().maximumFractionDigits ?? 2\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: code,\n minimumFractionDigits: digits,\n maximumFractionDigits: digits,\n }).format(safe / 10 ** digits)\n } catch {\n return `${(safe / 100).toFixed(2)} ${code}`\n }\n}\n\n/** The receipt timestamp, e.g. `Oct 6, 2026, 3:04 PM`. */\nexport function formatReceiptTime(atMs: number, timeZone?: string): string {\n const options: Intl.DateTimeFormatOptions = {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n }\n try {\n return new Intl.DateTimeFormat('en-US', { ...options, timeZone: timeZone || 'UTC' }).format(\n new Date(atMs),\n )\n } catch {\n return new Intl.DateTimeFormat('en-US', { ...options, timeZone: 'UTC' }).format(new Date(atMs))\n }\n}\n\nconst PAYMENT_METHOD_LABELS: Record<string, string> = {\n cash: 'Cash',\n card_present: 'Card',\n card_keyed: 'Card',\n card_link: 'Card',\n gift_card: 'Gift card',\n folio: 'Charged to room',\n}\n\nconst whole = (value: unknown): number => {\n const number = Math.round(Number(value))\n return Number.isFinite(number) && number > 0 ? number : 0\n}\n\n/**\n * The tenders an order was paid with: the register's payment ledger\n * (`payments[]`, AGL-3607) when the order has one, otherwise a single tender\n * read from the order itself (a cash sale's `changeCents`, a QR card sale).\n */\nexport function receiptTendersFromOrder(order: HostOrder & Record<string, any>): {\n tenders: ReceiptTender[]\n changeCents: number\n tipCents: number\n} {\n const payments: any[] = Array.isArray(order['payments']) ? order['payments'] : []\n if (payments.length) {\n let changeCents = 0\n let tipCents = 0\n const tenders = payments\n .filter((payment) => payment && payment.status === 'succeeded')\n .map((payment) => {\n changeCents += whole(payment.changeCents)\n tipCents += whole(payment.tipCents)\n const card = [payment.cardBrand, payment.last4 ? `**** ${payment.last4}` : '']\n .filter(Boolean)\n .join(' ')\n const label = card || PAYMENT_METHOD_LABELS[String(payment.method)] || 'Payment'\n const tendered = whole(payment.cashTenderedCents)\n return {\n label,\n amountCents: tendered || whole(payment.amountCents) + whole(payment.tipCents),\n }\n })\n return { tenders, changeCents, tipCents }\n }\n const totalCents = whole(order.totals?.totalCents)\n const changeCents = whole(order['changeCents'])\n if (!totalCents) return { tenders: [], changeCents: 0, tipCents: 0 }\n const isCash = order.channel === 'pos' && !order.checkoutSessionId && !order.paymentIntentId\n return {\n tenders: [\n {\n label: order['reservationId'] ? 'Charged to room' : isCash ? 'Cash' : 'Card',\n amountCents: totalCents + (isCash ? changeCents : 0),\n },\n ],\n changeCents: isCash ? changeCents : 0,\n tipCents: 0,\n }\n}\n\nexport interface ReceiptContext {\n storeName: string\n storeLines?: string[]\n registerName?: string\n cashierName?: string\n timeZone?: string\n currency?: string\n footer?: string\n banner?: string\n}\n\n/** The receipt an order prints, from the order as stored. */\nexport function receiptDataFromOrder(\n orderId: string,\n order: HostOrder & Record<string, any>,\n context: ReceiptContext,\n): ReceiptData {\n const lines: ReceiptLine[] = (order.lineItems ?? []).map((line) => {\n const quantity = Math.max(1, Math.round(Number(line.quantity) || 1))\n const unitCents = Math.round(Number(line.unitAmountCents) || 0)\n return {\n name: String(line.name ?? 'Item'),\n ...(line.variantLabel ? { detail: String(line.variantLabel) } : {}),\n quantity,\n unitCents,\n totalCents: unitCents * quantity,\n }\n })\n const totals = order.totals\n const subtotalCents = totals?.itemsCents ?? lines.reduce((sum, line) => sum + line.totalCents, 0)\n const { tenders, changeCents, tipCents } = receiptTendersFromOrder(order)\n const tip = whole((totals as any)?.tipCents) || tipCents\n const orderNumber = order.number ? String(order.number) : orderId.slice(0, 8).toUpperCase()\n return {\n storeName: context.storeName,\n ...(context.storeLines?.length ? { storeLines: context.storeLines } : {}),\n orderNumber,\n orderId,\n createdAtMs: Number(order['createdAtMs']) || Date.now(),\n ...(context.timeZone ? { timeZone: context.timeZone } : {}),\n ...(context.registerName ? { registerName: context.registerName } : {}),\n ...(context.cashierName ? { cashierName: context.cashierName } : {}),\n currency: String(order['currency'] ?? context.currency ?? 'usd'),\n lines,\n subtotalCents,\n ...(totals?.discountCents ? { discountCents: totals.discountCents } : {}),\n ...(totals?.shippingCents ? { shippingCents: totals.shippingCents } : {}),\n ...(totals?.taxCents ? { taxCents: totals.taxCents } : {}),\n ...(tip ? { tipCents: tip } : {}),\n totalCents: totals?.totalCents ?? subtotalCents,\n ...(tenders.length ? { tenders } : {}),\n ...(changeCents ? { changeCents } : {}),\n ...(whole(order.refundedCents) ? { refundedCents: whole(order.refundedCents) } : {}),\n ...(context.footer ? { footer: context.footer } : {}),\n barcode: orderNumber,\n ...(context.banner ? { banner: context.banner } : {}),\n }\n}\n"],"names":["formatReceiptMoney","cents","currency","code","String","toUpperCase","safe","Number","isFinite","Math","round","digits","Intl","NumberFormat","style","resolvedOptions","maximumFractionDigits","minimumFractionDigits","format","toFixed","formatReceiptTime","atMs","timeZone","options","year","month","day","hour","minute","DateTimeFormat","Date","PAYMENT_METHOD_LABELS","cash","card_present","card_keyed","card_link","gift_card","folio","whole","value","number","receiptTendersFromOrder","order","payments","Array","isArray","length","changeCents","tipCents","tenders","filter","payment","status","map","card","cardBrand","last4","Boolean","join","label","method","tendered","cashTenderedCents","amountCents","totalCents","totals","isCash","channel","checkoutSessionId","paymentIntentId","receiptDataFromOrder","orderId","context","lines","lineItems","line","quantity","max","unitCents","unitAmountCents","name","variantLabel","detail","subtotalCents","itemsCents","reduce","sum","tip","orderNumber","slice","storeName","storeLines","createdAtMs","now","registerName","cashierName","discountCents","shippingCents","taxCents","refundedCents","footer","barcode","banner"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-receipt.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 { HostOrder } from './commerce-orders'\nimport { withPosHardwareReceiptNotice } from './pos-hardware-terms'\n\n/**\n * A receipt as DATA (AGL-3619), shared by every surface that prints one: the\n * cloud printer renderers (Star bytes, Epson ePOS-Print XML, plain text) and\n * the 80mm thermal receipt the register shows and prints from the browser\n * (AGL-3609). One model, so a reprint from the console, an auto-print on a\n * sale and the on-screen receipt cannot disagree about what was sold.\n *\n * Amounts are integer minor units in `currency`, and every label is already\n * the words to print: the renderers lay text out, they do not decide it.\n */\nexport interface ReceiptLine {\n name: string\n /** A variant label or SKU, printed under the name. */\n detail?: string\n quantity: number\n unitCents: number\n totalCents: number\n}\n\nexport interface ReceiptTender {\n /** e.g. `Cash`, `Visa •••• 4242`, `Gift card`. */\n label: string\n amountCents: number\n}\n\nexport interface ReceiptData {\n storeName: string\n /** Address, phone or website lines under the store name. */\n storeLines?: string[]\n /** The printable order number, e.g. `1042`. */\n orderNumber: string\n orderId?: string\n createdAtMs: number\n /** IANA zone the timestamp is printed in; UTC when absent. */\n timeZone?: string\n registerName?: string\n cashierName?: string\n /** ISO 4217, any case. */\n currency: string\n lines: ReceiptLine[]\n subtotalCents: number\n discountCents?: number\n shippingCents?: number\n taxCents?: number\n tipCents?: number\n totalCents: number\n tenders?: ReceiptTender[]\n changeCents?: number\n refundedCents?: number\n /** Printed small at the bottom: a returns policy, a thank-you. */\n footer?: string\n /** Code128 under the totals so a return can scan the order back up. Defaults to the order number. */\n barcode?: string\n /** `REPRINT` / `COPY` banner. */\n banner?: string\n}\n\n/** A money amount for print: `$12.50`, `-€3.00`. Locale is fixed so receipts read the same everywhere. */\nexport function formatReceiptMoney(cents: number, currency: string): string {\n const code = String(currency || 'usd').toUpperCase()\n const safe = Number.isFinite(cents) ? Math.round(cents) : 0\n try {\n const digits = new Intl.NumberFormat('en-US', { style: 'currency', currency: code })\n .resolvedOptions().maximumFractionDigits ?? 2\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: code,\n minimumFractionDigits: digits,\n maximumFractionDigits: digits,\n }).format(safe / 10 ** digits)\n } catch {\n return `${(safe / 100).toFixed(2)} ${code}`\n }\n}\n\n/** The receipt timestamp, e.g. `Oct 6, 2026, 3:04 PM`. */\nexport function formatReceiptTime(atMs: number, timeZone?: string): string {\n const options: Intl.DateTimeFormatOptions = {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n }\n try {\n return new Intl.DateTimeFormat('en-US', { ...options, timeZone: timeZone || 'UTC' }).format(\n new Date(atMs),\n )\n } catch {\n return new Intl.DateTimeFormat('en-US', { ...options, timeZone: 'UTC' }).format(new Date(atMs))\n }\n}\n\nconst PAYMENT_METHOD_LABELS: Record<string, string> = {\n cash: 'Cash',\n card_present: 'Card',\n card_keyed: 'Card',\n card_link: 'Card',\n gift_card: 'Gift card',\n folio: 'Charged to room',\n}\n\nconst whole = (value: unknown): number => {\n const number = Math.round(Number(value))\n return Number.isFinite(number) && number > 0 ? number : 0\n}\n\n/**\n * The tenders an order was paid with: the register's payment ledger\n * (`payments[]`, AGL-3607) when the order has one, otherwise a single tender\n * read from the order itself (a cash sale's `changeCents`, a QR card sale).\n */\nexport function receiptTendersFromOrder(order: HostOrder & Record<string, any>): {\n tenders: ReceiptTender[]\n changeCents: number\n tipCents: number\n} {\n const payments: any[] = Array.isArray(order['payments']) ? order['payments'] : []\n if (payments.length) {\n let changeCents = 0\n let tipCents = 0\n const tenders = payments\n .filter((payment) => payment && payment.status === 'succeeded')\n .map((payment) => {\n changeCents += whole(payment.changeCents)\n tipCents += whole(payment.tipCents)\n const card = [payment.cardBrand, payment.last4 ? `**** ${payment.last4}` : '']\n .filter(Boolean)\n .join(' ')\n const label = card || PAYMENT_METHOD_LABELS[String(payment.method)] || 'Payment'\n const tendered = whole(payment.cashTenderedCents)\n return {\n label,\n amountCents: tendered || whole(payment.amountCents) + whole(payment.tipCents),\n }\n })\n return { tenders, changeCents, tipCents }\n }\n const totalCents = whole(order.totals?.totalCents)\n const changeCents = whole(order['changeCents'])\n if (!totalCents) return { tenders: [], changeCents: 0, tipCents: 0 }\n const isCash = order.channel === 'pos' && !order.checkoutSessionId && !order.paymentIntentId\n return {\n tenders: [\n {\n label: order['reservationId'] ? 'Charged to room' : isCash ? 'Cash' : 'Card',\n amountCents: totalCents + (isCash ? changeCents : 0),\n },\n ],\n changeCents: isCash ? changeCents : 0,\n tipCents: 0,\n }\n}\n\nexport interface ReceiptContext {\n storeName: string\n storeLines?: string[]\n registerName?: string\n cashierName?: string\n timeZone?: string\n currency?: string\n footer?: string\n banner?: string\n}\n\n/** The receipt an order prints, from the order as stored. */\nexport function receiptDataFromOrder(\n orderId: string,\n order: HostOrder & Record<string, any>,\n context: ReceiptContext,\n): ReceiptData {\n const lines: ReceiptLine[] = (order.lineItems ?? []).map((line) => {\n const quantity = Math.max(1, Math.round(Number(line.quantity) || 1))\n const unitCents = Math.round(Number(line.unitAmountCents) || 0)\n return {\n name: String(line.name ?? 'Item'),\n ...(line.variantLabel ? { detail: String(line.variantLabel) } : {}),\n quantity,\n unitCents,\n totalCents: unitCents * quantity,\n }\n })\n const totals = order.totals\n const subtotalCents = totals?.itemsCents ?? lines.reduce((sum, line) => sum + line.totalCents, 0)\n const { tenders, changeCents, tipCents } = receiptTendersFromOrder(order)\n const tip = whole((totals as any)?.tipCents) || tipCents\n const orderNumber = order.number ? String(order.number) : orderId.slice(0, 8).toUpperCase()\n return {\n storeName: context.storeName,\n ...(context.storeLines?.length ? { storeLines: context.storeLines } : {}),\n orderNumber,\n orderId,\n createdAtMs: Number(order['createdAtMs']) || Date.now(),\n ...(context.timeZone ? { timeZone: context.timeZone } : {}),\n ...(context.registerName ? { registerName: context.registerName } : {}),\n ...(context.cashierName ? { cashierName: context.cashierName } : {}),\n currency: String(order['currency'] ?? context.currency ?? 'usd'),\n lines,\n subtotalCents,\n ...(totals?.discountCents ? { discountCents: totals.discountCents } : {}),\n ...(totals?.shippingCents ? { shippingCents: totals.shippingCents } : {}),\n ...(totals?.taxCents ? { taxCents: totals.taxCents } : {}),\n ...(tip ? { tipCents: tip } : {}),\n totalCents: totals?.totalCents ?? subtotalCents,\n ...(tenders.length ? { tenders } : {}),\n ...(changeCents ? { changeCents } : {}),\n ...(whole(order.refundedCents) ? { refundedCents: whole(order.refundedCents) } : {}),\n // A sale of card reader hardware prints the terms it is sold under\n // (AGL-4038, Terms 4.28), after the store's own footer.\n ...(withPosHardwareReceiptNotice(context.footer, order) ? { footer: withPosHardwareReceiptNotice(context.footer, order) } : {}),\n barcode: orderNumber,\n ...(context.banner ? { banner: context.banner } : {}),\n }\n}\n"],"names":["withPosHardwareReceiptNotice","formatReceiptMoney","cents","currency","code","String","toUpperCase","safe","Number","isFinite","Math","round","digits","Intl","NumberFormat","style","resolvedOptions","maximumFractionDigits","minimumFractionDigits","format","toFixed","formatReceiptTime","atMs","timeZone","options","year","month","day","hour","minute","DateTimeFormat","Date","PAYMENT_METHOD_LABELS","cash","card_present","card_keyed","card_link","gift_card","folio","whole","value","number","receiptTendersFromOrder","order","payments","Array","isArray","length","changeCents","tipCents","tenders","filter","payment","status","map","card","cardBrand","last4","Boolean","join","label","method","tendered","cashTenderedCents","amountCents","totalCents","totals","isCash","channel","checkoutSessionId","paymentIntentId","receiptDataFromOrder","orderId","context","lines","lineItems","line","quantity","max","unitCents","unitAmountCents","name","variantLabel","detail","subtotalCents","itemsCents","reduce","sum","tip","orderNumber","slice","storeName","storeLines","createdAtMs","now","registerName","cashierName","discountCents","shippingCents","taxCents","refundedCents","footer","barcode","banner"],"mappings":"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"}
|
|
@@ -52,6 +52,24 @@ export interface HostResource {
|
|
|
52
52
|
* {@link resourceUnits}, never raw.
|
|
53
53
|
*/
|
|
54
54
|
units?: number;
|
|
55
|
+
/**
|
|
56
|
+
* When the booking widget's scarcity line ("Only 2 left") starts, for a
|
|
57
|
+
* resource with several units (AGL-4044): it shows once this many units or
|
|
58
|
+
* fewer are free on a night of the stay. Absent is
|
|
59
|
+
* {@link FEW_UNITS_LEFT}, the line every several-unit resource has always
|
|
60
|
+
* had; 0 never shows it. A whole number from 0 to
|
|
61
|
+
* {@link MAX_RESOURCE_UNITS}; read it through {@link resourceFewLeftAt},
|
|
62
|
+
* never raw.
|
|
63
|
+
*/
|
|
64
|
+
fewLeftAt?: number;
|
|
65
|
+
/**
|
|
66
|
+
* How a stay on this resource is checked in at the window (AGL-4043):
|
|
67
|
+
* `false` or absent, one check-in (and wristband) per stay per day; `true`,
|
|
68
|
+
* one per guest per day, from the stay's {@link HostReservation.guests}.
|
|
69
|
+
* The store's quick control on the check-in screen can set every resource
|
|
70
|
+
* at once (`settings/checkIn.stays`).
|
|
71
|
+
*/
|
|
72
|
+
checkInPerGuest?: boolean;
|
|
55
73
|
photoUrls?: string[];
|
|
56
74
|
amenities?: string[];
|
|
57
75
|
nightlyRateUsd: number;
|
|
@@ -80,6 +98,16 @@ export interface HostReservation {
|
|
|
80
98
|
checkOutDayMs: number;
|
|
81
99
|
guestName?: string | null;
|
|
82
100
|
guestEmail?: string | null;
|
|
101
|
+
/**
|
|
102
|
+
* How many guests the stay is for (AGL-4043), 1 up to what the resource
|
|
103
|
+
* sleeps ({@link resourceGuestCeiling}); absent on a stay booked before the
|
|
104
|
+
* field existed, or without an answer, which reads as 1. Checking in one
|
|
105
|
+
* guest at a time asks it.
|
|
106
|
+
*/
|
|
107
|
+
guests?: number;
|
|
108
|
+
/** When staff last changed {@link guests} after booking, and who (AGL-4043). */
|
|
109
|
+
guestsChangedAtMs?: number;
|
|
110
|
+
guestsChangedBy?: string;
|
|
83
111
|
nights?: number;
|
|
84
112
|
totalCents?: number;
|
|
85
113
|
depositCents?: number;
|
|
@@ -225,17 +253,54 @@ export declare function isRangeAvailable(resource: Pick<HostResource, 'blocks' |
|
|
|
225
253
|
* The widget's scarcity line starts at this many units left (AGL-4044):
|
|
226
254
|
* "Only 2 left". Never on a one-unit resource, where every free night would
|
|
227
255
|
* read "Only 1 left", and never on a night nothing is booked on.
|
|
256
|
+
* A resource may set its own threshold, {@link HostResource.fewLeftAt}.
|
|
228
257
|
*/
|
|
229
258
|
export declare const FEW_UNITS_LEFT = 3;
|
|
259
|
+
/**
|
|
260
|
+
* When a resource's scarcity line starts: its own `fewLeftAt` as a whole
|
|
261
|
+
* number from 0 (never) to {@link MAX_RESOURCE_UNITS}, and
|
|
262
|
+
* {@link FEW_UNITS_LEFT} for anything else (absent, negative, not a number),
|
|
263
|
+
* so a resource saved before the setting existed reads exactly as it did.
|
|
264
|
+
*/
|
|
265
|
+
export declare function resourceFewLeftAt(resource: Pick<HostResource, 'fewLeftAt'> | null | undefined): number;
|
|
230
266
|
/**
|
|
231
267
|
* The fewest units left across `[checkInDayMs, checkOutDayMs)` when that is
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
268
|
+
* `fewLeftAt` or fewer and at least one, else null. `fewLeft` lists only the
|
|
269
|
+
* scarce nights, as the availability endpoint sends them; a night it leaves
|
|
270
|
+
* out has more than the resource's threshold free. `fewLeftAt` is the
|
|
271
|
+
* resource's threshold ({@link resourceFewLeftAt}); 0 never shows the line.
|
|
235
272
|
*/
|
|
236
273
|
export declare function fewestUnitsLeft(fewLeft: Array<{
|
|
237
274
|
dayMs: number;
|
|
238
275
|
unitsLeft: number;
|
|
239
|
-
}> | null | undefined, checkInDayMs: number, checkOutDayMs: number): number | null;
|
|
276
|
+
}> | null | undefined, checkInDayMs: number, checkOutDayMs: number, fewLeftAt?: number): number | null;
|
|
277
|
+
/** The most guests one stay may name when its resource says nothing of how many it sleeps. */
|
|
278
|
+
export declare const MAX_RESERVATION_GUESTS = 50;
|
|
279
|
+
/** Where staff change a stay's guest count after it is booked (AGL-4043): `{ hostId, reservationId, guests }`. */
|
|
280
|
+
export declare const RESERVATION_GUESTS_ROUTE = "/api/commerce/reservations/guests";
|
|
281
|
+
/**
|
|
282
|
+
* The most guests a stay on this resource may name: what one unit sleeps
|
|
283
|
+
* (`capacity`), else {@link MAX_RESERVATION_GUESTS}.
|
|
284
|
+
*/
|
|
285
|
+
export declare function resourceGuestCeiling(resource: Pick<HostResource, 'capacity'> | null | undefined): number;
|
|
286
|
+
/** How many guests a stay is for: its `guests` as a whole number of at least 1, else 1. */
|
|
287
|
+
export declare function reservationGuests(reservation: Pick<HostReservation, 'guests'> | null | undefined): number;
|
|
288
|
+
/**
|
|
289
|
+
* Why a guest count cannot be booked on a resource, or null. Absent (or
|
|
290
|
+
* empty) is fine: the stay is for one guest as far as check-in is concerned.
|
|
291
|
+
*/
|
|
292
|
+
export declare function reservationGuestsProblem(guests: unknown, resource: Pick<HostResource, 'capacity'> | null | undefined): string | null;
|
|
293
|
+
/**
|
|
294
|
+
* How the store checks stays in (AGL-4043), the quick control on the
|
|
295
|
+
* check-in screen: `resource` follows each resource's own setting
|
|
296
|
+
* ({@link HostResource.checkInPerGuest}); `stay` and `guest` set every
|
|
297
|
+
* resource at once. Stored at `hosts/{hostId}/settings/checkIn.stays`.
|
|
298
|
+
*/
|
|
299
|
+
export type StayCheckInMode = 'resource' | 'stay' | 'guest';
|
|
300
|
+
export declare const STAY_CHECK_IN_MODES: readonly StayCheckInMode[];
|
|
301
|
+
/** A stored mode made safe: anything unknown follows each resource. */
|
|
302
|
+
export declare function readStayCheckInMode(value: unknown): StayCheckInMode;
|
|
303
|
+
/** Whether a stay on this resource is checked in one guest at a time, under the store's mode. */
|
|
304
|
+
export declare function stayChecksInPerGuest(resource: Pick<HostResource, 'checkInPerGuest'> | null | undefined, mode: StayCheckInMode): boolean;
|
|
240
305
|
/** Free-cancellation check against the policy window. */
|
|
241
306
|
export declare function canCancelReservation(resource: Pick<HostResource, 'cancellationHours'>, reservation: Pick<HostReservation, 'checkInDayMs' | 'status'>, nowMs?: number): boolean;
|
|
@@ -209,21 +209,73 @@ function inSeason(dayKey, season) {
|
|
|
209
209
|
* The widget's scarcity line starts at this many units left (AGL-4044):
|
|
210
210
|
* "Only 2 left". Never on a one-unit resource, where every free night would
|
|
211
211
|
* read "Only 1 left", and never on a night nothing is booked on.
|
|
212
|
+
* A resource may set its own threshold, {@link HostResource.fewLeftAt}.
|
|
212
213
|
*/ export const FEW_UNITS_LEFT = 3;
|
|
214
|
+
/**
|
|
215
|
+
* When a resource's scarcity line starts: its own `fewLeftAt` as a whole
|
|
216
|
+
* number from 0 (never) to {@link MAX_RESOURCE_UNITS}, and
|
|
217
|
+
* {@link FEW_UNITS_LEFT} for anything else (absent, negative, not a number),
|
|
218
|
+
* so a resource saved before the setting existed reads exactly as it did.
|
|
219
|
+
*/ export function resourceFewLeftAt(resource) {
|
|
220
|
+
const raw = resource == null ? void 0 : resource.fewLeftAt;
|
|
221
|
+
if (raw === undefined || raw === null || raw === '') return FEW_UNITS_LEFT;
|
|
222
|
+
const at = Math.floor(Number(raw));
|
|
223
|
+
if (!Number.isFinite(at) || at < 0) return FEW_UNITS_LEFT;
|
|
224
|
+
return Math.min(MAX_RESOURCE_UNITS, at);
|
|
225
|
+
}
|
|
213
226
|
/**
|
|
214
227
|
* The fewest units left across `[checkInDayMs, checkOutDayMs)` when that is
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
|
|
228
|
+
* `fewLeftAt` or fewer and at least one, else null. `fewLeft` lists only the
|
|
229
|
+
* scarce nights, as the availability endpoint sends them; a night it leaves
|
|
230
|
+
* out has more than the resource's threshold free. `fewLeftAt` is the
|
|
231
|
+
* resource's threshold ({@link resourceFewLeftAt}); 0 never shows the line.
|
|
232
|
+
*/ export function fewestUnitsLeft(fewLeft, checkInDayMs, checkOutDayMs, fewLeftAt = FEW_UNITS_LEFT) {
|
|
219
233
|
let fewest = null;
|
|
220
234
|
for (const night of fewLeft != null ? fewLeft : []){
|
|
221
235
|
if (night.dayMs < checkInDayMs || night.dayMs >= checkOutDayMs) continue;
|
|
222
|
-
if (!(night.unitsLeft >= 1) || night.unitsLeft >
|
|
236
|
+
if (!(night.unitsLeft >= 1) || night.unitsLeft > fewLeftAt) continue;
|
|
223
237
|
fewest = fewest === null ? night.unitsLeft : Math.min(fewest, night.unitsLeft);
|
|
224
238
|
}
|
|
225
239
|
return fewest;
|
|
226
240
|
}
|
|
241
|
+
/** The most guests one stay may name when its resource says nothing of how many it sleeps. */ export const MAX_RESERVATION_GUESTS = 50;
|
|
242
|
+
/** Where staff change a stay's guest count after it is booked (AGL-4043): `{ hostId, reservationId, guests }`. */ export const RESERVATION_GUESTS_ROUTE = '/api/commerce/reservations/guests';
|
|
243
|
+
/**
|
|
244
|
+
* The most guests a stay on this resource may name: what one unit sleeps
|
|
245
|
+
* (`capacity`), else {@link MAX_RESERVATION_GUESTS}.
|
|
246
|
+
*/ export function resourceGuestCeiling(resource) {
|
|
247
|
+
const sleeps = Math.floor(Number(resource == null ? void 0 : resource.capacity));
|
|
248
|
+
return Number.isFinite(sleeps) && sleeps >= 1 ? Math.min(sleeps, MAX_RESERVATION_GUESTS) : MAX_RESERVATION_GUESTS;
|
|
249
|
+
}
|
|
250
|
+
/** How many guests a stay is for: its `guests` as a whole number of at least 1, else 1. */ export function reservationGuests(reservation) {
|
|
251
|
+
const guests = Math.floor(Number(reservation == null ? void 0 : reservation.guests));
|
|
252
|
+
return Number.isFinite(guests) && guests >= 1 ? Math.min(guests, MAX_RESERVATION_GUESTS) : 1;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Why a guest count cannot be booked on a resource, or null. Absent (or
|
|
256
|
+
* empty) is fine: the stay is for one guest as far as check-in is concerned.
|
|
257
|
+
*/ export function reservationGuestsProblem(guests, resource) {
|
|
258
|
+
if (guests === undefined || guests === null || guests === '') return null;
|
|
259
|
+
const count = Number(guests);
|
|
260
|
+
const ceiling = resourceGuestCeiling(resource);
|
|
261
|
+
if (!Number.isInteger(count) || count < 1 || count > ceiling) {
|
|
262
|
+
return ceiling === 1 ? 'This stay sleeps 1 guest' : `Guests must be a whole number from 1 to ${ceiling}`;
|
|
263
|
+
}
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
export const STAY_CHECK_IN_MODES = [
|
|
267
|
+
'resource',
|
|
268
|
+
'stay',
|
|
269
|
+
'guest'
|
|
270
|
+
];
|
|
271
|
+
/** A stored mode made safe: anything unknown follows each resource. */ export function readStayCheckInMode(value) {
|
|
272
|
+
return value === 'stay' || value === 'guest' ? value : 'resource';
|
|
273
|
+
}
|
|
274
|
+
/** Whether a stay on this resource is checked in one guest at a time, under the store's mode. */ export function stayChecksInPerGuest(resource, mode) {
|
|
275
|
+
if (mode === 'guest') return true;
|
|
276
|
+
if (mode === 'stay') return false;
|
|
277
|
+
return (resource == null ? void 0 : resource.checkInPerGuest) === true;
|
|
278
|
+
}
|
|
227
279
|
/** Free-cancellation check against the policy window. */ export function canCancelReservation(resource, reservation, nowMs = Date.now()) {
|
|
228
280
|
var _resource_cancellationHours;
|
|
229
281
|
if (![
|