@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
@@ -15,6 +15,8 @@
15
15
  * limitations under the License.
16
16
  */
17
17
  import type { PluginApiHandler } from '@aglyn/aglyn/server';
18
+ /** A rewards or credit code no provider on this site takes (AGL-3640). */
19
+ export declare const CHECKOUT_CREDIT_INVALID_MESSAGE = "That rewards code is not valid.";
18
20
  /** An option the buyer ticked is no longer offered (AGL-3635). */
19
21
  export declare const CHECKOUT_EXTRA_UNAVAILABLE_MESSAGE = "An option you picked is no longer available. Review your cart and try again.";
20
22
  /**
@@ -14,7 +14,8 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
14
14
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
- */ import { readSiteReturnHost, siteReturnUrl } from "@aglyn/aglyn/app-utils/site-return-url";
17
+ */ import { randomUUID } from "crypto";
18
+ import { readSiteReturnHost, siteReturnUrl } from "@aglyn/aglyn/app-utils/site-return-url";
18
19
  import * as Aglyn from "@aglyn/aglyn/server";
19
20
  import * as CommerceModel from "../model/index.js";
20
21
  import { checkoutRecoveryState } from "../model/checkout-recovery.js";
@@ -29,11 +30,14 @@ import { allocateCentsByWeight, engineLineTaxPercentages, taxEngineSessionMetada
29
30
  import { quoteSaleTaxWithEngine } from "./tax-engine-quote.js";
30
31
  import { holdPromotionSlot, promotionHoldKey } from "./promotion-hold.js";
31
32
  import { holdStock, stockHoldKey } from "./stock-hold.js";
33
+ import { appendLocalFulfillmentMetadata, localFulfillmentLocationId, localStockRefusalMessage, planLocalFulfillment, readLocalFulfillmentRequest, readLocalFulfillmentStore } from "./local-fulfillment.js";
32
34
  import { applyNativeCheckoutParams, nativeCheckoutStripeHeaders, readCheckoutSessionPayload, resolveNativeCheckoutMode } from "./native-checkout.js";
33
35
  import { ensureCheckoutDomain } from "./payment-method-domains.js";
34
36
  import { raiseCheckoutStarted } from "./order-events.js";
35
37
  import { checkoutExtrasCents, encodeCheckoutExtrasMetadata, readChosenCheckoutExtras } from "@aglyn/aglyn/plugin-manager/plugin-checkout-extras";
36
38
  import { quoteCartExtras } from "./checkout-extras.js";
39
+ import { boundedCreditCents, checkoutCreditProviderForCode, encodeCheckoutCreditMetadata, normalizeCheckoutCreditAccount, normalizeCheckoutCreditCode } from "@aglyn/aglyn/plugin-manager/plugin-checkout-credits";
40
+ /** A rewards or credit code no provider on this site takes (AGL-3640). */ export const CHECKOUT_CREDIT_INVALID_MESSAGE = 'That rewards code is not valid.';
37
41
  /** An option the buyer ticked is no longer offered (AGL-3635). */ export const CHECKOUT_EXTRA_UNAVAILABLE_MESSAGE = 'An option you picked is no longer available. Review your cart and try again.';
38
42
  /**
39
43
  * Cart checkout (AGL-293): the whole cart in one Stripe Checkout
@@ -59,6 +63,8 @@ import { quoteCartExtras } from "./checkout-extras.js";
59
63
  const marketingOptIn = Boolean(body.marketingOptIn);
60
64
  const giftCardCode = String((_body_giftCardCode = body.giftCardCode) != null ? _body_giftCardCode : '').trim().toUpperCase().slice(0, 24);
61
65
  const couponCode = String((_body_couponCode = body.couponCode) != null ? _body_couponCode : '').trim().toUpperCase().slice(0, 40);
66
+ // A rewards or referral code another plugin keeps the account for (AGL-3640).
67
+ const creditCode = normalizeCheckoutCreditCode(body.creditCode);
62
68
  if (!hostId) return res.status(400).json({
63
69
  error: 'Missing hostId'
64
70
  });
@@ -97,7 +103,7 @@ import { quoteCartExtras } from "./checkout-extras.js";
97
103
  if (held) await held.release();
98
104
  };
99
105
  try {
100
- var _cartSnapshot_data, _ref1, _storeSettings_get, _storeSettings_get1, _req_headers_referer, _req_headers_host, _ref2, _ref3;
106
+ var _cartSnapshot_data, _ref1, _ref2, _storeSettings_get, _storeSettings_get1, _req_headers_referer, _req_headers_host, _ref3, _ref4;
101
107
  var _ownerOrg_org, _ownerOrg_org1, _ownerOrg_org2, _engineTax_quote;
102
108
  const firestore = firebaseAdmin.app().firestore();
103
109
  const hostRef = firestore.collection('hosts').doc(hostId);
@@ -288,7 +294,7 @@ import { quoteCartExtras } from "./checkout-extras.js";
288
294
  // visitor per day, lower while the workspace is young. Above the claim,
289
295
  // like the line refusals, so a refusal keeps the key.
290
296
  if (giftCardCents > 0) {
291
- var _ref4;
297
+ var _ref5;
292
298
  var _req_socket;
293
299
  const young = isYoungWorkspace(ownerOrg.org);
294
300
  const overCeiling = CommerceModel.giftCardPurchaseRefusal({
@@ -300,7 +306,7 @@ import { quoteCartExtras } from "./checkout-extras.js";
300
306
  visible: overCeiling
301
307
  });
302
308
  }
303
- const buys = await consumeRateLimit(`giftcard:buy:${hostId}:${String((_ref4 = (_req_socket = req.socket) == null ? void 0 : _req_socket.remoteAddress) != null ? _ref4 : '') || 'no-address'}`, {
309
+ const buys = await consumeRateLimit(`giftcard:buy:${hostId}:${String((_ref5 = (_req_socket = req.socket) == null ? void 0 : _req_socket.remoteAddress) != null ? _ref5 : '') || 'no-address'}`, {
304
310
  limit: young ? CommerceModel.GIFT_CARD_PURCHASE_LIMITS.perVisitorDailyYoung : CommerceModel.GIFT_CARD_PURCHASE_LIMITS.perVisitorDaily,
305
311
  windowMs: 24 * 60 * 60 * 1000
306
312
  });
@@ -325,10 +331,48 @@ import { quoteCartExtras } from "./checkout-extras.js";
325
331
  // session byte-identical to today's: no shipping charged, exactly as
326
332
  // before. Only a merchant who set rates up starts collecting them.
327
333
  const storeSettings = await hostRef.collection('settings').doc('store').get();
334
+ // PICKUP OR THE STORE'S OWN DELIVERY (AGL-3624), declared at the cart and
335
+ // decided here from the store's settings: the location, the zone's fee and
336
+ // minimum, the window. Above the claim, so a refusal keeps the key and the
337
+ // shopper's same button works once they choose again. See
338
+ // `local-fulfillment.ts`.
339
+ const localRequest = readLocalFulfillmentRequest(body.fulfillment);
340
+ const localStore = hasPhysicalLine ? await readLocalFulfillmentStore({
341
+ hostRef,
342
+ storeSettings: (_ref1 = storeSettings.data == null ? void 0 : storeSettings.data.call(storeSettings)) != null ? _ref1 : null,
343
+ org: ownerOrg.org
344
+ }) : null;
345
+ const localPlan = localStore ? await planLocalFulfillment({
346
+ hostId,
347
+ request: localRequest,
348
+ itemsCents,
349
+ hasPhysicalLine,
350
+ store: localStore
351
+ }) : {
352
+ kind: 'shipping'
353
+ };
354
+ if (localPlan.kind === 'refusal') {
355
+ return res.status(localPlan.status).json(_extends({
356
+ error: localPlan.error
357
+ }, localPlan.changed ? {
358
+ fulfillmentChanged: localPlan.changed
359
+ } : {}));
360
+ }
328
361
  // A live carrier rate (AGL-3612) is quoted here too, before the session,
329
362
  // for the destination the shopper declared; a store with none plans from
330
363
  // its table exactly as before. See `carrier-shipping.ts`.
331
- const shippingPlan = hasPhysicalLine ? await planCheckoutShippingWithCarriers({
364
+ const shippingPlan = // Collected: nothing to price, no address to ask for.
365
+ localPlan.kind === 'pickup' ? {
366
+ countries: [],
367
+ options: []
368
+ } : localPlan.kind === 'local_delivery' ? {
369
+ countries: [
370
+ localPlan.country
371
+ ],
372
+ options: [
373
+ localPlan.option
374
+ ]
375
+ } : hasPhysicalLine ? await planCheckoutShippingWithCarriers({
332
376
  hostId,
333
377
  settings: storeSettings.get('shipping'),
334
378
  cart: {
@@ -343,6 +387,12 @@ import { quoteCartExtras } from "./checkout-extras.js";
343
387
  countries: CommerceModel.CHECKOUT_SHIPPING_COUNTRIES,
344
388
  options: []
345
389
  };
390
+ // A store whose locations offer pickup has the shopper choose WHERE at
391
+ // the cart (AGL-3624); the old unnamed `Local pickup` rate would be a
392
+ // second, unrouted pickup inside a session the shopper chose shipping for.
393
+ if (localPlan.kind === 'shipping' && (localStore == null ? void 0 : localStore.pickupLocations.length)) {
394
+ shippingPlan.options = shippingPlan.options.filter((option)=>option.rateId !== CommerceModel.LOCAL_PICKUP_RATE_ID);
395
+ }
346
396
  if (shippingPlan.needsPostalCode) {
347
397
  return res.status(400).json({
348
398
  error: CARRIER_POSTAL_CODE_MESSAGE,
@@ -458,7 +508,7 @@ import { quoteCartExtras } from "./checkout-extras.js";
458
508
  const claimed = await claimAttempt(firestore, {
459
509
  kind: 'commerce-cart',
460
510
  scopeId: `${hostId}:${cartId}`,
461
- orgId: String((_ref1 = ownerOrg == null ? void 0 : (_ownerOrg_org1 = ownerOrg.org) == null ? void 0 : _ownerOrg_org1.id) != null ? _ref1 : ''),
511
+ orgId: String((_ref2 = ownerOrg == null ? void 0 : (_ownerOrg_org1 = ownerOrg.org) == null ? void 0 : _ownerOrg_org1.id) != null ? _ref2 : ''),
462
512
  key: idempotencyKey,
463
513
  busyMessage: 'This checkout is already being processed'
464
514
  });
@@ -499,7 +549,9 @@ import { quoteCartExtras } from "./checkout-extras.js";
499
549
  hostRef,
500
550
  holdKey: stockHoldKey(claimed.claim.stripeKey),
501
551
  lines: reserveLines,
502
- label: `cart ${hostId}/${cartId}`
552
+ label: `cart ${hostId}/${cartId}`,
553
+ // At the pickup location, or the one deliveries leave from (AGL-3624).
554
+ locationId: localFulfillmentLocationId(localPlan)
503
555
  });
504
556
  if (!held.ok) {
505
557
  // Nothing has been minted yet, so the key goes back and the same
@@ -510,7 +562,7 @@ import { quoteCartExtras } from "./checkout-extras.js";
510
562
  await releaseStock();
511
563
  await claim.release();
512
564
  return res.status(409).json({
513
- error: held.reason === 'sold-out' ? held.productName ? `"${held.productName}" — ${CommerceModel.STOCK_HELD_MESSAGE}` : CommerceModel.STOCK_HELD_MESSAGE : 'Your cart is no longer available'
565
+ error: held.reason === 'sold-out-at-location' ? localStockRefusalMessage(localPlan, held.productName) : held.reason === 'sold-out' ? held.productName ? `"${held.productName}" — ${CommerceModel.STOCK_HELD_MESSAGE}` : CommerceModel.STOCK_HELD_MESSAGE : 'Your cart is no longer available'
514
566
  });
515
567
  }
516
568
  if (held.holdKey) {
@@ -613,11 +665,11 @@ import { quoteCartExtras } from "./checkout-extras.js";
613
665
  })) : shippingPlan.options;
614
666
  // Coupons (AGL-96 semantics): percent off the items total.
615
667
  if (couponCode && !resolvedDiscount) {
616
- var _ref5;
668
+ var _ref6;
617
669
  const couponRef = hostRef.collection('coupons').doc(couponCode);
618
670
  const couponSnapshot = await couponRef.get();
619
671
  const coupon = couponSnapshot.data();
620
- const percentOff = Number((_ref5 = coupon == null ? void 0 : coupon.percentOff) != null ? _ref5 : 0);
672
+ const percentOff = Number((_ref6 = coupon == null ? void 0 : coupon.percentOff) != null ? _ref6 : 0);
621
673
  const expired = (coupon == null ? void 0 : coupon.expiresAtMs) != null && Number(coupon.expiresAtMs) < Date.now();
622
674
  if (!coupon || coupon.enabled === false || expired || !(percentOff > 0 && percentOff <= 100)) {
623
675
  // Deterministic and retryable — the shopper fixes the code and
@@ -767,13 +819,98 @@ import { quoteCartExtras } from "./checkout-extras.js";
767
819
  };
768
820
  }
769
821
  }
822
+ // STORE CREDIT ANOTHER PLUGIN KEEPS (AGL-3640) — a rewards balance, a
823
+ // friend's referral credit — by its code, through core's checkout-credits
824
+ // seam; this plugin never reads the account. Held exactly like the gift
825
+ // card above: against what is LEFT after every other reduction, BEFORE
826
+ // Stripe is contacted, keyed by this attempt so a retry re-places the same
827
+ // hold. The webhook takes it in the transaction that writes the order, and
828
+ // a session that expires lets it go.
829
+ let releaseCreditHold = async ()=>undefined;
830
+ let creditHeldCents = 0;
831
+ if (creditCode) {
832
+ const refuseCredit = async (status, error)=>{
833
+ await releaseGiftCardHold();
834
+ await releasePromotionHolds();
835
+ await releaseStock();
836
+ await (claim == null ? void 0 : claim.release());
837
+ return res.status(status).json({
838
+ error,
839
+ creditCodeInvalid: true
840
+ });
841
+ };
842
+ const entry = checkoutCreditProviderForCode(creditCode);
843
+ const offered = entry ? await entry.provider.offered({
844
+ hostId,
845
+ channel: 'online'
846
+ }).catch(()=>false) : false;
847
+ if (!entry || !offered) return refuseCredit(400, CHECKOUT_CREDIT_INVALID_MESSAGE);
848
+ const account = normalizeCheckoutCreditAccount(await entry.provider.resolve({
849
+ hostId,
850
+ code: creditCode,
851
+ channel: 'online',
852
+ customerEmail: email || null,
853
+ staff: false
854
+ }).catch((error)=>{
855
+ console.error('Checkout credit resolve failed', entry.providerId, error);
856
+ return null;
857
+ }));
858
+ if ('error' in account) return refuseCredit(account.status, account.error);
859
+ const maxCents = Math.max(0, itemsCents - totalOffCents);
860
+ // The attempt's key, so a retry re-places the same hold; a request
861
+ // that sent none (an older client) still needs a key of its own.
862
+ const holdKey = claim.stripeKey || `cart:${cartId}:${randomUUID()}`;
863
+ const held = await entry.provider.hold({
864
+ hostId,
865
+ reference: account.reference,
866
+ holdKey,
867
+ maxCents,
868
+ currency: 'usd',
869
+ customerEmail: email || null,
870
+ nowMs: Date.now()
871
+ }).catch((error)=>{
872
+ console.error('Checkout credit hold failed', entry.providerId, error);
873
+ return {
874
+ ok: false,
875
+ status: 409,
876
+ error: 'That rewards code cannot be used right now. Try again.'
877
+ };
878
+ });
879
+ if ('error' in held) {
880
+ return refuseCredit(held.status >= 400 && held.status < 500 ? held.status : 409, held.error);
881
+ }
882
+ const release = async ()=>{
883
+ await entry.provider.release({
884
+ hostId,
885
+ reference: account.reference,
886
+ holdKey
887
+ });
888
+ };
889
+ creditHeldCents = boundedCreditCents(held.cents, maxCents);
890
+ if (creditHeldCents > 0) {
891
+ totalOffCents += creditHeldCents;
892
+ for (const [key, value] of Object.entries(encodeCheckoutCreditMetadata({
893
+ providerId: entry.providerId,
894
+ reference: account.reference,
895
+ holdKey,
896
+ amountCents: creditHeldCents,
897
+ label: account.label,
898
+ last4: account.last4
899
+ }))){
900
+ params.set(`metadata[${key}]`, value);
901
+ }
902
+ releaseCreditHold = release;
903
+ } else {
904
+ await release();
905
+ }
906
+ }
770
907
  if (totalOffCents > 0) {
771
908
  const stripeCoupon = await fetch('https://api.stripe.com/v1/coupons', {
772
909
  method: 'POST',
773
910
  headers: _extends({
774
911
  Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,
775
912
  'Content-Type': 'application/x-www-form-urlencoded'
776
- }, stripeKeyHeader('coupon')),
913
+ }, stripeKeyHeader(creditHeldCents > 0 ? `coupon-c${creditHeldCents}` : 'coupon')),
777
914
  body: new URLSearchParams({
778
915
  amount_off: String(Math.min(totalOffCents, itemsCents)),
779
916
  currency: 'usd',
@@ -785,6 +922,20 @@ import { quoteCartExtras } from "./checkout-extras.js";
785
922
  // Scaled ONCE, by the reduction that actually reached the session.
786
923
  feeCents = Math.round(feeCents * Math.max(0, itemsCents - totalOffCents) / Math.max(1, itemsCents));
787
924
  chargedItemsCents = Math.max(0, itemsCents - totalOffCents);
925
+ } else if (creditHeldCents > 0) {
926
+ // A rewards balance that did not reach the price must not be taken
927
+ // from the member when the session is paid (AGL-3640): refuse, and
928
+ // hand back everything this attempt held, rather than sell at full
929
+ // price with their credit spent.
930
+ console.error('Stripe coupon error', stripeCoupon == null ? void 0 : stripeCoupon.error);
931
+ await releaseGiftCardHold();
932
+ await releaseCreditHold();
933
+ await releasePromotionHolds();
934
+ await releaseStock();
935
+ await claim.release();
936
+ return res.status(502).json({
937
+ error: 'Checkout failed'
938
+ });
788
939
  }
789
940
  }
790
941
  // THE FLOOR (AGL-2232). A rate above zero on goods the shopper actually
@@ -915,6 +1066,7 @@ import { quoteCartExtras } from "./checkout-extras.js";
915
1066
  // BELOW the claim on this path, so release it: the merchant fixes the
916
1067
  // setting and the shopper retries under the same key (AGL-1697).
917
1068
  await releaseGiftCardHold();
1069
+ await releaseCreditHold();
918
1070
  await releasePromotionHolds();
919
1071
  await releaseStock();
920
1072
  await claim.release();
@@ -930,6 +1082,7 @@ import { quoteCartExtras } from "./checkout-extras.js";
930
1082
  // the undecided case above.
931
1083
  // Below the claim here too, so release it for the same reason.
932
1084
  await releaseGiftCardHold();
1085
+ await releaseCreditHold();
933
1086
  await releasePromotionHolds();
934
1087
  await releaseStock();
935
1088
  await claim.release();
@@ -968,6 +1121,7 @@ import { quoteCartExtras } from "./checkout-extras.js";
968
1121
  // The same visible refusal as the store's own rate: never an
969
1122
  // untaxed session.
970
1123
  await releaseGiftCardHold();
1124
+ await releaseCreditHold();
971
1125
  await releasePromotionHolds();
972
1126
  await releaseStock();
973
1127
  await claim.release();
@@ -1012,6 +1166,7 @@ import { quoteCartExtras } from "./checkout-extras.js";
1012
1166
  // (AGL-1697); the retry re-derives the same digest, so Stripe
1013
1167
  // replays any rate the first run did mint.
1014
1168
  await releaseGiftCardHold();
1169
+ await releaseCreditHold();
1015
1170
  await releasePromotionHolds();
1016
1171
  await releaseStock();
1017
1172
  await claim.release();
@@ -1066,6 +1221,10 @@ import { quoteCartExtras } from "./checkout-extras.js";
1066
1221
  shippingFloorCents: cartShippingFloorCents
1067
1222
  })).forEach(([key, value])=>params.set(key, value));
1068
1223
  params.set('metadata[type]', 'commerce-cart');
1224
+ // How the order reaches the buyer (AGL-3624): the webhook routes it.
1225
+ if (localPlan.kind === 'pickup' || localPlan.kind === 'local_delivery') {
1226
+ appendLocalFulfillmentMetadata(params, localPlan.metadata);
1227
+ }
1069
1228
  for (const [key, value] of Object.entries(taxEngineSessionMetadata(engineTax.stamp))){
1070
1229
  params.set(key, value);
1071
1230
  }
@@ -1112,7 +1271,7 @@ import { quoteCartExtras } from "./checkout-extras.js";
1112
1271
  // and is identical on both checkout paths. This swaps the redirect's URL
1113
1272
  // pair for `ui_mode` + `return_url` and stops. Off unless the flag is on for
1114
1273
  // this org AND a publishable key exists; see `resolveNativeCheckoutMode`.
1115
- const nativeMode = await resolveNativeCheckoutMode(String((_ref2 = ownerOrg == null ? void 0 : (_ownerOrg_org2 = ownerOrg.org) == null ? void 0 : _ownerOrg_org2.id) != null ? _ref2 : ''));
1274
+ const nativeMode = await resolveNativeCheckoutMode(String((_ref3 = ownerOrg == null ? void 0 : (_ownerOrg_org2 = ownerOrg.org) == null ? void 0 : _ownerOrg_org2.id) != null ? _ref3 : ''));
1116
1275
  if (nativeMode.native) {
1117
1276
  applyNativeCheckoutParams(params, `${backUrl}${separator}order=success&session_id={CHECKOUT_SESSION_ID}`);
1118
1277
  }
@@ -1128,7 +1287,7 @@ import { quoteCartExtras } from "./checkout-extras.js";
1128
1287
  headers: _extends({
1129
1288
  Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,
1130
1289
  'Content-Type': 'application/x-www-form-urlencoded'
1131
- }, stripeKeyHeader(`${engineTax.stamp ? `session-tax-${(_ref3 = (_engineTax_quote = engineTax.quote) == null ? void 0 : _engineTax_quote.taxCents) != null ? _ref3 : 'own'}-${engineTax.stamp.status}` : 'session'}${extrasCents > 0 ? `-x${extrasCents}` : ''}`), nativeCheckoutStripeHeaders(nativeMode)),
1290
+ }, stripeKeyHeader(`${engineTax.stamp ? `session-tax-${(_ref4 = (_engineTax_quote = engineTax.quote) == null ? void 0 : _engineTax_quote.taxCents) != null ? _ref4 : 'own'}-${engineTax.stamp.status}` : 'session'}${extrasCents > 0 ? `-x${extrasCents}` : ''}${creditHeldCents > 0 ? `-c${creditHeldCents}` : ''}`), nativeCheckoutStripeHeaders(nativeMode)),
1132
1291
  body: params.toString()
1133
1292
  });
1134
1293
  const session = await response.json();
@@ -1144,6 +1303,7 @@ import { quoteCartExtras } from "./checkout-extras.js";
1144
1303
  // does not lock this cart out. The retry re-derives the same derived
1145
1304
  // keys, so Stripe replays whatever objects the first run did create.
1146
1305
  await releaseGiftCardHold();
1306
+ await releaseCreditHold();
1147
1307
  await releasePromotionHolds();
1148
1308
  await releaseStock();
1149
1309
  await claim.release();