@aglyn/plugins-commerce 1.0.0-beta.232 → 1.0.0-beta.233

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.
Files changed (228) hide show
  1. package/package.json +13 -13
  2. package/src/lib/components/cart-extras.d.ts +6 -0
  3. package/src/lib/components/cart-extras.js +4 -1
  4. package/src/lib/components/cart-extras.js.map +1 -1
  5. package/src/lib/components/cart-fulfillment.d.ts +117 -0
  6. package/src/lib/components/cart-fulfillment.js +367 -0
  7. package/src/lib/components/cart-fulfillment.js.map +1 -0
  8. package/src/lib/components/cart.js +52 -10
  9. package/src/lib/components/cart.js.map +1 -1
  10. package/src/lib/components/commerce-console-page.js +42 -1
  11. package/src/lib/components/commerce-console-page.js.map +1 -1
  12. package/src/lib/components/console/host-orders-card.component.js +38 -10
  13. package/src/lib/components/console/host-orders-card.component.js.map +1 -1
  14. package/src/lib/components/console/local-delivery-settings-card.component.d.ts +37 -0
  15. package/src/lib/components/console/local-delivery-settings-card.component.js +542 -0
  16. package/src/lib/components/console/local-delivery-settings-card.component.js.map +1 -0
  17. package/src/lib/components/console/locations-card.component.js +177 -3
  18. package/src/lib/components/console/locations-card.component.js.map +1 -1
  19. package/src/lib/components/console/order-detail-dialog.component.js +10 -0
  20. package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
  21. package/src/lib/components/console/order-local-fulfillment-panel.component.d.ts +44 -0
  22. package/src/lib/components/console/order-local-fulfillment-panel.component.js +265 -0
  23. package/src/lib/components/console/order-local-fulfillment-panel.component.js.map +1 -0
  24. package/src/lib/components/console/order-offline-notice.component.d.ts +12 -0
  25. package/src/lib/components/console/order-offline-notice.component.js +53 -0
  26. package/src/lib/components/console/order-offline-notice.component.js.map +1 -0
  27. package/src/lib/components/console/pickup-delivery-queue-card.component.d.ts +35 -0
  28. package/src/lib/components/console/pickup-delivery-queue-card.component.js +222 -0
  29. package/src/lib/components/console/pickup-delivery-queue-card.component.js.map +1 -0
  30. package/src/lib/components/console/pos/pos-api.d.ts +24 -1
  31. package/src/lib/components/console/pos/pos-api.js +13 -0
  32. package/src/lib/components/console/pos/pos-api.js.map +1 -1
  33. package/src/lib/components/console/pos/pos-item-dialog.component.d.ts +12 -0
  34. package/src/lib/components/console/pos/pos-item-dialog.component.js +10 -8
  35. package/src/lib/components/console/pos/pos-item-dialog.component.js.map +1 -1
  36. package/src/lib/components/console/pos/pos-kiosk-queue.component.d.ts +22 -0
  37. package/src/lib/components/console/pos/pos-kiosk-queue.component.js +168 -0
  38. package/src/lib/components/console/pos/pos-kiosk-queue.component.js.map +1 -0
  39. package/src/lib/components/console/pos/pos-tender-dialogs.component.d.ts +18 -0
  40. package/src/lib/components/console/pos/pos-tender-dialogs.component.js +116 -1
  41. package/src/lib/components/console/pos/pos-tender-dialogs.component.js.map +1 -1
  42. package/src/lib/components/console/pos/pos-tender-panel.component.d.ts +8 -0
  43. package/src/lib/components/console/pos/pos-tender-panel.component.js +77 -18
  44. package/src/lib/components/console/pos/pos-tender-panel.component.js.map +1 -1
  45. package/src/lib/components/console/pos/use-pos-display.d.ts +5 -2
  46. package/src/lib/components/console/pos/use-pos-display.js +3 -2
  47. package/src/lib/components/console/pos/use-pos-display.js.map +1 -1
  48. package/src/lib/components/console/pos-devices-card.component.js +2 -2
  49. package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
  50. package/src/lib/components/console/pos-display/pairing-screen.d.ts +5 -2
  51. package/src/lib/components/console/pos-display/pairing-screen.js +7 -5
  52. package/src/lib/components/console/pos-display/pairing-screen.js.map +1 -1
  53. package/src/lib/components/console/pos-display/pos-display-api.d.ts +7 -1
  54. package/src/lib/components/console/pos-display/pos-display-api.js +7 -2
  55. package/src/lib/components/console/pos-display/pos-display-api.js.map +1 -1
  56. package/src/lib/components/console/pos-kiosk/kiosk-checkout.d.ts +41 -0
  57. package/src/lib/components/console/pos-kiosk/kiosk-checkout.js +275 -0
  58. package/src/lib/components/console/pos-kiosk/kiosk-checkout.js.map +1 -0
  59. package/src/lib/components/console/pos-kiosk/kiosk-menu.d.ts +32 -0
  60. package/src/lib/components/console/pos-kiosk/kiosk-menu.js +344 -0
  61. package/src/lib/components/console/pos-kiosk/kiosk-menu.js.map +1 -0
  62. package/src/lib/components/console/pos-kiosk/kiosk-staff.d.ts +12 -0
  63. package/src/lib/components/console/pos-kiosk/kiosk-staff.js +257 -0
  64. package/src/lib/components/console/pos-kiosk/kiosk-staff.js.map +1 -0
  65. package/src/lib/components/console/pos-kiosk/pos-kiosk-api.d.ts +114 -0
  66. package/src/lib/components/console/pos-kiosk/pos-kiosk-api.js +189 -0
  67. package/src/lib/components/console/pos-kiosk/pos-kiosk-api.js.map +1 -0
  68. package/src/lib/components/console/pos-kiosk/use-kiosk-idle.d.ts +16 -0
  69. package/src/lib/components/console/pos-kiosk/use-kiosk-idle.js +80 -0
  70. package/src/lib/components/console/pos-kiosk/use-kiosk-idle.js.map +1 -0
  71. package/src/lib/components/console/pos-kiosk-page.component.d.ts +28 -0
  72. package/src/lib/components/console/pos-kiosk-page.component.js +791 -0
  73. package/src/lib/components/console/pos-kiosk-page.component.js.map +1 -0
  74. package/src/lib/components/console/pos-offline/pos-offline-api.d.ts +44 -0
  75. package/src/lib/components/console/pos-offline/pos-offline-api.js +69 -0
  76. package/src/lib/components/console/pos-offline/pos-offline-api.js.map +1 -0
  77. package/src/lib/components/console/pos-offline/pos-offline-banner.component.d.ts +7 -0
  78. package/src/lib/components/console/pos-offline/pos-offline-banner.component.js +135 -0
  79. package/src/lib/components/console/pos-offline/pos-offline-banner.component.js.map +1 -0
  80. package/src/lib/components/console/pos-offline/pos-offline-checkout.component.d.ts +32 -0
  81. package/src/lib/components/console/pos-offline/pos-offline-checkout.component.js +287 -0
  82. package/src/lib/components/console/pos-offline/pos-offline-checkout.component.js.map +1 -0
  83. package/src/lib/components/console/pos-offline/pos-offline-store.d.ts +63 -0
  84. package/src/lib/components/console/pos-offline/pos-offline-store.js +145 -0
  85. package/src/lib/components/console/pos-offline/pos-offline-store.js.map +1 -0
  86. package/src/lib/components/console/pos-offline/register-offline.d.ts +24 -0
  87. package/src/lib/components/console/pos-offline/register-offline.js +24 -0
  88. package/src/lib/components/console/pos-offline/register-offline.js.map +1 -0
  89. package/src/lib/components/console/pos-offline/use-pos-offline.d.ts +85 -0
  90. package/src/lib/components/console/pos-offline/use-pos-offline.js +542 -0
  91. package/src/lib/components/console/pos-offline/use-pos-offline.js.map +1 -0
  92. package/src/lib/components/console/pos-ops/pos-shift-history-card.component.js +72 -17
  93. package/src/lib/components/console/pos-ops/pos-shift-history-card.component.js.map +1 -1
  94. package/src/lib/components/console/pos-page.component.js +174 -27
  95. package/src/lib/components/console/pos-page.component.js.map +1 -1
  96. package/src/lib/components/console/products-hub-card.component.js +35 -10
  97. package/src/lib/components/console/products-hub-card.component.js.map +1 -1
  98. package/src/lib/components/console/shipping-settings-card.component.js +1 -1
  99. package/src/lib/components/console/shipping-settings-card.component.js.map +1 -1
  100. package/src/lib/components/console/stock-movements-card.component.js +48 -16
  101. package/src/lib/components/console/stock-movements-card.component.js.map +1 -1
  102. package/src/lib/components/console/store-zones.d.ts +8 -1
  103. package/src/lib/components/console/store-zones.js +11 -1
  104. package/src/lib/components/console/store-zones.js.map +1 -1
  105. package/src/lib/components/order-status.js +66 -0
  106. package/src/lib/components/order-status.js.map +1 -1
  107. package/src/lib/components/product-detail.js +42 -15
  108. package/src/lib/components/product-detail.js.map +1 -1
  109. package/src/lib/constants/local-fulfillment-list-query.d.ts +48 -0
  110. package/src/lib/constants/local-fulfillment-list-query.js +84 -0
  111. package/src/lib/constants/local-fulfillment-list-query.js.map +1 -0
  112. package/src/lib/constants/orders-list-query.d.ts +7 -2
  113. package/src/lib/constants/orders-list-query.js +89 -8
  114. package/src/lib/constants/orders-list-query.js.map +1 -1
  115. package/src/lib/constants/pos-shift-list-query.d.ts +22 -0
  116. package/src/lib/constants/pos-shift-list-query.js +115 -0
  117. package/src/lib/constants/pos-shift-list-query.js.map +1 -0
  118. package/src/lib/constants/product-list-query.d.ts +6 -1
  119. package/src/lib/constants/product-list-query.js +77 -8
  120. package/src/lib/constants/product-list-query.js.map +1 -1
  121. package/src/lib/constants/stock-movement-query.d.ts +3 -2
  122. package/src/lib/constants/stock-movement-query.js +52 -8
  123. package/src/lib/constants/stock-movement-query.js.map +1 -1
  124. package/src/lib/model/buyer-notifications.d.ts +5 -3
  125. package/src/lib/model/buyer-notifications.js +25 -4
  126. package/src/lib/model/buyer-notifications.js.map +1 -1
  127. package/src/lib/model/commerce-orders.d.ts +59 -0
  128. package/src/lib/model/commerce-orders.js.map +1 -1
  129. package/src/lib/model/commerce-pos-kiosk.d.ts +175 -0
  130. package/src/lib/model/commerce-pos-kiosk.js +147 -0
  131. package/src/lib/model/commerce-pos-kiosk.js.map +1 -0
  132. package/src/lib/model/commerce-pos-offline.d.ts +234 -0
  133. package/src/lib/model/commerce-pos-offline.js +301 -0
  134. package/src/lib/model/commerce-pos-offline.js.map +1 -0
  135. package/src/lib/model/commerce-pos-ops.d.ts +16 -6
  136. package/src/lib/model/commerce-pos-ops.js +12 -4
  137. package/src/lib/model/commerce-pos-ops.js.map +1 -1
  138. package/src/lib/model/commerce-pos.d.ts +13 -1
  139. package/src/lib/model/commerce-pos.js +5 -4
  140. package/src/lib/model/commerce-pos.js.map +1 -1
  141. package/src/lib/model/commerce-stock-holds.d.ts +24 -3
  142. package/src/lib/model/commerce-stock-holds.js +26 -8
  143. package/src/lib/model/commerce-stock-holds.js.map +1 -1
  144. package/src/lib/model/commerce.d.ts +3 -0
  145. package/src/lib/model/commerce.js.map +1 -1
  146. package/src/lib/model/index.d.ts +4 -0
  147. package/src/lib/model/index.js +4 -0
  148. package/src/lib/model/index.js.map +1 -1
  149. package/src/lib/model/local-fulfillment-settings.d.ts +178 -0
  150. package/src/lib/model/local-fulfillment-settings.js +361 -0
  151. package/src/lib/model/local-fulfillment-settings.js.map +1 -0
  152. package/src/lib/model/order-list-fields.d.ts +10 -1
  153. package/src/lib/model/order-list-fields.js +1 -0
  154. package/src/lib/model/order-list-fields.js.map +1 -1
  155. package/src/lib/model/order-local-fulfillment.d.ts +131 -0
  156. package/src/lib/model/order-local-fulfillment.js +125 -0
  157. package/src/lib/model/order-local-fulfillment.js.map +1 -0
  158. package/src/lib/model/order-shipping-export.js +4 -0
  159. package/src/lib/model/order-shipping-export.js.map +1 -1
  160. package/src/lib/model/order-status-view.d.ts +20 -1
  161. package/src/lib/model/order-status-view.js +60 -6
  162. package/src/lib/model/order-status-view.js.map +1 -1
  163. package/src/lib/plugin-config.js +6 -2
  164. package/src/lib/plugin-config.js.map +1 -1
  165. package/src/lib/plugin.js +8 -0
  166. package/src/lib/plugin.js.map +1 -1
  167. package/src/lib/pos-kiosk-config.d.ts +26 -0
  168. package/src/lib/pos-kiosk-config.js +59 -0
  169. package/src/lib/pos-kiosk-config.js.map +1 -0
  170. package/src/lib/server/api-v1/openapi.js +8 -0
  171. package/src/lib/server/api-v1/openapi.js.map +1 -1
  172. package/src/lib/server/api-v1/order-view.d.ts +19 -0
  173. package/src/lib/server/api-v1/order-view.js +35 -0
  174. package/src/lib/server/api-v1/order-view.js.map +1 -1
  175. package/src/lib/server/billing-webhook.js +238 -87
  176. package/src/lib/server/billing-webhook.js.map +1 -1
  177. package/src/lib/server/cart-checkout.d.ts +2 -0
  178. package/src/lib/server/cart-checkout.js +173 -13
  179. package/src/lib/server/cart-checkout.js.map +1 -1
  180. package/src/lib/server/checkout-extras.js +18 -4
  181. package/src/lib/server/checkout-extras.js.map +1 -1
  182. package/src/lib/server/checkout.js +71 -14
  183. package/src/lib/server/checkout.js.map +1 -1
  184. package/src/lib/server/local-fulfillment-status.d.ts +86 -0
  185. package/src/lib/server/local-fulfillment-status.js +379 -0
  186. package/src/lib/server/local-fulfillment-status.js.map +1 -0
  187. package/src/lib/server/local-fulfillment.d.ts +190 -0
  188. package/src/lib/server/local-fulfillment.js +580 -0
  189. package/src/lib/server/local-fulfillment.js.map +1 -0
  190. package/src/lib/server/membership-register.js +3 -1
  191. package/src/lib/server/membership-register.js.map +1 -1
  192. package/src/lib/server/order-notifications.js +61 -0
  193. package/src/lib/server/order-notifications.js.map +1 -1
  194. package/src/lib/server/pos-display.d.ts +27 -1
  195. package/src/lib/server/pos-display.js +50 -9
  196. package/src/lib/server/pos-display.js.map +1 -1
  197. package/src/lib/server/pos-kiosk-principal.d.ts +29 -0
  198. package/src/lib/server/pos-kiosk-principal.js +42 -0
  199. package/src/lib/server/pos-kiosk-principal.js.map +1 -0
  200. package/src/lib/server/pos-kiosk.d.ts +54 -0
  201. package/src/lib/server/pos-kiosk.js +972 -0
  202. package/src/lib/server/pos-kiosk.js.map +1 -0
  203. package/src/lib/server/pos-offline-sync.d.ts +85 -0
  204. package/src/lib/server/pos-offline-sync.js +765 -0
  205. package/src/lib/server/pos-offline-sync.js.map +1 -0
  206. package/src/lib/server/pos-order.js +22 -4
  207. package/src/lib/server/pos-order.js.map +1 -1
  208. package/src/lib/server/pos-payment.d.ts +6 -0
  209. package/src/lib/server/pos-payment.js +225 -6
  210. package/src/lib/server/pos-payment.js.map +1 -1
  211. package/src/lib/server/pos-print.js +5 -0
  212. package/src/lib/server/pos-print.js.map +1 -1
  213. package/src/lib/server/pos-return.js +27 -0
  214. package/src/lib/server/pos-return.js.map +1 -1
  215. package/src/lib/server/pos-sale.d.ts +6 -0
  216. package/src/lib/server/pos-sale.js +2 -0
  217. package/src/lib/server/pos-sale.js.map +1 -1
  218. package/src/lib/server/pos-shift.js +8 -1
  219. package/src/lib/server/pos-shift.js.map +1 -1
  220. package/src/lib/server/refund.js +3 -0
  221. package/src/lib/server/refund.js.map +1 -1
  222. package/src/lib/server/stock-hold.d.ts +9 -1
  223. package/src/lib/server/stock-hold.js +15 -7
  224. package/src/lib/server/stock-hold.js.map +1 -1
  225. package/src/lib/server.js +16 -0
  226. package/src/lib/server.js.map +1 -1
  227. package/src/lib/tenant-emails.js +161 -0
  228. package/src/lib/tenant-emails.js.map +1 -1
@@ -0,0 +1,580 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { resolveSiteTimeZone } from "@aglyn/aglyn/app-utils/collection-entry-date";
18
+ import { normalizePhone } from "@aglyn/aglyn/foundation/definitions/contact.types";
19
+ import { pluginSmsMessaging } from "@aglyn/aglyn/plugin-manager/plugin-sms-messaging";
20
+ import { pluginShippingRateQuoter } from "@aglyn/aglyn/plugin-manager/plugin-shipping-rates";
21
+ import * as Aglyn from "@aglyn/aglyn/server";
22
+ import { consumeRateLimit, firebaseAdmin, getOrgForHost } from "@aglyn/tenant-data-admin";
23
+ import { isDocumentId } from "@aglyn/tenant-data-admin/server/document-id";
24
+ import * as CommerceModel from "../model/index.js";
25
+ import { readCartId } from "./cart-cookie.js";
26
+ /** Reads the cart's declaration; `null` when it made none (a shipped order). */ export function readLocalFulfillmentRequest(value) {
27
+ if (!value || typeof value !== 'object') return null;
28
+ const raw = value;
29
+ const method = raw['method'];
30
+ if (method !== 'pickup' && method !== 'local_delivery') return null;
31
+ const text = (input, max)=>String(input != null ? input : '').trim().slice(0, max);
32
+ const request = {
33
+ method
34
+ };
35
+ const phone = normalizePhone(text(raw['textPhone'], 32));
36
+ if (phone) request.textPhone = phone;
37
+ if (method === 'pickup') {
38
+ request.locationId = text(raw['locationId'], 120);
39
+ return request;
40
+ }
41
+ const postalCode = text(raw['postalCode'], 16);
42
+ if (postalCode) request.postalCode = postalCode;
43
+ const start = Math.round(Number(raw['windowStartMs']));
44
+ if (Number.isFinite(start) && start > 0) request.windowStartMs = start;
45
+ const address = raw['address'];
46
+ if (address && typeof address === 'object') {
47
+ const parts = address;
48
+ request.address = {
49
+ line1: text(parts['line1'], 120),
50
+ line2: text(parts['line2'], 120),
51
+ city: text(parts['city'], 80),
52
+ state: text(parts['state'], 40),
53
+ postalCode: text(parts['postalCode'], 16) || postalCode
54
+ };
55
+ }
56
+ return request;
57
+ }
58
+ /** Reads what checkout and the cart need. Never throws: a failed read offers nothing. */ export async function readLocalFulfillmentStore(input) {
59
+ var _ref, _input_org;
60
+ const { hostRef } = input;
61
+ const [locationsSnapshot, settings, host] = await Promise.all([
62
+ hostRef.collection('locations').limit(CommerceModel.PICKUP_LOCATIONS_MAX).get().catch(()=>null),
63
+ input.storeSettings !== undefined ? Promise.resolve(input.storeSettings) : hostRef.collection('settings').doc('store').get().then((snapshot)=>{
64
+ var _ref;
65
+ var _snapshot_data;
66
+ return (_ref = snapshot == null ? void 0 : (_snapshot_data = snapshot.data) == null ? void 0 : _snapshot_data.call(snapshot)) != null ? _ref : null;
67
+ }).catch(()=>null),
68
+ input.host !== undefined ? Promise.resolve(input.host) : hostRef.get().then((snapshot)=>{
69
+ var _ref;
70
+ var _snapshot_data;
71
+ return (_ref = snapshot == null ? void 0 : (_snapshot_data = snapshot.data) == null ? void 0 : _snapshot_data.call(snapshot)) != null ? _ref : null;
72
+ }).catch(()=>null)
73
+ ]);
74
+ const locations = ((_ref = locationsSnapshot == null ? void 0 : locationsSnapshot.docs) != null ? _ref : []).map((snapshot)=>_extends({}, snapshot.data(), {
75
+ id: String(snapshot.id)
76
+ }));
77
+ return {
78
+ pickupLocations: CommerceModel.pickupLocationOptions(locations),
79
+ delivery: CommerceModel.normalizeLocalDeliverySettings(settings == null ? void 0 : settings['localDelivery']),
80
+ timeZone: resolveSiteTimeZone((_input_org = input.org) != null ? _input_org : null, host != null ? host : null)
81
+ };
82
+ }
83
+ /** How long checkout waits for an address to be placed on a map. */ export const LOCAL_DELIVERY_GEOCODE_TIMEOUT_MS = 3000;
84
+ /**
85
+ * Places an address on the map through the shipping plugin's address check,
86
+ * or answers `null`: no plugin, not available on this site, no position in
87
+ * the answer, or slower than the timeout. Never throws.
88
+ */ export async function placeDeliveryAddress(hostId, address) {
89
+ if (!address.line1 || !address.postalCode) return null;
90
+ const quoter = pluginShippingRateQuoter();
91
+ if (!(quoter == null ? void 0 : quoter.validateAddress)) return null;
92
+ let timer;
93
+ try {
94
+ const placed = await Promise.race([
95
+ (async ()=>{
96
+ var _ref;
97
+ if (!await quoter.available(hostId)) return null;
98
+ const check = await (quoter.validateAddress == null ? void 0 : quoter.validateAddress.call(quoter, hostId, _extends({
99
+ line1: address.line1
100
+ }, address.line2 ? {
101
+ line2: address.line2
102
+ } : {}, address.city ? {
103
+ city: address.city
104
+ } : {}, address.state ? {
105
+ state: address.state
106
+ } : {}, {
107
+ postalCode: address.postalCode,
108
+ country: address.country
109
+ })));
110
+ return (check == null ? void 0 : check.verdict) === 'invalid' ? null : (_ref = check == null ? void 0 : check.coordinates) != null ? _ref : null;
111
+ })().catch(()=>null),
112
+ new Promise((resolve)=>{
113
+ timer = setTimeout(()=>resolve(null), LOCAL_DELIVERY_GEOCODE_TIMEOUT_MS);
114
+ })
115
+ ]);
116
+ return placed != null ? placed : null;
117
+ } finally{
118
+ if (timer) clearTimeout(timer);
119
+ }
120
+ }
121
+ /** The shipping option id a local delivery fee is declared under. */ export const LOCAL_DELIVERY_RATE_ID = 'local-delivery';
122
+ const money = (cents)=>CommerceModel.formatOrderMoney(cents, 'USD');
123
+ /**
124
+ * Turns the cart's declaration into what the session needs, or a refusal.
125
+ * Taken BEFORE any reservation or Stripe call, so a refusal costs nothing
126
+ * and the buyer's same button works once they choose again.
127
+ */ export async function planLocalFulfillment(input) {
128
+ var _input_nowMs, _request_postalCode;
129
+ var _request_address;
130
+ const { request, store } = input;
131
+ // A cart of downloads is not collected or driven anywhere.
132
+ if (!request || request.method === 'shipping' || !input.hasPhysicalLine) return {
133
+ kind: 'shipping'
134
+ };
135
+ const nowMs = (_input_nowMs = input.nowMs) != null ? _input_nowMs : Date.now();
136
+ if (request.method === 'pickup') {
137
+ const location = store.pickupLocations.find((entry)=>entry.id === request.locationId);
138
+ if (!location) {
139
+ return {
140
+ kind: 'refusal',
141
+ status: 409,
142
+ error: 'That pickup location is no longer available. Choose another.',
143
+ changed: 'pickup'
144
+ };
145
+ }
146
+ return {
147
+ kind: 'pickup',
148
+ location,
149
+ metadata: _extends({
150
+ fulfillment: 'pickup',
151
+ pickupLocationId: location.id
152
+ }, request.textPhone ? {
153
+ orderTextPhone: request.textPhone
154
+ } : {})
155
+ };
156
+ }
157
+ const delivery = store.delivery;
158
+ if (!CommerceModel.localDeliveryOffered(delivery)) {
159
+ return {
160
+ kind: 'refusal',
161
+ status: 409,
162
+ error: 'Local delivery is not available right now. Choose shipping or pickup.',
163
+ changed: 'delivery'
164
+ };
165
+ }
166
+ const country = String(delivery.country);
167
+ const postalCode = CommerceModel.normalizePostcode((_request_postalCode = request.postalCode) != null ? _request_postalCode : (_request_address = request.address) == null ? void 0 : _request_address.postalCode, country);
168
+ if (!postalCode) {
169
+ return {
170
+ kind: 'refusal',
171
+ status: 400,
172
+ error: 'Enter your postal code for delivery.'
173
+ };
174
+ }
175
+ let zone = CommerceModel.matchLocalDeliveryZone(delivery, {
176
+ country,
177
+ postalCode
178
+ });
179
+ if (!zone && CommerceModel.localDeliveryHasRadiusZones(delivery)) {
180
+ var _request_address1;
181
+ if (!((_request_address1 = request.address) == null ? void 0 : _request_address1.line1)) {
182
+ return {
183
+ kind: 'refusal',
184
+ status: 400,
185
+ error: 'Enter your street address for delivery.'
186
+ };
187
+ }
188
+ const coordinates = await placeDeliveryAddress(input.hostId, _extends({}, request.address, {
189
+ postalCode,
190
+ country
191
+ }));
192
+ zone = CommerceModel.matchLocalDeliveryZone(delivery, {
193
+ country,
194
+ postalCode,
195
+ coordinates
196
+ });
197
+ }
198
+ if (!zone) {
199
+ return {
200
+ kind: 'refusal',
201
+ status: 409,
202
+ error: `We don’t deliver to ${postalCode}. Choose shipping or pickup instead.`
203
+ };
204
+ }
205
+ const shortfall = CommerceModel.localDeliveryMinimumShortfall(zone, input.itemsCents);
206
+ if (shortfall > 0) {
207
+ var _zone_minimumCents;
208
+ return {
209
+ kind: 'refusal',
210
+ status: 409,
211
+ error: `Delivery to ${postalCode} needs an order of at least ${money((_zone_minimumCents = zone.minimumCents) != null ? _zone_minimumCents : 0)}. ` + `Add ${money(shortfall)} more, or choose shipping or pickup.`
212
+ };
213
+ }
214
+ const window = CommerceModel.findLocalDeliveryWindow(delivery, request.windowStartMs, {
215
+ nowMs,
216
+ timeZone: store.timeZone
217
+ });
218
+ if (!window) {
219
+ return {
220
+ kind: 'refusal',
221
+ status: 409,
222
+ error: 'That delivery time is no longer available. Choose another.',
223
+ changed: 'delivery'
224
+ };
225
+ }
226
+ const feeCents = CommerceModel.localDeliveryFeeCents(zone, input.itemsCents);
227
+ const label = CommerceModel.formatLocalDeliveryWindow(window, store.timeZone);
228
+ return _extends({
229
+ kind: 'local_delivery',
230
+ zone,
231
+ window,
232
+ feeCents,
233
+ country
234
+ }, delivery.locationId ? {
235
+ locationId: delivery.locationId
236
+ } : {}, {
237
+ option: {
238
+ rateId: LOCAL_DELIVERY_RATE_ID,
239
+ name: `Local delivery — ${label}`.slice(0, 100),
240
+ amountCents: feeCents
241
+ },
242
+ metadata: _extends({
243
+ fulfillment: 'local_delivery',
244
+ deliveryZoneId: zone.id,
245
+ deliveryWindowStartMs: String(window.startMs),
246
+ deliveryWindowEndMs: String(window.endMs),
247
+ deliveryPostalCode: postalCode,
248
+ deliveryFeeCents: String(feeCents)
249
+ }, delivery.locationId ? {
250
+ deliveryLocationId: delivery.locationId
251
+ } : {}, request.textPhone ? {
252
+ orderTextPhone: request.textPhone
253
+ } : {})
254
+ });
255
+ }
256
+ /** Sets a plan's metadata on a session's form body. */ export function appendLocalFulfillmentMetadata(params, metadata) {
257
+ for (const [key, value] of Object.entries(metadata)){
258
+ params.set(`metadata[${key}]`, String(value).slice(0, 480));
259
+ }
260
+ }
261
+ /** The location a plan takes its stock from, if any. */ export function localFulfillmentLocationId(plan) {
262
+ if (plan.kind === 'pickup') return plan.location.id;
263
+ if (plan.kind === 'local_delivery') return plan.locationId;
264
+ return undefined;
265
+ }
266
+ /** A stock refusal for a plan that reserves at one location (AGL-3624). */ export function localStockRefusalMessage(plan, productName) {
267
+ const what = productName ? `"${productName}"` : 'An item in your cart';
268
+ if (plan.kind === 'pickup') {
269
+ return `${what} is not in stock at ${plan.location.name}. Choose another location, or have it shipped.`;
270
+ }
271
+ return `${what} is not in stock for local delivery. Choose pickup at another location, or have it shipped.`;
272
+ }
273
+ /**
274
+ * Reads a paid session's `fulfillment` metadata into order fields, or `null`
275
+ * for a shipped order. The location and zone are re-read so the order keeps
276
+ * their names as they were when the buyer paid.
277
+ */ export async function orderLocalFulfillmentFromSession(input) {
278
+ var _input_metadata, _metadata_orderTextPhone, _ref, _metadata_deliveryZoneId, _store_delivery_zones, _metadata_deliveryPostalCode, _store_delivery_country, _ref1, _metadata_deliveryLocationId, _ref2, _store_delivery_instructions;
279
+ var _input_shippingAddress, _input_shippingAddress1;
280
+ const metadata = (_input_metadata = input.metadata) != null ? _input_metadata : {};
281
+ const method = metadata['fulfillment'];
282
+ if (method !== 'pickup' && method !== 'local_delivery') return null;
283
+ const textPhone = normalizePhone(String((_metadata_orderTextPhone = metadata['orderTextPhone']) != null ? _metadata_orderTextPhone : ''));
284
+ const phone = textPhone ? {
285
+ customerPhone: textPhone
286
+ } : {};
287
+ const org = await getOrgForHost(input.hostId).catch(()=>null);
288
+ const store = await readLocalFulfillmentStore({
289
+ hostRef: input.hostRef,
290
+ org: (_ref = org == null ? void 0 : org.org) != null ? _ref : null
291
+ });
292
+ if (method === 'pickup') {
293
+ var _metadata_pickupLocationId, _location_instructions;
294
+ const locationId = String((_metadata_pickupLocationId = metadata['pickupLocationId']) != null ? _metadata_pickupLocationId : '');
295
+ let location = store.pickupLocations.find((entry)=>entry.id === locationId);
296
+ // A location renamed or switched off after the buyer paid: the order
297
+ // still goes there, under the name the location doc now has.
298
+ if (!location && locationId) {
299
+ const snapshot = await input.hostRef.collection('locations').doc(locationId).get().catch(()=>null);
300
+ const data = (snapshot == null ? void 0 : snapshot.exists) ? snapshot.data() : null;
301
+ location = _extends({
302
+ id: locationId,
303
+ name: String((data == null ? void 0 : data.name) || 'the store')
304
+ }, data ? {
305
+ address: CommerceModel.formatPostalAddressLine(data.postalAddress, data.address) || undefined
306
+ } : {});
307
+ }
308
+ if (!location) return null;
309
+ const pickup = _extends({
310
+ locationId: location.id,
311
+ locationName: location.name
312
+ }, location.address ? {
313
+ address: location.address
314
+ } : {}, location.instructions ? {
315
+ instructions: location.instructions
316
+ } : {}, location.hours ? {
317
+ hours: location.hours
318
+ } : {}, {
319
+ status: 'preparing',
320
+ updatedAtMs: input.createdAtMs
321
+ });
322
+ const order = {
323
+ fulfillmentMethod: 'pickup',
324
+ pickup,
325
+ createdAtMs: input.createdAtMs
326
+ };
327
+ const list = CommerceModel.orderLocalFulfillmentListFields(order);
328
+ const ready = CommerceModel.pickupReadyWithinLabel(location.readyWithinMinutes);
329
+ return {
330
+ fields: _extends({
331
+ fulfillmentMethod: 'pickup',
332
+ pickup,
333
+ locationId: location.id
334
+ }, phone, list),
335
+ summary: [
336
+ `Pickup at ${location.name}${location.address ? `, ${location.address}` : ''}.`,
337
+ `We’ll let you know when it’s ready${ready ? ` (${ready.toLowerCase()})` : ''}.`,
338
+ (_location_instructions = location.instructions) != null ? _location_instructions : ''
339
+ ].filter(Boolean).join(' '),
340
+ outsideZone: false
341
+ };
342
+ }
343
+ const zoneId = String((_metadata_deliveryZoneId = metadata['deliveryZoneId']) != null ? _metadata_deliveryZoneId : '');
344
+ const zone = ((_store_delivery_zones = store.delivery.zones) != null ? _store_delivery_zones : []).find((entry)=>entry.id === zoneId);
345
+ const windowStartMs = Number(metadata['deliveryWindowStartMs']) || 0;
346
+ const windowEndMs = Number(metadata['deliveryWindowEndMs']) || windowStartMs;
347
+ const declared = String((_metadata_deliveryPostalCode = metadata['deliveryPostalCode']) != null ? _metadata_deliveryPostalCode : '');
348
+ const country = (_store_delivery_country = store.delivery.country) != null ? _store_delivery_country : String((_ref1 = (_input_shippingAddress = input.shippingAddress) == null ? void 0 : _input_shippingAddress.country) != null ? _ref1 : '');
349
+ const entered = CommerceModel.normalizePostcode((_input_shippingAddress1 = input.shippingAddress) == null ? void 0 : _input_shippingAddress1.postalCode, country);
350
+ // The fee was charged for the code the buyer declared. The address they
351
+ // then typed at payment must be in a zone too — a zone of ANY fee, since
352
+ // the store delivers there — or the store is told.
353
+ const outsideZone = Boolean(entered && entered !== declared && !CommerceModel.matchLocalDeliveryZone(store.delivery, {
354
+ country,
355
+ postalCode: entered
356
+ }));
357
+ const windowLabel = windowStartMs ? CommerceModel.formatLocalDeliveryWindow({
358
+ startMs: windowStartMs,
359
+ endMs: windowEndMs
360
+ }, store.timeZone) : '';
361
+ const locationId = String((_metadata_deliveryLocationId = metadata['deliveryLocationId']) != null ? _metadata_deliveryLocationId : '') || undefined;
362
+ const localDelivery = _extends({
363
+ zoneId,
364
+ zoneName: (_ref2 = zone == null ? void 0 : zone.name) != null ? _ref2 : 'Local delivery',
365
+ feeCents: Math.max(0, Math.round(Number(metadata['deliveryFeeCents']) || 0)),
366
+ windowStartMs,
367
+ windowEndMs
368
+ }, windowLabel ? {
369
+ windowLabel
370
+ } : {}, declared ? {
371
+ postalCode: declared
372
+ } : {}, locationId ? {
373
+ locationId
374
+ } : {}, {
375
+ status: 'scheduled'
376
+ }, outsideZone ? {
377
+ addressOutsideZone: true
378
+ } : {}, {
379
+ updatedAtMs: input.createdAtMs
380
+ });
381
+ const order = {
382
+ fulfillmentMethod: 'local_delivery',
383
+ localDelivery,
384
+ createdAtMs: input.createdAtMs
385
+ };
386
+ const list = CommerceModel.orderLocalFulfillmentListFields(order);
387
+ return {
388
+ fields: _extends({
389
+ fulfillmentMethod: 'local_delivery',
390
+ localDelivery
391
+ }, locationId ? {
392
+ locationId
393
+ } : {}, phone, list),
394
+ summary: [
395
+ `Local delivery${windowLabel ? `: ${windowLabel}` : ''}.`,
396
+ (_store_delivery_instructions = store.delivery.instructions) != null ? _store_delivery_instructions : ''
397
+ ].filter(Boolean).join(' '),
398
+ outsideZone
399
+ };
400
+ }
401
+ // ---------------------------------------------------------------------------
402
+ // The cart's question: what can this basket be?
403
+ // ---------------------------------------------------------------------------
404
+ /** Distance zones place an address through a provider: a visitor may ask this often. */ const PLACE_LIMIT = {
405
+ limit: 20,
406
+ windowMs: 60 * 60 * 1000
407
+ };
408
+ /**
409
+ * `POST commerce/local-fulfillment-options` (AGL-3624): what the cart offers
410
+ * besides shipping — the pickup locations and, with a postal code, the
411
+ * delivery zone's fee, minimum and the windows open to book.
412
+ *
413
+ * Public, because a shopper asks it; it returns only what a storefront would
414
+ * print anyway (location names, addresses, hours, fees). It reads the
415
+ * visitor's own cart through the cart cookie and offers nothing for a
416
+ * basket with nothing physical in it.
417
+ */ export const localFulfillmentOptionsHandler = async (req, res)=>{
418
+ var _body_hostId;
419
+ if (req.method !== 'POST' && req.method !== 'GET') {
420
+ return res.status(405).json({
421
+ error: 'Method not allowed'
422
+ });
423
+ }
424
+ let body;
425
+ try {
426
+ var _req_body, _req_query;
427
+ body = req.method === 'POST' ? typeof req.body === 'string' ? JSON.parse(req.body || '{}') : (_req_body = req.body) != null ? _req_body : {} : (_req_query = req.query) != null ? _req_query : {};
428
+ } catch (unused) {
429
+ return res.status(400).json({
430
+ error: 'Invalid request'
431
+ });
432
+ }
433
+ const hostId = String((_body_hostId = body.hostId) != null ? _body_hostId : '');
434
+ if (!isDocumentId(hostId)) return res.status(400).json({
435
+ error: 'Missing or invalid hostId'
436
+ });
437
+ const nothing = {
438
+ pickup: [],
439
+ delivery: null
440
+ };
441
+ try {
442
+ var _cart_lines, _cart_lines1;
443
+ var _pluginSmsMessaging;
444
+ const firestore = firebaseAdmin.app().firestore();
445
+ const hostRef = firestore.collection('hosts').doc(hostId);
446
+ const ownerOrg = await getOrgForHost(hostId).catch(()=>null);
447
+ if (!(ownerOrg == null ? void 0 : ownerOrg.org) || !Aglyn.checkEntitlement(ownerOrg.org, 'commerce')) {
448
+ return res.status(200).json(nothing);
449
+ }
450
+ // The visitor's own basket decides whether there is anything to collect
451
+ // — or, from a product's Buy button, that one product (AGL-3624).
452
+ const buyNowProductId = typeof body.productId === 'string' ? body.productId : '';
453
+ let cart;
454
+ if (buyNowProductId) {
455
+ if (!isDocumentId(buyNowProductId)) return res.status(400).json({
456
+ error: 'Invalid productId'
457
+ });
458
+ cart = {
459
+ lines: [
460
+ _extends({
461
+ productId: buyNowProductId
462
+ }, typeof body.variantId === 'string' && body.variantId ? {
463
+ variantId: body.variantId
464
+ } : {}, {
465
+ quantity: Math.min(99, Math.max(1, Math.round(Number(body.quantity) || 1)))
466
+ })
467
+ ]
468
+ };
469
+ } else {
470
+ var _ref;
471
+ var _cartSnapshot_data;
472
+ const cartId = readCartId(req.cookies, hostId);
473
+ const cartSnapshot = cartId ? await hostRef.collection('carts').doc(cartId).get().catch(()=>null) : null;
474
+ cart = (_ref = cartSnapshot == null ? void 0 : (_cartSnapshot_data = cartSnapshot.data) == null ? void 0 : _cartSnapshot_data.call(cartSnapshot)) != null ? _ref : {
475
+ lines: []
476
+ };
477
+ }
478
+ const productIds = [
479
+ ...new Set(((_cart_lines = cart.lines) != null ? _cart_lines : []).map((line)=>line.productId))
480
+ ].slice(0, 50);
481
+ const products = await Promise.all(productIds.map((id)=>hostRef.collection('products').doc(id).get().catch(()=>null)));
482
+ let itemsCents = 0;
483
+ let hasPhysicalLine = false;
484
+ for (const line of (_cart_lines1 = cart.lines) != null ? _cart_lines1 : []){
485
+ var _ref1, _product_type;
486
+ const snapshot = products[productIds.indexOf(line.productId)];
487
+ if (!(snapshot == null ? void 0 : snapshot.exists)) continue;
488
+ const product = CommerceModel.liftLegacyProduct(snapshot.data());
489
+ const variant = line.variantId ? product.variants.find((item)=>item.id === line.variantId) : product.variants[0];
490
+ itemsCents += Math.round(Number((_ref1 = variant == null ? void 0 : variant.priceUsd) != null ? _ref1 : 0) * 100) * (Number(line.quantity) || 0);
491
+ if (((_product_type = product.type) != null ? _product_type : 'physical') === 'physical') hasPhysicalLine = true;
492
+ }
493
+ if (!hasPhysicalLine) return res.status(200).json(nothing);
494
+ const store = await readLocalFulfillmentStore({
495
+ hostRef,
496
+ org: ownerOrg.org
497
+ });
498
+ const nowMs = Date.now();
499
+ let delivery = null;
500
+ if (CommerceModel.localDeliveryOffered(store.delivery)) {
501
+ var _ref2;
502
+ var _request_address;
503
+ const settings = store.delivery;
504
+ const country = String(settings.country);
505
+ const windows = CommerceModel.upcomingLocalDeliveryWindows(settings, {
506
+ nowMs,
507
+ timeZone: store.timeZone
508
+ }).map((window)=>_extends({}, window, {
509
+ label: CommerceModel.formatLocalDeliveryWindow(window, store.timeZone)
510
+ }));
511
+ const radius = CommerceModel.localDeliveryHasRadiusZones(settings);
512
+ delivery = _extends({
513
+ country
514
+ }, settings.instructions ? {
515
+ instructions: settings.instructions
516
+ } : {}, {
517
+ needsAddress: radius,
518
+ windows
519
+ });
520
+ const request = readLocalFulfillmentRequest({
521
+ method: 'local_delivery',
522
+ postalCode: body.postalCode,
523
+ address: body.address
524
+ });
525
+ const postalCode = CommerceModel.normalizePostcode((_ref2 = request == null ? void 0 : request.postalCode) != null ? _ref2 : request == null ? void 0 : (_request_address = request.address) == null ? void 0 : _request_address.postalCode, country);
526
+ if (postalCode) {
527
+ var _request_address1;
528
+ let zone = CommerceModel.matchLocalDeliveryZone(settings, {
529
+ country,
530
+ postalCode
531
+ });
532
+ if (!zone && radius && (request == null ? void 0 : (_request_address1 = request.address) == null ? void 0 : _request_address1.line1)) {
533
+ var _ref3;
534
+ var _req_socket;
535
+ const visitor = String((_ref3 = (_req_socket = req.socket) == null ? void 0 : _req_socket.remoteAddress) != null ? _ref3 : '') || 'no-address';
536
+ const allowed = await consumeRateLimit(`local-delivery:place:${hostId}:${visitor}`, PLACE_LIMIT).then((result)=>result.allowed).catch(()=>false);
537
+ const coordinates = allowed ? await placeDeliveryAddress(hostId, _extends({}, request.address, {
538
+ postalCode,
539
+ country
540
+ })) : null;
541
+ zone = CommerceModel.matchLocalDeliveryZone(settings, {
542
+ country,
543
+ postalCode,
544
+ coordinates
545
+ });
546
+ }
547
+ delivery['quote'] = zone ? _extends({
548
+ zoneId: zone.id,
549
+ zoneName: zone.name,
550
+ feeCents: CommerceModel.localDeliveryFeeCents(zone, itemsCents)
551
+ }, zone.minimumCents ? {
552
+ minimumCents: zone.minimumCents
553
+ } : {}, zone.freeOverCents ? {
554
+ freeOverCents: zone.freeOverCents
555
+ } : {}, {
556
+ shortfallCents: CommerceModel.localDeliveryMinimumShortfall(zone, itemsCents)
557
+ }) : {
558
+ unavailable: `We don’t deliver to ${postalCode}.`
559
+ };
560
+ }
561
+ }
562
+ // Whether the cart may offer texts about the pickup or delivery: the
563
+ // platform can send them and the store has not turned them off.
564
+ const toggles = await hostRef.collection('settings').doc('store').get().then((snapshot)=>{
565
+ var _snapshot_get;
566
+ return snapshot == null ? void 0 : (_snapshot_get = snapshot.get) == null ? void 0 : _snapshot_get.call(snapshot, 'buyerNotifications');
567
+ }).catch(()=>null);
568
+ const texts = Boolean(((_pluginSmsMessaging = pluginSmsMessaging()) == null ? void 0 : _pluginSmsMessaging.isConfigured()) && CommerceModel.buyerNotificationEnabled(toggles, 'texts'));
569
+ return res.status(200).json({
570
+ pickup: store.pickupLocations,
571
+ delivery,
572
+ texts
573
+ });
574
+ } catch (error) {
575
+ console.error('[local-fulfillment] options failed', hostId, error);
576
+ return res.status(200).json(nothing);
577
+ }
578
+ };
579
+
580
+ //# sourceMappingURL=local-fulfillment.js.map