@aglyn/plugins-commerce 1.0.0-beta.234 → 1.0.0-beta.236

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 (135) hide show
  1. package/package.json +13 -13
  2. package/src/lib/components/cart.js +3 -2
  3. package/src/lib/components/cart.js.map +1 -1
  4. package/src/lib/components/console/commerce-analytics-card.component.js +3 -6
  5. package/src/lib/components/console/commerce-analytics-card.component.js.map +1 -1
  6. package/src/lib/components/console/commerce-glance-card.component.js +9 -3
  7. package/src/lib/components/console/commerce-glance-card.component.js.map +1 -1
  8. package/src/lib/components/console/customer-notifications-card.component.js +3 -1
  9. package/src/lib/components/console/customer-notifications-card.component.js.map +1 -1
  10. package/src/lib/components/console/discounts-card.component.js +2 -3
  11. package/src/lib/components/console/discounts-card.component.js.map +1 -1
  12. package/src/lib/components/console/host-coupons-card.component.js +3 -4
  13. package/src/lib/components/console/host-coupons-card.component.js.map +1 -1
  14. package/src/lib/components/console/local-delivery-settings-card.component.js +2 -2
  15. package/src/lib/components/console/local-delivery-settings-card.component.js.map +1 -1
  16. package/src/lib/components/console/locations-card.component.js +1 -1
  17. package/src/lib/components/console/locations-card.component.js.map +1 -1
  18. package/src/lib/components/console/member-posts-card.component.js +2 -2
  19. package/src/lib/components/console/member-posts-card.component.js.map +1 -1
  20. package/src/lib/components/console/order-detail-dialog.component.js +35 -30
  21. package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
  22. package/src/lib/components/console/order-fulfillment-panel.component.d.ts +11 -1
  23. package/src/lib/components/console/order-fulfillment-panel.component.js +75 -4
  24. package/src/lib/components/console/order-fulfillment-panel.component.js.map +1 -1
  25. package/src/lib/components/console/order-zones.d.ts +45 -1
  26. package/src/lib/components/console/order-zones.js +8 -0
  27. package/src/lib/components/console/order-zones.js.map +1 -1
  28. package/src/lib/components/console/pickup-delivery-queue-card.component.js +15 -1
  29. package/src/lib/components/console/pickup-delivery-queue-card.component.js.map +1 -1
  30. package/src/lib/components/console/pos-page.component.js +3 -2
  31. package/src/lib/components/console/pos-page.component.js.map +1 -1
  32. package/src/lib/components/console/product-editor-dialog.component.d.ts +2 -0
  33. package/src/lib/components/console/product-editor-dialog.component.js +30 -12
  34. package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
  35. package/src/lib/components/console/products-hub-card.component.js +13 -4
  36. package/src/lib/components/console/products-hub-card.component.js.map +1 -1
  37. package/src/lib/components/console/products-hub-zone.component.d.ts +2 -1
  38. package/src/lib/components/console/products-hub-zone.component.js +1 -1
  39. package/src/lib/components/console/products-hub-zone.component.js.map +1 -1
  40. package/src/lib/components/console/reviews-moderation-card.component.js +1 -2
  41. package/src/lib/components/console/reviews-moderation-card.component.js.map +1 -1
  42. package/src/lib/components/console/shipping-settings-card.component.js +2 -2
  43. package/src/lib/components/console/shipping-settings-card.component.js.map +1 -1
  44. package/src/lib/components/console/shippingeasy-card.component.js +1 -1
  45. package/src/lib/components/console/shippingeasy-card.component.js.map +1 -1
  46. package/src/lib/components/console/shipstation-card.component.js +1 -2
  47. package/src/lib/components/console/shipstation-card.component.js.map +1 -1
  48. package/src/lib/components/console/store-settings-card.component.js +3 -1
  49. package/src/lib/components/console/store-settings-card.component.js.map +1 -1
  50. package/src/lib/components/console/tax-settings-card.component.js +4 -1
  51. package/src/lib/components/console/tax-settings-card.component.js.map +1 -1
  52. package/src/lib/components/order-status.js +24 -1
  53. package/src/lib/components/order-status.js.map +1 -1
  54. package/src/lib/components/product-detail.js +30 -16
  55. package/src/lib/components/product-detail.js.map +1 -1
  56. package/src/lib/components/product-grid.d.ts +7 -0
  57. package/src/lib/components/product-grid.js +50 -7
  58. package/src/lib/components/product-grid.js.map +1 -1
  59. package/src/lib/components/related-products.js +1 -1
  60. package/src/lib/components/related-products.js.map +1 -1
  61. package/src/lib/components/wishlist.js +1 -1
  62. package/src/lib/components/wishlist.js.map +1 -1
  63. package/src/lib/declarations.server.js +11 -0
  64. package/src/lib/declarations.server.js.map +1 -1
  65. package/src/lib/model/commerce-orders.d.ts +8 -0
  66. package/src/lib/model/commerce-orders.js.map +1 -1
  67. package/src/lib/model/commerce.d.ts +21 -0
  68. package/src/lib/model/commerce.js +39 -1
  69. package/src/lib/model/commerce.js.map +1 -1
  70. package/src/lib/model/index.d.ts +1 -0
  71. package/src/lib/model/index.js +1 -0
  72. package/src/lib/model/index.js.map +1 -1
  73. package/src/lib/model/order-annotations.d.ts +54 -0
  74. package/src/lib/model/order-annotations.js +76 -0
  75. package/src/lib/model/order-annotations.js.map +1 -0
  76. package/src/lib/model/order-events.d.ts +2 -0
  77. package/src/lib/model/order-events.js.map +1 -1
  78. package/src/lib/model/order-fulfillment.d.ts +29 -0
  79. package/src/lib/model/order-fulfillment.js +63 -0
  80. package/src/lib/model/order-fulfillment.js.map +1 -1
  81. package/src/lib/model/order-local-fulfillment.d.ts +36 -0
  82. package/src/lib/model/order-local-fulfillment.js +19 -1
  83. package/src/lib/model/order-local-fulfillment.js.map +1 -1
  84. package/src/lib/model/order-status-view.d.ts +11 -0
  85. package/src/lib/model/order-status-view.js +20 -3
  86. package/src/lib/model/order-status-view.js.map +1 -1
  87. package/src/lib/model/product-write.js +8 -4
  88. package/src/lib/model/product-write.js.map +1 -1
  89. package/src/lib/server/api-v1/openapi.js +1 -0
  90. package/src/lib/server/api-v1/openapi.js.map +1 -1
  91. package/src/lib/server/api-v1/order-view.d.ts +1 -0
  92. package/src/lib/server/api-v1/order-view.js +10 -0
  93. package/src/lib/server/api-v1/order-view.js.map +1 -1
  94. package/src/lib/server/cart-checkout.js +9 -1
  95. package/src/lib/server/cart-checkout.js.map +1 -1
  96. package/src/lib/server/catalog.d.ts +6 -0
  97. package/src/lib/server/catalog.js +3 -1
  98. package/src/lib/server/catalog.js.map +1 -1
  99. package/src/lib/server/checkout.js +11 -1
  100. package/src/lib/server/checkout.js.map +1 -1
  101. package/src/lib/server/fulfill-order.d.ts +12 -0
  102. package/src/lib/server/fulfill-order.js +13 -0
  103. package/src/lib/server/fulfill-order.js.map +1 -1
  104. package/src/lib/server/local-delivery-records.d.ts +30 -0
  105. package/src/lib/server/local-delivery-records.js +284 -0
  106. package/src/lib/server/local-delivery-records.js.map +1 -0
  107. package/src/lib/server/order-annotations.d.ts +28 -0
  108. package/src/lib/server/order-annotations.js +143 -0
  109. package/src/lib/server/order-annotations.js.map +1 -0
  110. package/src/lib/server/order-events.js +2 -1
  111. package/src/lib/server/order-events.js.map +1 -1
  112. package/src/lib/server/order-notifications.js +39 -8
  113. package/src/lib/server/order-notifications.js.map +1 -1
  114. package/src/lib/server/pos-payment.js +9 -3
  115. package/src/lib/server/pos-payment.js.map +1 -1
  116. package/src/lib/server/product-catalog.js +6 -2
  117. package/src/lib/server/product-catalog.js.map +1 -1
  118. package/src/lib/server/product-drafts.d.ts +36 -9
  119. package/src/lib/server/product-drafts.js +24 -5
  120. package/src/lib/server/product-drafts.js.map +1 -1
  121. package/src/lib/server/product.d.ts +6 -1
  122. package/src/lib/server/product.js +1 -1
  123. package/src/lib/server/product.js.map +1 -1
  124. package/src/lib/server/products-write.d.ts +29 -0
  125. package/src/lib/server/products-write.js +7 -7
  126. package/src/lib/server/products-write.js.map +1 -1
  127. package/src/lib/server/shipment-records.js +2 -0
  128. package/src/lib/server/shipment-records.js.map +1 -1
  129. package/src/lib/server/site-page-resolver.d.ts +13 -0
  130. package/src/lib/server/site-page-resolver.js +138 -1
  131. package/src/lib/server/site-page-resolver.js.map +1 -1
  132. package/src/lib/server.js +5 -0
  133. package/src/lib/server.js.map +1 -1
  134. package/src/lib/utils/use-storefront-purchase-event.js +8 -2
  135. package/src/lib/utils/use-storefront-purchase-event.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/cart-checkout.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 { randomUUID } from 'crypto'\nimport { readSiteReturnHost, siteReturnUrl } from '@aglyn/aglyn/app-utils/site-return-url'\nimport type { AttemptClaim, PluginApiHandler } from '@aglyn/aglyn/server'\nimport * as Aglyn from '@aglyn/aglyn/server'\nimport * as CommerceModel from '../model'\nimport { checkoutRecoveryState } from '../model/checkout-recovery'\nimport { claimAttempt, deriveStripeObjectKey } from '@aglyn/aglyn/server'\nimport {\n consumeRateLimit,\n firebaseAdmin,\n getOrgForHost,\n isYoungWorkspace,\n} from '@aglyn/tenant-data-admin'\nimport { merchantAccountIsReady } from '@aglyn/tenant-data-admin/server/payment-provider'\nimport { checkoutSessionCardAuthenticationParams } from '@aglyn/tenant-data-admin/server/stripe-card-authentication'\nimport {\n CARRIER_POSTAL_CODE_MESSAGE,\n planCheckoutShippingWithCarriers,\n type CarrierShippingPlan,\n} from './carrier-shipping'\nimport { readCartId } from './cart-cookie'\nimport { resolveManualTaxRateId } from './manual-tax-rate'\nimport {\n allocateCentsByWeight,\n engineLineTaxPercentages,\n taxEngineSessionMetadata,\n} from '../model/commerce-tax-engine'\nimport { quoteSaleTaxWithEngine } from './tax-engine-quote'\nimport {\n type PromotionSlotHold,\n holdPromotionSlot,\n promotionHoldKey,\n} from './promotion-hold'\nimport { type StockHoldLine, holdStock, stockHoldKey } from './stock-hold'\nimport {\n appendLocalFulfillmentMetadata,\n localFulfillmentLocationId,\n localStockRefusalMessage,\n planLocalFulfillment,\n readLocalFulfillmentRequest,\n readLocalFulfillmentStore,\n} from './local-fulfillment'\nimport {\n applyNativeCheckoutParams,\n nativeCheckoutStripeHeaders,\n readCheckoutSessionPayload,\n resolveNativeCheckoutMode,\n} from './native-checkout'\nimport { ensureCheckoutDomain } from './payment-method-domains'\nimport { raiseCheckoutStarted } from './order-events'\nimport {\n checkoutExtrasCents,\n encodeCheckoutExtrasMetadata,\n readChosenCheckoutExtras,\n} from '@aglyn/aglyn/plugin-manager/plugin-checkout-extras'\nimport { quoteCartExtras, type CheckoutExtrasLine } from './checkout-extras'\nimport {\n boundedCreditCents,\n checkoutCreditProviderForCode,\n encodeCheckoutCreditMetadata,\n normalizeCheckoutCreditAccount,\n normalizeCheckoutCreditCode,\n} from '@aglyn/aglyn/plugin-manager/plugin-checkout-credits'\n\n/** A rewards or credit code no provider on this site takes (AGL-3640). */\nexport const CHECKOUT_CREDIT_INVALID_MESSAGE = 'That rewards code is not valid.'\n\n/** An option the buyer ticked is no longer offered (AGL-3635). */\nexport const CHECKOUT_EXTRA_UNAVAILABLE_MESSAGE =\n 'An option you picked is no longer available. Review your cart and try again.'\n\n/**\n * Cart checkout (AGL-293): the whole cart in one Stripe Checkout\n * session on the merchant's account. Every line re-prices from its\n * product doc, unavailable lines block with a visible error, the\n * platform fee sums per line by product type (AGL-278 ladder), and the\n * webhook creates one multi-line order and clears the cart.\n */\n\nexport const cartCheckoutHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n if (!process.env.STRIPE_SECRET_KEY) {\n return res\n .status(501)\n .json({ error: 'Purchases are not configured on this site.' })\n }\n const body =\n typeof req.body === 'string' ? JSON.parse(req.body) : (req.body ?? {})\n const hostId = String(body.hostId ?? '')\n const email = String(body.email ?? '').trim().toLowerCase().slice(0, 120)\n const marketingOptIn = Boolean(body.marketingOptIn)\n const giftCardCode = String(body.giftCardCode ?? '')\n .trim()\n .toUpperCase()\n .slice(0, 24)\n const couponCode = String(body.couponCode ?? '')\n .trim()\n .toUpperCase()\n .slice(0, 40)\n // A rewards or referral code another plugin keeps the account for (AGL-3640).\n const creditCode = normalizeCheckoutCreditCode(body.creditCode)\n if (!hostId) return res.status(400).json({ error: 'Missing hostId' })\n // AGL-1769: validated here even though this handler only READS the cart,\n // because it is where the raw cookie left the request — `:342` stamps it\n // into `metadata[cartId]`, and the billing webhook builds a document path\n // from that copy. Closing it at the source is what lets the webhook keep\n // taking its metadata at face value.\n const cartId = readCartId(req.cookies, hostId)\n if (!cartId) return res.status(400).json({ error: 'Your cart is empty' })\n // One checkout attempt, minted by the cart drawer (AGL-1697). Node\n // lowercases incoming headers, but read both spellings — the plugin API\n // request type makes no promise about casing.\n const idempotencyKey = String(\n req.headers['idempotency-key'] ?? req.headers['Idempotency-Key'] ?? '',\n ).trim().slice(0, 200)\n\n let claim: AttemptClaim | null = null\n /**\n * Promotion slots this attempt reserved (AGL-2453), hoisted so the catch\n * below can hand them back too. A throw after a hold is placed is exactly the\n * case where an invisible slot would sit against a merchant's cap for a day.\n */\n const heldPromotions: PromotionSlotHold[] = []\n const releasePromotionHolds = async (): Promise<void> => {\n for (const hold of heldPromotions.splice(0)) await hold.release()\n }\n /**\n * The units this attempt reserved (AGL-2356), hoisted for the same reason —\n * a throw after the hold is placed is exactly the case where a merchant's\n * last unit would sit invisibly spoken for until the TTL lapsed.\n */\n let stockHold: { holdKey: string; release: () => Promise<void> } | null = null\n let heldStockKey = ''\n const releaseStock = async (): Promise<void> => {\n const held = stockHold\n stockHold = null\n heldStockKey = ''\n if (held) await held.release()\n }\n try {\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const cartSnapshot = await hostRef.collection('carts').doc(cartId).get()\n const cart = (cartSnapshot.data() as CommerceModel.HostCart | undefined) ?? {\n lines: [],\n }\n if (cart.lines.length === 0) {\n return res.status(400).json({ error: 'Your cart is empty' })\n }\n\n const ownerOrg = await getOrgForHost(hostId)\n const ownerId = ownerOrg?.org?.ownerUid\n if (!ownerId) {\n return res.status(409).json({ error: 'This site cannot sell yet' })\n }\n // Storefront selling is the `commerce` entitlement (Starter+) — not\n // `marketplaceSelling`, which gates the marketplace marketplace (AGL-470).\n if (!Aglyn.checkEntitlement(ownerOrg.org as any, 'commerce')) {\n return res.status(403).json({ error: 'Selling is not enabled' })\n }\n const ownerProfile = await firestore\n .collection('profiles')\n .doc(String(ownerId))\n .get()\n const accountId = ownerProfile.get('stripeAccountId')\n if (\n !merchantAccountIsReady(\n {\n accountId,\n chargesEnabled: ownerProfile.get('stripeChargesEnabled'),\n accountLivemode: ownerProfile.get('stripeAccountLivemode'),\n },\n { subject: `cart checkout host ${hostId}` },\n )\n ) {\n return res\n .status(409)\n .json({ error: 'This site has not enabled payments yet' })\n }\n\n // Re-price every line from its doc; any unavailable line blocks.\n const productSnapshots = await Promise.all(\n [...new Set(cart.lines.map((line) => line.productId))].map((id) =>\n hostRef.collection('products').doc(id).get(),\n ),\n )\n const productsById = new Map(\n productSnapshots.map((snapshot) => [\n snapshot.id,\n snapshot.exists ? CommerceModel.liftLegacyProduct(snapshot.data() as any) : null,\n ]),\n )\n let itemsCents = 0\n /** Per-line values for scoped discount pricing. */\n const discountLines: { productId: string; amountCents: number }[] = []\n // Gift-card value in the basket, for the cash-out ceiling (AGL-3363).\n let giftCardCents = 0\n let feeCents = 0\n /** Whether any line carries a fee rate above zero (AGL-2232). */\n let feeApplies = false\n /**\n * The goods the shopper actually pays for, after any discount that reached\n * the session (AGL-2232). Starts as the whole basket and is lowered only\n * when a Stripe coupon is minted, which is the one reduction the fee is\n * scaled by.\n */\n let chargedItemsCents = 0\n // Weight-tier shipping rates price off this (AGL-1707). Without it every\n // `weight_tiers` rate would resolve at 0g and quote the lightest tier.\n let totalGrams = 0\n // Whether anything in this cart is posted at all (AGL-1721). Buy-now has\n // always resolved shipping for physical products only; the cart never\n // asked, so a cart of downloads could be charged to ship. It matters more\n // now that an unresolvable destination REFUSES: a shopper buying two PDFs\n // must not be asked which country to ship them to.\n let hasPhysicalLine = false\n /**\n * What this cart will RESERVE (AGL-2356), collected as the loop below\n * resolves each line's variant rather than re-derived afterwards: the\n * reservation and the price must be taken against the same variant, and a\n * second resolution is a second chance to pick a different one.\n */\n const reserveLines: StockHoldLine[] = []\n /** The basket as `checkout.started` reports it (AGL-3639). */\n const startedItems: Parameters<typeof raiseCheckoutStarted>[1]['items'] = []\n /** The basket as an extras offer prices it (AGL-3635). */\n const extrasLines: CheckoutExtrasLine[] = []\n // Cart checkout never builds subscription sessions — every line bills\n // one-time in `payment` mode (recurring products subscribe through the\n // PDP's direct checkout, AGL-303) — so the buyer-chosen billing field\n // (AGL-545) does not apply here.\n const params = new URLSearchParams({\n mode: 'payment',\n // 3-D Secure, from the one seam every card payment shares (AGL-3360).\n ...checkoutSessionCardAuthenticationParams('payment'),\n })\n cart.lines.forEach((line, index) => {\n const product = productsById.get(line.productId)\n if (!product || product.deletedAt || product.status !== 'active') {\n throw Object.assign(new Error('unavailable'), {\n visible: `\"${product?.name ?? 'A product'}\" is no longer available`,\n })\n }\n const variant = line.variantId\n ? product.variants.find((item) => item.id === line.variantId)\n : product.variants[0]\n if (!variant || !CommerceModel.canPurchase(product, variant.id, line.quantity)) {\n throw Object.assign(new Error('unavailable'), {\n visible: `\"${product.name}\" is sold out`,\n })\n }\n // A variant with no price yet (AGL-2916) is not for sale: pricing it\n // would charge nothing for it, or send the processor no amount.\n if (!CommerceModel.variantHasPrice(variant)) {\n throw Object.assign(new Error('unavailable'), {\n visible: `\"${product.name}\" is not available`,\n })\n }\n // Gift cards are a Business+ entitlement, checked per sale (AGL-470).\n if (\n product.giftCard &&\n !Aglyn.checkEntitlement(ownerOrg.org as any, 'giftCards')\n ) {\n throw Object.assign(new Error('unavailable'), {\n visible: `\"${product.name}\" is not available on this store's plan`,\n })\n }\n // Only the lines there is something to reserve ON. An untracked variant\n // and a `backorder` product have no finite shelf, so a hold could never\n // change what they answer — and collecting them would open a transaction\n // over the products of a purely digital storefront to write nothing at\n // all. The same decision `holdPromotionSlot` makes for an UNCAPPED\n // promotion: the unlimited case stays off the write path entirely.\n //\n // A pre-filter, so the authority is still the in-transaction re-check in\n // `holdStock`. The only thing it can get wrong is skipping a hold for a\n // variant that became tracked between this read and that transaction,\n // which is the behaviour that shipped before this issue.\n if (CommerceModel.stockIsReservable(product, variant.id, Date.now())) {\n reserveLines.push({\n productId: line.productId,\n variantId: variant.id,\n quantity: line.quantity,\n })\n }\n const unitCents = Math.round(Number(variant.priceUsd) * 100)\n itemsCents += unitCents * line.quantity\n extrasLines.push({\n productId: line.productId,\n name: product.name,\n ...((variant as { sku?: string }).sku ? { sku: String((variant as { sku?: string }).sku) } : {}),\n quantity: line.quantity,\n unitCents,\n physical: (product.type ?? 'physical') === 'physical',\n })\n startedItems.push({\n productId: line.productId,\n variantId: variant.id ?? null,\n name: product.name,\n sku: (variant as { sku?: string }).sku ?? null,\n quantity: line.quantity,\n unitCents,\n })\n if (product.giftCard) giftCardCents += unitCents * line.quantity\n // What THIS line is worth, so a product-scoped discount can be priced\n // against the lines it actually covers rather than the whole basket\n //.\n discountLines.push({\n productId: line.productId,\n amountCents: unitCents * line.quantity,\n })\n totalGrams += Math.max(0, Number(variant.weightGrams ?? 0)) * line.quantity\n // A missing `type` reads as physical, exactly as the fee ladder below\n // reads it — `liftLegacyProduct` only defaults the field on docs it\n // synthesizes variants for, so a part-migrated doc can reach here\n // without one and the two reads must not disagree about what it is.\n if ((product.type ?? 'physical') === 'physical') hasPhysicalLine = true\n // `?? 'physical'`, which the comment four lines up already promised and\n // the code did not deliver (AGL-2251). `resolveTransactionFeePct` keys\n // on `productType === 'physical'` and sends everything else — `undefined`\n // included — to the DIGITAL rate, so a part-migrated product doc with no\n // `type` was priced at 3%/2%/1% here while `checkout.ts` (`lifted.type ??\n // 'physical'`) priced the identical product at the physical rate. The\n // same basket cost the merchant a different fee depending on which\n // button the shopper pressed.\n const feePct = Aglyn.resolveTransactionFeePct(\n ownerOrg.org as any,\n product.type ?? 'physical',\n )\n // WHETHER a rate applies is not the same fact as what it rounds to\n // (AGL-2232), and only the sum was being kept. `checkout.ts`,\n // `draft-order.ts` and `reserve.ts` all floor at `Math.max(1, …)` when\n // `feePct > 0`; the cart floored nothing, so a Scale-plan cart of $0.30\n // digital lines rounded every line to 0, the emission guard below read\n // `feeCents > 0` as \"this plan charges nothing\", and the session went out\n // with no application fee at all.\n if (feePct > 0) feeApplies = true\n feeCents += Math.round((unitCents * line.quantity * feePct) / 100)\n params.set(`line_items[${index}][quantity]`, String(line.quantity))\n params.set(`line_items[${index}][price_data][currency]`, 'usd')\n params.set(\n `line_items[${index}][price_data][unit_amount]`,\n String(unitCents),\n )\n params.set(\n `line_items[${index}][price_data][product_data][name]`,\n `${product.name}${\n Object.keys(variant.options ?? {}).length\n ? ` (${Object.values(variant.options ?? {}).join(' / ')})`\n : ''\n }`.slice(0, 120),\n )\n })\n\n // GIFT CARDS ARE A CASH-OUT (AGL-3363): a ceiling per order, and per\n // visitor per day, lower while the workspace is young. Above the claim,\n // like the line refusals, so a refusal keeps the key.\n if (giftCardCents > 0) {\n const young = isYoungWorkspace(ownerOrg.org as { createdAt?: unknown })\n const overCeiling = CommerceModel.giftCardPurchaseRefusal({ giftCardCents, young })\n if (overCeiling) {\n throw Object.assign(new Error('unavailable'), { visible: overCeiling })\n }\n const buys = await consumeRateLimit(\n `giftcard:buy:${hostId}:${String(req.socket?.remoteAddress ?? '') || 'no-address'}`,\n {\n limit: young\n ? CommerceModel.GIFT_CARD_PURCHASE_LIMITS.perVisitorDailyYoung\n : CommerceModel.GIFT_CARD_PURCHASE_LIMITS.perVisitorDaily,\n windowMs: 24 * 60 * 60 * 1000,\n },\n )\n if (!buys.allowed) {\n throw Object.assign(new Error('unavailable'), {\n visible:\n 'Gift cards can only be bought a few times a day from one place. ' +\n 'Please try again tomorrow, or contact the shop.',\n })\n }\n }\n\n // Shipping (AGL-1707), from the settings document tax is also read from.\n // This is the ONLY thing that makes Stripe charge shipping: without\n // `shipping_options` it presents no shipping choice,\n // `total_details.amount_shipping` is 0, and the zones and rates the\n // merchant configured in the console are silently worth nothing.\n //\n // RESOLVED BEFORE ANY STRIPE CALL, ahead of the discount block below,\n // because the plan can refuse — and the discount block creates real coupon\n // objects on the merchant's account. Refusing after them would leave one\n // orphaned on every attempt.\n //\n // A merchant who has configured nothing plans to no options and gets a\n // session byte-identical to today's: no shipping charged, exactly as\n // before. Only a merchant who set rates up starts collecting them.\n const storeSettings = await hostRef\n .collection('settings')\n .doc('store')\n .get()\n // PICKUP OR THE STORE'S OWN DELIVERY (AGL-3624), declared at the cart and\n // decided here from the store's settings: the location, the zone's fee and\n // minimum, the window. Above the claim, so a refusal keeps the key and the\n // shopper's same button works once they choose again. See\n // `local-fulfillment.ts`.\n const localRequest = readLocalFulfillmentRequest(body.fulfillment)\n const localStore = hasPhysicalLine\n ? await readLocalFulfillmentStore({\n hostRef,\n storeSettings: (storeSettings.data?.() ?? null) as Record<string, unknown> | null,\n org: ownerOrg.org as { timeZone?: string },\n })\n : null\n const localPlan = localStore\n ? await planLocalFulfillment({\n hostId,\n request: localRequest,\n itemsCents,\n hasPhysicalLine,\n store: localStore,\n })\n : ({ kind: 'shipping' } as const)\n if (localPlan.kind === 'refusal') {\n return res.status(localPlan.status).json({\n error: localPlan.error,\n ...(localPlan.changed ? { fulfillmentChanged: localPlan.changed } : {}),\n })\n }\n // A live carrier rate (AGL-3612) is quoted here too, before the session,\n // for the destination the shopper declared; a store with none plans from\n // its table exactly as before. See `carrier-shipping.ts`.\n const shippingPlan: CarrierShippingPlan =\n // Collected: nothing to price, no address to ask for.\n localPlan.kind === 'pickup'\n ? { countries: [], options: [] }\n : // Driven by the store: its one fee, an address in its country only.\n localPlan.kind === 'local_delivery'\n ? { countries: [localPlan.country], options: [localPlan.option] }\n : hasPhysicalLine\n ? await planCheckoutShippingWithCarriers({\n hostId,\n settings: storeSettings.get('shipping') as\n | CommerceModel.ShippingSettings\n | undefined,\n cart: { subtotalCents: itemsCents, totalGrams },\n destination: {\n country: body.shippingCountry,\n postalCode: body.shippingPostalCode,\n },\n })\n : { countries: CommerceModel.CHECKOUT_SHIPPING_COUNTRIES, options: [] }\n // A store whose locations offer pickup has the shopper choose WHERE at\n // the cart (AGL-3624); the old unnamed `Local pickup` rate would be a\n // second, unrouted pickup inside a session the shopper chose shipping for.\n if (localPlan.kind === 'shipping' && localStore?.pickupLocations.length) {\n shippingPlan.options = shippingPlan.options.filter(\n (option) => option.rateId !== CommerceModel.LOCAL_PICKUP_RATE_ID,\n )\n }\n if (shippingPlan.needsPostalCode) {\n return res.status(400).json({\n error: CARRIER_POSTAL_CODE_MESSAGE,\n needsShippingCountry: true,\n needsShippingPostalCode: true,\n shippingCountries: [...CommerceModel.CHECKOUT_SHIPPING_COUNTRIES],\n })\n }\n // The shopper is asked, then the answer is ENFORCED (AGL-1721): a declared\n // destination narrows `allowed_countries` to itself, so the rate resolved\n // for it is the only one on the session and no other address can be\n // entered against it. Answering the 400 is not optional — a caller that\n // skips the field is refused again rather than served the union.\n if (shippingPlan.refusal === 'destination-required') {\n return res.status(400).json({\n error: 'Choose where you’re shipping to.',\n needsShippingCountry: true,\n shippingCountries: [...CommerceModel.CHECKOUT_SHIPPING_COUNTRIES],\n })\n }\n if (shippingPlan.refusal === 'destination-unserved') {\n const declared = CommerceModel.normalizeCheckoutShippingCountry(\n body.shippingCountry,\n )\n return res.status(409).json({\n error: `This store does not ship to ${\n CommerceModel.CHECKOUT_SHIPPING_COUNTRY_NAMES[declared as string] ??\n 'that country'\n }.`,\n needsShippingCountry: true,\n shippingCountries: [...CommerceModel.CHECKOUT_SHIPPING_COUNTRIES],\n })\n }\n // The merchant priced shipping and their table cannot reach this cart\n // (AGL-2232) — a 3 kg basket at a store whose weight tiers stop at 2 kg.\n // No `needsShippingCountry`: asking where it goes cannot price it, and a\n // shopper handed the country select would answer it and be refused again.\n if (shippingPlan.refusal === 'cart-unpriceable') {\n return res\n .status(409)\n .json({ error: CommerceModel.CART_UNPRICEABLE_SHIPPING_MESSAGE })\n }\n\n // OPTIONAL LINES THE BUYER TICKED (AGL-3635) — package protection — asked\n // of core's checkout-extras seam AGAIN here, never taken from the request:\n // the body names which offers, the provider names the price. Above the\n // claim, so a refusal keeps the key. An offer the buyer ticked that is no\n // longer made refuses rather than selling without it: they asked for\n // cover, and a parcel that silently has none is the worse surprise.\n const chosenExtras = readChosenCheckoutExtras(body.extras)\n const chargedExtras = chosenExtras.length\n ? (\n await quoteCartExtras({\n hostId,\n lines: extrasLines,\n destination: { country: body.shippingCountry, postalCode: body.shippingPostalCode },\n })\n ).filter((extra) => chosenExtras.includes(extra.id))\n : []\n if (chargedExtras.length !== chosenExtras.length) {\n return res.status(409).json({ error: CHECKOUT_EXTRA_UNAVAILABLE_MESSAGE, extrasChanged: true })\n }\n const extrasCents = checkoutExtrasCents(chargedExtras)\n // Each as its own line after the goods, untaxed (`txcd_00000000`, Stripe's\n // nontaxable code) and outside every per-line tax, engine and fee loop\n // below, which read `cart.lines` only: an extra is not goods, so the\n // platform's take is not charged on it.\n chargedExtras.forEach((extra, offset) => {\n const index = cart.lines.length + offset\n params.set(`line_items[${index}][quantity]`, '1')\n params.set(`line_items[${index}][price_data][currency]`, 'usd')\n params.set(`line_items[${index}][price_data][unit_amount]`, String(extra.amountCents))\n params.set(`line_items[${index}][price_data][product_data][name]`, extra.label.slice(0, 120))\n params.set(`line_items[${index}][price_data][product_data][tax_code]`, 'txcd_00000000')\n })\n for (const [key, value] of Object.entries(encodeCheckoutExtrasMetadata(chargedExtras))) {\n params.set(`metadata[${key}]`, value)\n }\n\n chargedItemsCents = itemsCents\n // Discounts engine (AGL-305): entered codes and automatic\n // promotions from hosts/{id}/discounts; the AGL-96 coupons remain a\n // fallback for unknown codes.\n const discountsSnapshot = await hostRef\n .collection('discounts')\n .limit(100)\n .get()\n const resolvedDiscount = CommerceModel.resolveDiscount(\n discountsSnapshot.docs.map((docSnapshot) => ({\n ...(docSnapshot.data() as CommerceModel.HostDiscount),\n $id: docSnapshot.id,\n })),\n {\n ...(couponCode ? { code: couponCode } : {}),\n subtotalCents: itemsCents,\n productIds: cart.lines.map((line) => line.productId),\n lines: discountLines,\n },\n )\n if (resolvedDiscount?.codeProblem) {\n return res.status(400).json({ error: resolvedDiscount.codeProblem })\n }\n\n // Point of no return (AGL-1697): everything past here creates Stripe\n // objects — up to two coupons and the session — on the merchant's live\n // account. The cart is not consumed until the webhook clears it, so\n // before the claim one cart could spawn unlimited sessions, each retry\n // leaving an orphan `checkouts/{sessionId}` doc (which feeds the AGL-323\n // abandoned-cart emails) plus a stray coupon.\n //\n // The two refusals that remain BELOW this line (invalid legacy coupon,\n // empty gift card) each release the claim: they are deterministic, the\n // shopper fixes the code and presses the same button, and because the\n // retry re-derives the same digest Stripe replays any coupon the first\n // run already minted rather than creating a twin (AGL-1714's rule).\n const claimed = await claimAttempt(firestore, {\n kind: 'commerce-cart',\n scopeId: `${hostId}:${cartId}`,\n orgId: String(ownerOrg?.org?.id ?? ''),\n key: idempotencyKey,\n busyMessage: 'This checkout is already being processed',\n })\n if ('replay' in claimed) {\n return res.status(claimed.replay.status).json(claimed.replay.body)\n }\n claim = claimed.claim\n // Derived per OBJECT, never the raw digest (AGL-1714): Stripe\n // parameter-compares a repeated key account-wide, so one digest sent to\n // `/v1/coupons` and then `checkout/sessions` would error the second call.\n const stripeKeyHeader = (object: string): Record<string, string> => {\n const derived = deriveStripeObjectKey(claimed.claim.stripeKey, object)\n return derived ? { 'Idempotency-Key': derived } : {}\n }\n\n // THE UNITS ARE RESERVED HERE (AGL-2356), not merely read in the loop\n // above.\n //\n // `canPurchase` up there is a plain `.get()` compared against a number, and\n // the webhook decremented minutes later without ever re-asking. Nothing was\n // written, so there was no document to contend on: every shopper who\n // reached this line while the shelf held one unit passed it, and they all\n // paid. This is the write that makes them serialise.\n //\n // ONE transaction across every product in the cart, so a two-product basket\n // reserves both or neither — a partial reservation would charge a shopper\n // for goods the store cannot fill, which is this issue's own defect\n // arriving through its fix. See `stock-hold.ts`.\n //\n // FIRST among the three holds this handler takes, before the promotion\n // slots and the gift card below: stock is the likeliest refusal, and\n // reserving a merchant's capped discount for a basket the store cannot fill\n // is a slot held for nothing.\n {\n const held = await holdStock({\n firestore,\n hostRef,\n holdKey: stockHoldKey(claimed.claim.stripeKey),\n lines: reserveLines,\n label: `cart ${hostId}/${cartId}`,\n // At the pickup location, or the one deliveries leave from (AGL-3624).\n locationId: localFulfillmentLocationId(localPlan),\n })\n if (!held.ok) {\n // Nothing has been minted yet, so the key goes back and the same\n // button works when the units return (AGL-1697). `sold-out` names the\n // shelf being SPOKEN FOR rather than empty — the merchant has stock and\n // another shopper is in checkout with it, and telling this one to come\n // back in a few minutes is the difference between a wait and a loss.\n await releaseStock()\n await claim.release()\n return res.status(409).json({\n error:\n held.reason === 'sold-out-at-location'\n ? localStockRefusalMessage(localPlan, held.productName)\n : held.reason === 'sold-out'\n ? held.productName\n ? `\"${held.productName}\" — ${CommerceModel.STOCK_HELD_MESSAGE}`\n : CommerceModel.STOCK_HELD_MESSAGE\n : 'Your cart is no longer available',\n })\n }\n if (held.holdKey) {\n stockHold = held\n heldStockKey = held.holdKey\n }\n }\n\n // ONE Stripe discount, and that is the whole shape of this block\n // (AGL-2112).\n //\n // A Checkout Session takes exactly one entry in `discounts`. This used to\n // be three independent blocks — an AGL-305 discount, a legacy AGL-96\n // coupon code, and an AGL-322 gift card — each minting its own Stripe\n // coupon and each doing `params.set('discounts[0][coupon]', …)`.\n // `URLSearchParams.set` REPLACES, so a cart carrying a discount AND a gift\n // card minted two real coupon objects on the merchant's account and sent\n // only the LAST one. The shopper paid full price minus the gift card; the\n // discount silently evaporated — and the webhook still burned its\n // redemption against `maxRedemptions`, so the code was spent on an order\n // that never got it.\n //\n // The fee was wrong in the same combination and in Aglyn's favour by\n // accident, then against it: `feeCents` was scaled DOWN once per block, so\n // two reductions compounded for one discount that was actually applied.\n //\n // So: resolve every reduction FIRST, add them up, and mint a single\n // `amount_off` coupon for the sum. A percentage coupon becomes its cash\n // equivalent against `itemsCents` — which is what the fee arithmetic here\n // has always assumed it was, and what `checkout.ts` already does for\n // buy-now (`amountCents * (100 - percentOff) / 100`).\n //\n // Validation still happens in the same order and still 400s the same way,\n // BEFORE anything is minted — the shopper fixes the code and presses the\n // same button, so the claim is released and the key survives (AGL-1697).\n // REDEMPTION SLOTS ARE HELD, NOT MERELY READ (AGL-2453).\n //\n // `resolveDiscount` above and the coupon `.get()` below both answered \"is\n // there a slot left?\" from a plain read, and the webhook incremented the\n // counter minutes later without ever re-asking. The gap is the whole\n // Checkout Session lifetime — up to 24 hours — so every shopper who reached\n // this line while a cap of 100 sat at 99 passed it, and the counter\n // finished at 99+N. Nothing was lost; nothing was ever CHECKED.\n //\n // The AGL-2449 shape, reused: the slot is claimed in a transaction here and\n // settled in one at the webhook. Both redemption paths hold — the typed\n // coupon and the automatic discount — because holding one of two doors onto\n // the same counter fixes neither, and the automatic path is the one a\n // shopper does not even have to opt into.\n //\n // A held slot COUNTS while its session is live and is released when the\n // session expires, is cancelled, or this handler refuses below. See\n // `model/commerce-promotions.ts` for why that is the right way round.\n const slotKey = promotionHoldKey(claim.stripeKey)\n let totalOffCents = 0\n if (resolvedDiscount && resolvedDiscount.benefit.kind !== 'none') {\n const discountRef = hostRef\n .collection('discounts')\n .doc(resolvedDiscount.discountId)\n const slot = await holdPromotionSlot({\n firestore,\n ref: discountRef,\n holdKey: slotKey,\n label: `discount ${resolvedDiscount.discountId}`,\n })\n if (!slot.ok) {\n // Refused rather than silently dropped, and that is a deliberate\n // choice for the AUTOMATIC case: this discount was in the price the\n // storefront showed, so continuing without it would charge more than\n // the shopper agreed to and tell them nothing. Nothing has been minted\n // yet, so this is retryable — and the retry prices correctly, because\n // `resolveDiscount` counts holds too and simply stops offering it.\n await claim.release()\n return res.status(409).json({\n error:\n couponCode && resolvedDiscount.discount.code\n ? CommerceModel.PROMOTION_EXHAUSTED_MESSAGE\n : CommerceModel.PROMOTION_UNAVAILABLE_MESSAGE,\n })\n }\n if (slot.holdKey) heldPromotions.push(slot)\n totalOffCents += resolvedDiscount.discountCents\n params.set('metadata[discountId]', resolvedDiscount.discountId)\n // Only when a slot was actually reserved. Its ABSENCE is what tells the\n // webhook to fall back to the unconditional increment, which is right for\n // an uncapped promotion and for a session minted before this deploy.\n if (slot.holdKey) {\n params.set('metadata[discountHoldKey]', slot.holdKey)\n }\n }\n // FREE SHIPPING IS A ZEROED RATE, NOT A COUPON.\n //\n // A session-level Stripe coupon discounts LINE ITEMS; shipping is a\n // separate `shipping_options` concept and a coupon never touches it. There\n // is also no amount to discount at this point — the shopper picks their\n // rate after the session is created — so the only construction that can be\n // exact is to offer every rate at zero.\n //\n // Every rate, not just the cheapest: `free_shipping` carries no field\n // scoping it to one, so the merchant asked for shipping to be free rather\n // than for one particular rate to be. Zeroing the dearest too also keeps\n // the fee and transfer arithmetic below honest, since both read these same\n // options and the shopper is paying nothing for carriage either way.\n const shippingOptions =\n resolvedDiscount?.freeShipping === true\n ? shippingPlan.options.map((option) => ({ ...option, amountCents: 0 }))\n : shippingPlan.options\n\n // Coupons (AGL-96 semantics): percent off the items total.\n if (couponCode && !resolvedDiscount) {\n const couponRef = hostRef.collection('coupons').doc(couponCode)\n const couponSnapshot = await couponRef.get()\n const coupon = couponSnapshot.data() as any\n const percentOff = Number(coupon?.percentOff ?? 0)\n const expired =\n coupon?.expiresAtMs != null && Number(coupon.expiresAtMs) < Date.now()\n if (\n !coupon ||\n coupon.enabled === false ||\n expired ||\n !(percentOff > 0 && percentOff <= 100)\n ) {\n // Deterministic and retryable — the shopper fixes the code and\n // presses the same button, so the key survives (AGL-1697).\n await claim.release()\n return res.status(400).json({ error: 'Invalid or expired coupon' })\n }\n // The exhaustion test moved INTO a transaction that also writes\n // (AGL-2453). It used to be a comparison against a plain `.get()` here,\n // and the webhook incremented the counter minutes later without\n // re-asking — so the check bounded nothing once two shoppers held\n // sessions at the same time.\n const slot = await holdPromotionSlot({\n firestore,\n ref: couponRef,\n holdKey: slotKey,\n label: `coupon ${couponCode}`,\n })\n if (!slot.ok) {\n await claim.release()\n return res.status(400).json({\n error:\n slot.reason === 'exhausted'\n ? CommerceModel.PROMOTION_EXHAUSTED_MESSAGE\n : 'Invalid or expired coupon',\n })\n }\n if (slot.holdKey) {\n heldPromotions.push(slot)\n params.set('metadata[couponHoldKey]', slot.holdKey)\n }\n totalOffCents += Math.round((itemsCents * percentOff) / 100)\n params.set('metadata[couponCode]', couponCode)\n }\n\n // Gift cards (AGL-322): balance applies as amount-off; the webhook settles\n // the hold this places on completion.\n //\n // The read is a TRANSACTION and it WRITES (AGL-2449). A gift card is cash,\n // and a plain `.get()` here was the last unreserved limited resource in the\n // order path: two checkouts entering the same code both read $50, both got\n // $50 off, and the webhook's bare `increment(-N)` settled the card at\n // -$50 — $100 of goods against a $50 card, and a negative row in the\n // outstanding-liability total. The increment was atomic the whole time;\n // nothing ever CHECKED.\n //\n // Unlike stock (AGL-2320), this door can still refuse: the card is applied\n // BEFORE Stripe is contacted, so no money has moved yet and a shortfall is\n // a 400 rather than a shipped parcel and an apology. That is why this\n // reserves instead of merely reporting.\n //\n // `giftCardCents` stays in the session metadata because the webhook still\n // needs to know what to settle, but it is no longer the AUTHORITY for the\n // decrement — the hold is, and it is keyed by the session id below.\n // Set only once a hold actually stands, so the refusals below can call it\n // unconditionally without knowing whether there was a gift card at all.\n let releaseGiftCardHold: () => Promise<void> = async () => undefined\n if (giftCardCode) {\n const cardRef = hostRef.collection('giftCards').doc(giftCardCode)\n // Placed before the Stripe session exists, so it is keyed by the\n // idempotency claim's derived key rather than by a session id we do not\n // have yet — the same string the session is minted under below, so a\n // retry of this attempt re-places the SAME hold rather than a second one.\n const holdKey = claim.stripeKey\n const nowMs = Date.now()\n const held = await firestore\n .runTransaction(async (transaction: any) => {\n const fresh = await transaction.get(cardRef)\n if (!fresh.exists) return null\n const card = (fresh.data() ?? {}) as CommerceModel.HostGiftCard\n const holds = CommerceModel.pruneGiftCardHolds(card.holds, nowMs)\n // This attempt's own prior hold does not stand in its way — a retry\n // must be able to re-claim what it already reserved, or the second\n // press of the same button refuses the shopper their own money.\n const mine = Number(holds[holdKey]?.cents ?? 0)\n delete holds[holdKey]\n const available = CommerceModel.giftCardAvailableCents(\n { ...card, holds },\n nowMs,\n )\n if (available <= 0 && mine <= 0) return null\n // Against what is LEFT after the discount, not against the whole\n // basket (AGL-2112). The old cap was `min(balance, itemsCents)`, so a\n // $50 card on a $100 basket already 30% off consumed $50 of card\n // against $70 of goods — the webhook decrements by exactly this\n // number, so the extra was burned off the customer's card for nothing.\n const applyCents = Math.min(\n available + mine,\n Math.max(0, itemsCents - totalOffCents),\n )\n if (applyCents <= 0) return 0\n // Lapsed holds are deleted by SENTINEL, not by writing back a pruned\n // copy of the map: `set(…, { merge: true })` merges nested maps\n // rather than replacing them, so a locally-pruned object leaves every\n // stale key exactly where it was. Correctness does not depend on this\n // — every read prunes again — but the document would otherwise grow\n // without bound, one dead key per abandoned checkout, forever.\n const swept: Record<string, unknown> = {}\n for (const stale of Object.keys(card.holds ?? {})) {\n if (stale !== holdKey && !holds[stale]) {\n swept[stale] = firebaseAdmin.firestore.FieldValue.delete()\n }\n }\n transaction.set(\n cardRef,\n {\n holds: {\n ...swept,\n [holdKey]: {\n cents: applyCents,\n expiresAtMs: nowMs + CommerceModel.GIFT_CARD_HOLD_TTL_MS,\n },\n },\n },\n { merge: true },\n )\n return applyCents\n })\n .catch((error: unknown) => {\n console.error('Gift card hold failed', giftCardCode, error)\n return null\n })\n if (held == null) {\n // Deterministic and retryable; nothing has been minted yet. Covers the\n // unknown card, the empty card, and the card whose whole balance is\n // already held by someone else's live checkout — the shopper is told\n // the same thing in each case, because \"someone is spending it right\n // now\" is not a distinction a storefront should draw.\n await claim.release()\n return res.status(400).json({ error: 'Gift card is empty or invalid' })\n }\n if (held > 0) {\n totalOffCents += held\n params.set('metadata[giftCardCode]', giftCardCode)\n params.set('metadata[giftCardCents]', String(held))\n params.set('metadata[giftCardHoldKey]', holdKey)\n // Every refusal BELOW this point releases the claim so the shopper can\n // retry; the hold has to come back with it or the retry finds the card\n // empty and the shopper is locked out of their own balance until the\n // TTL lapses. The expiry is the backstop for a crash, not the\n // mechanism for a refusal the code can see happening.\n releaseGiftCardHold = async () => {\n await cardRef\n .set(\n {\n holds: {\n [holdKey]: firebaseAdmin.firestore.FieldValue.delete(),\n },\n },\n { merge: true },\n )\n .catch((error: unknown) => {\n // Best effort: the TTL still releases it. Never let tidying up a\n // hold turn a clean 409 into a 500.\n console.error('Gift card hold release failed', giftCardCode, error)\n })\n }\n }\n }\n\n // STORE CREDIT ANOTHER PLUGIN KEEPS (AGL-3640) — a rewards balance, a\n // friend's referral credit — by its code, through core's checkout-credits\n // seam; this plugin never reads the account. Held exactly like the gift\n // card above: against what is LEFT after every other reduction, BEFORE\n // Stripe is contacted, keyed by this attempt so a retry re-places the same\n // hold. The webhook takes it in the transaction that writes the order, and\n // a session that expires lets it go.\n let releaseCreditHold: () => Promise<void> = async () => undefined\n let creditHeldCents = 0\n if (creditCode) {\n const refuseCredit = async (status: number, error: string) => {\n await releaseGiftCardHold()\n await releasePromotionHolds()\n await releaseStock()\n await claim?.release()\n return res.status(status).json({ error, creditCodeInvalid: true })\n }\n const entry = checkoutCreditProviderForCode(creditCode)\n const offered = entry\n ? await entry.provider.offered({ hostId, channel: 'online' }).catch(() => false)\n : false\n if (!entry || !offered) return refuseCredit(400, CHECKOUT_CREDIT_INVALID_MESSAGE)\n const account = normalizeCheckoutCreditAccount(\n await entry.provider\n .resolve({ hostId, code: creditCode, channel: 'online', customerEmail: email || null, staff: false })\n .catch((error: unknown) => {\n console.error('Checkout credit resolve failed', entry.providerId, error)\n return null\n }),\n )\n if ('error' in account) return refuseCredit(account.status, account.error)\n const maxCents = Math.max(0, itemsCents - totalOffCents)\n // The attempt's key, so a retry re-places the same hold; a request\n // that sent none (an older client) still needs a key of its own.\n const holdKey = claim.stripeKey || `cart:${cartId}:${randomUUID()}`\n const held = await entry.provider\n .hold({\n hostId,\n reference: account.reference,\n holdKey,\n maxCents,\n currency: 'usd',\n customerEmail: email || null,\n nowMs: Date.now(),\n })\n .catch((error: unknown) => {\n console.error('Checkout credit hold failed', entry.providerId, error)\n return { ok: false as const, status: 409, error: 'That rewards code cannot be used right now. Try again.' }\n })\n if ('error' in held) {\n return refuseCredit(held.status >= 400 && held.status < 500 ? held.status : 409, held.error)\n }\n const release = async () => {\n await entry.provider.release({ hostId, reference: account.reference, holdKey })\n }\n creditHeldCents = boundedCreditCents((held as { cents: number }).cents, maxCents)\n if (creditHeldCents > 0) {\n totalOffCents += creditHeldCents\n for (const [key, value] of Object.entries(\n encodeCheckoutCreditMetadata({\n providerId: entry.providerId,\n reference: account.reference,\n holdKey,\n amountCents: creditHeldCents,\n label: account.label,\n last4: account.last4,\n }),\n )) {\n params.set(`metadata[${key}]`, value)\n }\n releaseCreditHold = release\n } else {\n await release()\n }\n }\n\n if (totalOffCents > 0) {\n const stripeCoupon = await fetch('https://api.stripe.com/v1/coupons', {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,\n 'Content-Type': 'application/x-www-form-urlencoded',\n // ONE derived object key now, because there is one coupon. The\n // three old keys existed only because there were three mints. A\n // store-credit share names itself (AGL-3640): a balance that moved\n // between two tries is a different coupon, never a mismatch.\n ...stripeKeyHeader(creditHeldCents > 0 ? `coupon-c${creditHeldCents}` : 'coupon'),\n },\n body: new URLSearchParams({\n amount_off: String(Math.min(totalOffCents, itemsCents)),\n currency: 'usd',\n duration: 'once',\n }).toString(),\n }).then((response) => response.json())\n if (stripeCoupon?.id) {\n params.set('discounts[0][coupon]', stripeCoupon.id)\n // Scaled ONCE, by the reduction that actually reached the session.\n feeCents = Math.round(\n (feeCents * Math.max(0, itemsCents - totalOffCents)) /\n Math.max(1, itemsCents),\n )\n chargedItemsCents = Math.max(0, itemsCents - totalOffCents)\n } else if (creditHeldCents > 0) {\n // A rewards balance that did not reach the price must not be taken\n // from the member when the session is paid (AGL-3640): refuse, and\n // hand back everything this attempt held, rather than sell at full\n // price with their credit spent.\n console.error('Stripe coupon error', stripeCoupon?.error)\n await releaseGiftCardHold()\n await releaseCreditHold()\n await releasePromotionHolds()\n await releaseStock()\n await claim.release()\n return res.status(502).json({ error: 'Checkout failed' })\n }\n }\n // THE FLOOR (AGL-2232). A rate above zero on goods the shopper actually\n // pays for is a fee, however small the arithmetic makes it — the same rule\n // the other three doors already hold. Gated on `chargedItemsCents` so a\n // basket a gift card covered entirely still takes nothing: the fee is a cut\n // of the goods, and there are no goods left to cut.\n if (feeApplies && chargedItemsCents > 0 && feeCents < 1) feeCents = 1\n // STRIPE'S CARD COST, PASSED THROUGH AT COST (AGL-2152). The loop above\n // accumulates the platform's advertised TAKE per line and nothing else; on\n // a destination charge Stripe debits 2.9% + 30¢ of the whole card total\n // from the PLATFORM's balance, so a cart on any tier carrying a 0% rate\n // cost Aglyn money on every single order — and Starter's 2% is below 2.9%,\n // so it lost money too. Added ONCE per charge, not per line: the 30¢ is a\n // per-charge cost.\n //\n // The base is everything the card will be run for that can be seen from\n // here: the goods the shopper actually pays for, the DEAREST shipping\n // option on the session (the shopper picks after this and\n // `application_fee_amount` is fixed at creation), and the manual origin tax\n // rate the lines carry. A store on Stripe Tax is the one residual — its tax\n // is computed inside Stripe after the session is made.\n // The merchant's own tax service, where they connected one (AGL-3631).\n // Asked once, here, because the card-cost estimate just below and the\n // tax lines further down both need its answer. Each line goes over at\n // what Stripe will tax it at — its amount less its share of the session\n // coupon, shared the way Stripe shares it — so the rate derived from\n // the answer reproduces it. Taxed at the store's address, like the store's\n // own rate: the shopper's address arrives inside Stripe Checkout, after\n // this session exists. When the service does not answer, the store's\n // rate stands and the order says so.\n const cartEngineLines = cart.lines\n .map((line, index) => {\n const unitCents = Number(params.get(`line_items[${index}][price_data][unit_amount]`) ?? NaN)\n return {\n index,\n line,\n amountCents: Number.isFinite(unitCents) ? unitCents * line.quantity : 0,\n }\n })\n .filter((entry) => entry.amountCents > 0)\n const cartEngineDiscounts = allocateCentsByWeight(\n Math.max(0, itemsCents - chargedItemsCents),\n cartEngineLines.map((entry) => entry.amountCents),\n )\n const cartEngineNet = cartEngineLines.map(\n (entry, position) => entry.amountCents - cartEngineDiscounts[position],\n )\n const engineTax =\n chargedItemsCents > 0\n ? await quoteSaleTaxWithEngine({\n hostId,\n settings: (storeSettings.get('tax') ?? {}) as CommerceModel.TaxSettings,\n channel: 'online',\n lines: cartEngineLines.map((entry, position) => ({\n id: String(entry.index),\n productId: entry.line.productId,\n ...(entry.line.variantId ? { variantId: entry.line.variantId } : {}),\n quantity: entry.line.quantity,\n amountCents: cartEngineNet[position],\n ...(productsById.get(entry.line.productId)?.taxExempt ? { exempt: true } : {}),\n })),\n customerEmail: email || null,\n })\n : { quote: null, stamp: null }\n if (chargedItemsCents > 0) {\n const shippingCeilingCents = shippingOptions.reduce(\n (most, option) =>\n Math.max(most, Math.max(0, Number(option.amountCents ?? 0))),\n 0,\n )\n const cartTaxSettings = (storeSettings.get('tax') ??\n {}) as CommerceModel.TaxSettings\n const manualRate =\n cartTaxSettings.mode === 'manual' && !cartTaxSettings.pricesIncludeTax\n ? CommerceModel.resolveTaxRate(\n cartTaxSettings,\n cartTaxSettings.origin ?? {},\n )\n : null\n const manualTaxPct =\n manualRate && manualRate.pct > 0 ? manualRate.pct : 0\n const chargeCents =\n chargedItemsCents +\n // Stripe's cost on the extras is a real cost of this charge, passed\n // through at cost like the rest; no take is added on them (AGL-3635).\n extrasCents +\n (engineTax.quote\n ? engineTax.quote.taxCents\n : Math.round((chargedItemsCents * manualTaxPct) / 100)) +\n shippingCeilingCents\n feeCents = Math.min(\n chargeCents,\n feeCents + Aglyn.saleProcessingCostCents(chargeCents),\n )\n }\n // THE CONNECT SPLIT IS EMITTED BELOW, NOT HERE (AGL-1956). It used to be\n // written at this point, which is ABOVE the tax decision — and the shape\n // of the split now depends on that decision: a Stripe Tax sale must send a\n // fixed `transfer_data[amount]` instead of `application_fee_amount`,\n // because the fee form transfers `amount_total − fee` and `amount_total`\n // carries tax that AGLYN owes. `feeCents` is final as of this line; only\n // its emission moved. See `commerce-connect-transfer.ts`.\n //\n // Address collection feeds order shipping + destination tax later, and is\n // the enforcing half of the plan above (AGL-1721) — emitted from the\n // plan's own country list, never a default, so it cannot widen past the\n // destinations the declared rates were resolved for. The emission is\n // shared with buy-now (AGL-1720).\n CommerceModel.appendShippingAddressCollectionParams(\n params,\n shippingPlan.countries,\n )\n CommerceModel.appendCheckoutShippingParams(params, shippingOptions)\n\n // Taxes (AGL-1953). This block used to be the `stripe` line alone, and a\n // `manual`-mode store — the mode the AGL-285 zone editor leaves a merchant\n // in by default — collected tax on a buy-now sale and NOTHING on a cart\n // sale of the same goods. Same store, same shopper, same items, two totals\n // depending on which button was pressed, with the merchant left owing the\n // destination tax they never charged.\n //\n // The manual tax rides a real Stripe Tax Rate on each taxable line rather\n // than buy-now's `line_items[1]` product line (AGL-1711). That is not a\n // gratuitous difference: THIS path applies its discounts, coupons and gift\n // cards as session-level Stripe coupons, which spread across every line,\n // so a fake tax line would be discounted along with the goods. Stripe\n // applies a tax rate AFTER the discount — measured, see\n // `manual-tax-rate.ts` — so the arithmetic stays right and\n // `total_details.amount_tax` becomes real, which is what lets the webhook\n // record the tax with no metadata snapshot to keep in step.\n //\n // `automatic_tax.enabled` stays false on a manual sale, so\n // `storefront-tax.ts` still classifies it `manual` and it is never summed\n // into the Aglyn-liable figure (AGL-1904).\n const taxSettings = (storeSettings.get('tax') ?? {}) as CommerceModel.TaxSettings\n // AGL-1999: an unset tax mode is NOT a decision, and it must not sell.\n // `mode` was `undefined` for every store whose owner never opened the\n // Taxes card — the default state of every new storefront — and each\n // branch below tested for a specific string, so no branch was taken, no\n // tax was computed, and the shopper was charged an untaxed total. See\n // `commerce-tax-decision.ts` for why refusing beats defaulting to either\n // mode. `mode: 'none'` is the explicit opt-out and passes straight\n // through.\n const taxDecision = CommerceModel.storefrontTaxDecision({\n settings: taxSettings,\n })\n if (taxDecision.kind === 'undecided') {\n // BELOW the claim on this path, so release it: the merchant fixes the\n // setting and the shopper retries under the same key (AGL-1697).\n await releaseGiftCardHold()\n await releaseCreditHold()\n await releasePromotionHolds()\n await releaseStock()\n await claim.release()\n return res\n .status(409)\n .json({ error: CommerceModel.STOREFRONT_TAX_UNDECIDED_MESSAGE })\n }\n const taxMisconfigured =\n CommerceModel.storefrontTaxMisconfiguration(taxSettings)\n if (taxMisconfigured) {\n // AGL-2145: the merchant DECIDED to collect and the sale would have\n // charged zero anyway — an unfinished settings card, not a\n // jurisdiction miss. Refused in the same words and the same place as\n // the undecided case above.\n // Below the claim here too, so release it for the same reason.\n await releaseGiftCardHold()\n await releaseCreditHold()\n await releasePromotionHolds()\n await releaseStock()\n await claim.release()\n return res.status(409).json({ error: taxMisconfigured })\n }\n if (taxDecision.kind === 'stripe-automatic') {\n params.set('automatic_tax[enabled]', 'true')\n } else if (engineTax.quote) {\n // The tax service's cents per line as one rate per line (AGL-3631),\n // applied after the coupon exactly like the store's own rate below. One\n // Tax Rate per distinct percentage, cached under the host like the\n // store's, so a store's usual rates are minted once.\n const engineAnswer = new Map(engineTax.quote.lines.map((line) => [line.id, line.taxCents]))\n const percentages = engineLineTaxPercentages(\n cartEngineLines.map((entry, position) => ({\n amountCents: cartEngineNet[position],\n taxCents: engineAnswer.get(String(entry.index)) ?? 0,\n })),\n 0,\n )\n for (let position = 0; position < cartEngineLines.length; position++) {\n const pct = percentages[position]\n if (pct === null) continue\n const taxRateId = await resolveManualTaxRateId({\n hostRef,\n taxPct: pct,\n taxLabel: 'Sales tax',\n headers: stripeKeyHeader(`tax-rate-engine-${Math.round(pct * 10_000)}`),\n })\n if (!taxRateId) {\n // The same visible refusal as the store's own rate: never an\n // untaxed session.\n await releaseGiftCardHold()\n await releaseCreditHold()\n await releasePromotionHolds()\n await releaseStock()\n await claim.release()\n return res.status(502).json({ error: 'Checkout failed' })\n }\n params.set(`line_items[${cartEngineLines[position].index}][tax_rates][0]`, taxRateId)\n }\n } else if (taxSettings.mode === 'manual' && !taxSettings.pricesIncludeTax) {\n // Origin-based, exactly as buy-now resolves it: the cart collects the\n // shopper's address inside Stripe Checkout, so there is no destination\n // to resolve against before the session exists. Destination-based manual\n // tax is AGL-296's checkout, and taxing by origin here is what makes the\n // two paths agree — which is the whole point of this issue.\n //\n // `pricesIncludeTax` stores charge no separate tax on ANY path (buy-now\n // skips it the same way); the tax is already inside the displayed price.\n const rate = CommerceModel.resolveTaxRate(\n taxSettings,\n taxSettings.origin ?? {},\n )\n // Tax-exempt products are excluded per LINE, which a single session-wide\n // figure could not express — a cart mixing a taxable chair with an exempt\n // download must tax only the chair.\n const taxableIndexes = cart.lines\n .map((line, index) => ({ line, index }))\n .filter(({ line }) => !productsById.get(line.productId)?.taxExempt)\n .map(({ index }) => index)\n if (rate && rate.pct > 0 && taxableIndexes.length > 0) {\n const taxRateId = await resolveManualTaxRateId({\n hostRef,\n taxPct: rate.pct,\n taxLabel: rate.label || `Tax (${rate.pct}%)`,\n headers: stripeKeyHeader('tax-rate'),\n })\n if (!taxRateId) {\n // A VISIBLE refusal, never a fallback to an untaxed session — that\n // silent under-collection is this issue. Nothing was sold, so the\n // key goes back and the same button works once Stripe does\n // (AGL-1697); the retry re-derives the same digest, so Stripe\n // replays any rate the first run did mint.\n await releaseGiftCardHold()\n await releaseCreditHold()\n await releasePromotionHolds()\n await releaseStock()\n await claim.release()\n return res.status(502).json({ error: 'Checkout failed' })\n }\n for (const index of taxableIndexes) {\n params.set(`line_items[${index}][tax_rates][0]`, taxRateId)\n }\n }\n }\n\n // The site's own page, never a header's say-so (AGL-3363): a caller\n // writes `Referer` and `Host`, and this URL is where Stripe sends the\n // payer — and what the receipt's download links are built from.\n const returnSite = await readSiteReturnHost(hostRef)\n const backUrl = siteReturnUrl({\n candidates: [String(req.headers.referer ?? ''), `https://${req.headers.host}`],\n site: returnSite,\n requestHost: String(req.headers.host ?? ''),\n })\n const separator = backUrl.includes('?') ? '&' : '?'\n // `{CHECKOUT_SESSION_ID}` is substituted by Stripe on redirect (AGL-1641).\n // Without it the return URL said only that SOMETHING succeeded, so the\n // storefront could not name the order it had just completed — which is why\n // `purchase` could not be reported at all. It is also the order doc id and\n // becomes the GA `transaction_id`, so GA4's own de-duplication makes a\n // refresh of this page harmless.\n params.set(\n 'success_url',\n `${backUrl}${separator}order=success&session_id={CHECKOUT_SESSION_ID}`,\n )\n params.set('cancel_url', `${backUrl}${separator}order=canceled`)\n if (email) params.set('customer_email', email)\n // THE CONNECT SPLIT (AGL-1956), emitted here because it needs the tax\n // decision taken above. On a Stripe Tax cart the tax is computed against\n // AGLYN's registrations and is Aglyn's to remit (AGL-1904), so the\n // merchant is paid a FIXED `transfer_data[amount]` and no\n // `application_fee_amount` is sent at all — the fee form would have Stripe\n // transfer `amount_total − fee`, handing the state's money to the\n // merchant. A manual-rate or untaxed cart keeps the fee form byte for\n // byte. See `commerce-connect-transfer.ts` for why only one of the two\n // knobs may ever be present.\n const cartTaxOwner =\n taxDecision.kind === 'stripe-automatic' ? 'platform' : 'merchant'\n const cartShippingFloorCents = CommerceModel.shippingFloorCents(\n shippingOptions,\n )\n Object.entries(\n CommerceModel.destinationChargeParams({\n accountId: String(accountId),\n feeCents,\n taxOwner: cartTaxOwner,\n // The extras are the merchant's to keep and remit (AGL-3635).\n merchantGoodsCents: chargedItemsCents + extrasCents,\n shippingFloorCents: cartShippingFloorCents,\n }),\n ).forEach(([key, value]) => params.set(key, value))\n params.set('metadata[type]', 'commerce-cart')\n // How the order reaches the buyer (AGL-3624): the webhook routes it.\n if (localPlan.kind === 'pickup' || localPlan.kind === 'local_delivery') {\n appendLocalFulfillmentMetadata(params, localPlan.metadata)\n }\n for (const [key, value] of Object.entries(taxEngineSessionMetadata(engineTax.stamp))) {\n params.set(key, value)\n }\n // The postal code live carrier rates were quoted for (AGL-3612), so the\n // order can show it beside the address the shopper then entered.\n if (shippingPlan.quotedPostalCode) {\n params.set('metadata[shippingQuotePostalCode]', shippingPlan.quotedPostalCode)\n }\n params.set('metadata[hostId]', hostId)\n params.set('metadata[cartId]', cartId)\n params.set('metadata[feeCents]', String(Math.max(0, feeCents)))\n // Only on the shape where `transfer.amount` is no longer `charge.amount`,\n // so the merchant's actual payout stops being derivable from the charge\n // (AGL-1956). The shipping baseline is the other half: the difference\n // between it and the option the shopper chose is owed back to the merchant.\n if (cartTaxOwner === 'platform') {\n params.set(\n 'metadata[transferCents]',\n String(\n CommerceModel.platformLiableTransferCents({\n feeCents,\n merchantGoodsCents: chargedItemsCents + extrasCents,\n shippingFloorCents: cartShippingFloorCents,\n }),\n ),\n )\n params.set(\n 'metadata[transferShippingCents]',\n String(cartShippingFloorCents),\n )\n }\n // Set only when units were actually reserved (AGL-2356). Its ABSENCE is\n // what tells the webhook there is nothing to release — right for a cart of\n // untracked or backorder lines, and right for a session minted before this\n // deploy, neither of which reserved anything.\n if (heldStockKey) params.set('metadata[stockHoldKey]', heldStockKey)\n // A HALF-HOUR SESSION, AND IT IS THE HOLD'S WHOLE SAFETY ARGUMENT\n // (AGL-2356). Stripe's default is 24 hours, and a hold must outlive any\n // session that can still be paid — so the default would mean one abandoned\n // cart standing on a merchant's last unit for a day, which is exactly the\n // unbounded, invisible UNDER-sell this issue was held back for. Shortening\n // the SESSION is what bounds it; see `CHECKOUT_SESSION_TTL_MS` for why the\n // figure is 31 minutes and not Stripe's advertised 30-minute floor.\n params.set(\n 'expires_at',\n String(\n Math.floor((Date.now() + CommerceModel.CHECKOUT_SESSION_TTL_MS) / 1000),\n ),\n )\n\n // The payment methods the merchant switched off (AGL-3629): exclusions\n // only, on the hosted page and the in-page one alike.\n const paymentMethodControls =\n CommerceModel.resolveStorefrontPaymentMethodControls(\n CommerceModel.normalizeStorefrontPaymentMethodSettings(\n storeSettings.get('paymentMethods'),\n ),\n )\n CommerceModel.appendStorefrontPaymentMethodParams(\n params,\n paymentMethodControls,\n )\n\n // The Payment Element (AGL-1944), LAST and touching nothing above it. Every\n // figure the shopper is charged — line prices, the discount coupon, the tax\n // construction, the shipping rates, the fee — was decided before this line\n // and is identical on both checkout paths. This swaps the redirect's URL\n // pair for `ui_mode` + `return_url` and stops. Off unless the flag is on for\n // this org AND a publishable key exists; see `resolveNativeCheckoutMode`.\n const nativeMode = await resolveNativeCheckoutMode(\n String(ownerOrg?.org?.id ?? ''),\n )\n if (nativeMode.native) {\n applyNativeCheckoutParams(\n params,\n `${backUrl}${separator}order=success&session_id={CHECKOUT_SESSION_ID}`,\n )\n }\n // Apple Pay shows on a registered domain only (AGL-3629): registered\n // beside the session create, inside a budget, never failing the sale.\n const domainRegistration = nativeMode.native\n ? ensureCheckoutDomain({ pageUrl: backUrl, site: returnSite, hostId }).catch(\n () => null,\n )\n : Promise.resolve(null)\n const response = await fetch(\n 'https://api.stripe.com/v1/checkout/sessions',\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,\n 'Content-Type': 'application/x-www-form-urlencoded',\n // The half that costs real money (AGL-1697): Stripe replays the\n // existing session for a repeated key instead of opening a second\n // one, covering the window where the claim is written but the\n // response never arrives.\n // A tax service's answer can differ between two tries under one\n // key, so the key names it and a changed answer opens a new session\n // rather than a Stripe parameter mismatch (AGL-3631).\n // Likewise an extra's price (AGL-3635): a re-quote that moved opens\n // a new session instead of a parameter mismatch.\n ...stripeKeyHeader(\n `${\n engineTax.stamp\n ? `session-tax-${engineTax.quote?.taxCents ?? 'own'}-${engineTax.stamp.status}`\n : 'session'\n }${extrasCents > 0 ? `-x${extrasCents}` : ''}${creditHeldCents > 0 ? `-c${creditHeldCents}` : ''}`,\n ),\n // Empty on the hosted path (AGL-1944).\n ...nativeCheckoutStripeHeaders(nativeMode),\n },\n body: params.toString(),\n },\n )\n const session = (await response.json()) as {\n url?: string\n id?: string\n client_secret?: string\n error?: any\n }\n // `!session.url` was the liveness check and a `ui_mode` session has no url,\n // so it moves with the mode (AGL-1944) — otherwise every successful native\n // session reads as a Stripe failure: a 502 at the shopper, a released claim,\n // and a real Checkout Session left open on the merchant's account.\n await domainRegistration\n const payload = response.ok\n ? readCheckoutSessionPayload(\n session,\n nativeMode,\n paymentMethodControls.wallets,\n )\n : null\n if (!payload) {\n console.error('Stripe cart checkout error', session.error)\n // The checkout did not happen — hand the key back so one flaky moment\n // does not lock this cart out. The retry re-derives the same derived\n // keys, so Stripe replays whatever objects the first run did create.\n await releaseGiftCardHold()\n await releaseCreditHold()\n await releasePromotionHolds()\n await releaseStock()\n await claim.release()\n return res.status(502).json({ error: 'Checkout failed' })\n }\n // THE SESSION NOW EXISTS, SO THE SLOTS BELONG TO IT (AGL-2453). Past this\n // line a hold must survive: the shopper can still pay, and the webhook\n // settles against the reservation. A catch that released one here would\n // leave a paid order whose redemption is never counted — the same\n // over-redemption this fix closes, arriving through the release path.\n heldPromotions.length = 0\n // And so do the units (AGL-2356), for the stronger version of the same\n // reason: releasing here would hand the last unit to the next shopper while\n // this one still holds a payable session, which is the oversell itself.\n stockHold = null\n // Recoverable checkout (AGL-296): email captured before the redirect\n // makes abandonment actionable (AGL-323 sends the recovery emails). Written\n // for the native path too, and it matters MORE there: a shopper who\n // abandons an in-page form never leaves the store, so this row is the only\n // trace that a cart reached checkout at all.\n await hostRef\n .collection('checkouts')\n .doc(String(session.id))\n .set({\n cartId,\n ...(email ? { email } : {}),\n ...(marketingOptIn ? { marketingOptIn: true } : {}),\n itemsCents,\n resumeUrl: backUrl,\n status: 'open',\n // Explicit, so the recovery queue can COUNT it (AGL-3321): a query\n // cannot ask for a checkout that lacks an email or a reminder.\n recoveryState: checkoutRecoveryState({ email }),\n createdAtMs: Date.now(),\n })\n .catch(() => undefined)\n // The fact a merchant's own email platform starts an abandoned-cart flow\n // from (AGL-3639); nothing subscribes unless a site connected one.\n if (email) {\n await raiseCheckoutStarted(String(hostId), {\n id: String(session.id),\n email,\n marketingOptIn,\n currency: 'usd',\n itemsCents,\n resumeUrl: backUrl ?? null,\n items: startedItems,\n })\n }\n await claim.record(200, payload)\n return res.status(200).json(payload)\n } catch (error: any) {\n if (error?.visible) {\n // Line-availability refusals throw from the pricing loop, which sits\n // ABOVE the claim — so there is nothing to release and the key\n // survives, as a deterministic refusal should (AGL-1697).\n await releasePromotionHolds()\n await releaseStock()\n await claim?.release()\n return res.status(409).json({ error: error.visible })\n }\n console.error(error)\n // Release on the way out so a transient failure does not strand the key\n // (AGL-1691's rule) — nor a merchant's promotion slot (AGL-2453).\n await releasePromotionHolds()\n await releaseStock()\n await claim?.release()\n return res.status(500).json({ error: 'Checkout failed' })\n }\n}\n"],"names":["randomUUID","readSiteReturnHost","siteReturnUrl","Aglyn","CommerceModel","checkoutRecoveryState","claimAttempt","deriveStripeObjectKey","consumeRateLimit","firebaseAdmin","getOrgForHost","isYoungWorkspace","merchantAccountIsReady","checkoutSessionCardAuthenticationParams","CARRIER_POSTAL_CODE_MESSAGE","planCheckoutShippingWithCarriers","readCartId","resolveManualTaxRateId","allocateCentsByWeight","engineLineTaxPercentages","taxEngineSessionMetadata","quoteSaleTaxWithEngine","holdPromotionSlot","promotionHoldKey","holdStock","stockHoldKey","appendLocalFulfillmentMetadata","localFulfillmentLocationId","localStockRefusalMessage","planLocalFulfillment","readLocalFulfillmentRequest","readLocalFulfillmentStore","applyNativeCheckoutParams","nativeCheckoutStripeHeaders","readCheckoutSessionPayload","resolveNativeCheckoutMode","ensureCheckoutDomain","raiseCheckoutStarted","checkoutExtrasCents","encodeCheckoutExtrasMetadata","readChosenCheckoutExtras","quoteCartExtras","boundedCreditCents","checkoutCreditProviderForCode","encodeCheckoutCreditMetadata","normalizeCheckoutCreditAccount","normalizeCheckoutCreditCode","CHECKOUT_CREDIT_INVALID_MESSAGE","CHECKOUT_EXTRA_UNAVAILABLE_MESSAGE","cartCheckoutHandler","req","res","body","method","status","json","error","process","env","STRIPE_SECRET_KEY","JSON","parse","hostId","String","email","trim","toLowerCase","slice","marketingOptIn","Boolean","giftCardCode","toUpperCase","couponCode","creditCode","cartId","cookies","idempotencyKey","headers","claim","heldPromotions","releasePromotionHolds","hold","splice","release","stockHold","heldStockKey","releaseStock","held","cartSnapshot","storeSettings","ownerOrg","engineTax","firestore","app","hostRef","collection","doc","get","cart","data","lines","length","ownerId","org","ownerUid","checkEntitlement","ownerProfile","accountId","chargesEnabled","accountLivemode","subject","productSnapshots","Promise","all","Set","map","line","productId","id","productsById","Map","snapshot","exists","liftLegacyProduct","itemsCents","discountLines","giftCardCents","feeCents","feeApplies","chargedItemsCents","totalGrams","hasPhysicalLine","reserveLines","startedItems","extrasLines","params","URLSearchParams","mode","forEach","index","product","variant","deletedAt","Object","assign","Error","visible","name","variantId","variants","find","item","canPurchase","quantity","variantHasPrice","giftCard","stockIsReservable","Date","now","push","unitCents","Math","round","Number","priceUsd","sku","physical","type","amountCents","max","weightGrams","feePct","resolveTransactionFeePct","set","keys","options","values","join","young","overCeiling","giftCardPurchaseRefusal","buys","socket","remoteAddress","limit","GIFT_CARD_PURCHASE_LIMITS","perVisitorDailyYoung","perVisitorDaily","windowMs","allowed","localRequest","fulfillment","localStore","localPlan","request","store","kind","changed","fulfillmentChanged","shippingPlan","countries","country","option","settings","subtotalCents","destination","shippingCountry","postalCode","shippingPostalCode","CHECKOUT_SHIPPING_COUNTRIES","pickupLocations","filter","rateId","LOCAL_PICKUP_RATE_ID","needsPostalCode","needsShippingCountry","needsShippingPostalCode","shippingCountries","refusal","declared","normalizeCheckoutShippingCountry","CHECKOUT_SHIPPING_COUNTRY_NAMES","CART_UNPRICEABLE_SHIPPING_MESSAGE","chosenExtras","extras","chargedExtras","extra","includes","extrasChanged","extrasCents","offset","label","key","value","entries","discountsSnapshot","resolvedDiscount","resolveDiscount","docs","docSnapshot","$id","code","productIds","codeProblem","claimed","scopeId","orgId","busyMessage","replay","stripeKeyHeader","object","derived","stripeKey","holdKey","locationId","ok","reason","productName","STOCK_HELD_MESSAGE","slotKey","totalOffCents","benefit","discountRef","discountId","slot","ref","discount","PROMOTION_EXHAUSTED_MESSAGE","PROMOTION_UNAVAILABLE_MESSAGE","discountCents","shippingOptions","freeShipping","couponRef","couponSnapshot","coupon","percentOff","expired","expiresAtMs","enabled","releaseGiftCardHold","undefined","cardRef","nowMs","runTransaction","transaction","fresh","card","holds","pruneGiftCardHolds","mine","cents","available","giftCardAvailableCents","applyCents","min","swept","stale","FieldValue","delete","GIFT_CARD_HOLD_TTL_MS","merge","catch","console","releaseCreditHold","creditHeldCents","refuseCredit","creditCodeInvalid","entry","offered","provider","channel","account","resolve","customerEmail","staff","providerId","maxCents","reference","currency","last4","stripeCoupon","fetch","Authorization","amount_off","duration","toString","then","response","cartEngineLines","NaN","isFinite","cartEngineDiscounts","cartEngineNet","position","taxExempt","exempt","quote","stamp","cartTaxSettings","shippingCeilingCents","reduce","most","manualRate","pricesIncludeTax","resolveTaxRate","origin","manualTaxPct","pct","chargeCents","taxCents","saleProcessingCostCents","appendShippingAddressCollectionParams","appendCheckoutShippingParams","taxSettings","taxDecision","storefrontTaxDecision","STOREFRONT_TAX_UNDECIDED_MESSAGE","taxMisconfigured","storefrontTaxMisconfiguration","engineAnswer","percentages","taxRateId","taxPct","taxLabel","rate","taxableIndexes","returnSite","backUrl","candidates","referer","host","site","requestHost","separator","cartTaxOwner","cartShippingFloorCents","shippingFloorCents","destinationChargeParams","taxOwner","merchantGoodsCents","metadata","quotedPostalCode","platformLiableTransferCents","floor","CHECKOUT_SESSION_TTL_MS","paymentMethodControls","resolveStorefrontPaymentMethodControls","normalizeStorefrontPaymentMethodSettings","appendStorefrontPaymentMethodParams","nativeMode","native","domainRegistration","pageUrl","session","payload","wallets","resumeUrl","recoveryState","createdAtMs","items","record"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/cart-checkout.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 { randomUUID } from 'crypto'\nimport { readSiteReturnHost, siteReturnUrl } from '@aglyn/aglyn/app-utils/site-return-url'\nimport type { AttemptClaim, PluginApiHandler } from '@aglyn/aglyn/server'\nimport {\n advertisingRequestFacts,\n recordAdvertisingOrderConsent,\n} from '@aglyn/aglyn/plugin-manager/plugin-advertising-conversions'\nimport * as Aglyn from '@aglyn/aglyn/server'\nimport * as CommerceModel from '../model'\nimport { checkoutRecoveryState } from '../model/checkout-recovery'\nimport { claimAttempt, deriveStripeObjectKey } from '@aglyn/aglyn/server'\nimport {\n consumeRateLimit,\n firebaseAdmin,\n getOrgForHost,\n isYoungWorkspace,\n} from '@aglyn/tenant-data-admin'\nimport { merchantAccountIsReady } from '@aglyn/tenant-data-admin/server/payment-provider'\nimport { checkoutSessionCardAuthenticationParams } from '@aglyn/tenant-data-admin/server/stripe-card-authentication'\nimport {\n CARRIER_POSTAL_CODE_MESSAGE,\n planCheckoutShippingWithCarriers,\n type CarrierShippingPlan,\n} from './carrier-shipping'\nimport { readCartId } from './cart-cookie'\nimport { resolveManualTaxRateId } from './manual-tax-rate'\nimport {\n allocateCentsByWeight,\n engineLineTaxPercentages,\n taxEngineSessionMetadata,\n} from '../model/commerce-tax-engine'\nimport { quoteSaleTaxWithEngine } from './tax-engine-quote'\nimport {\n type PromotionSlotHold,\n holdPromotionSlot,\n promotionHoldKey,\n} from './promotion-hold'\nimport { type StockHoldLine, holdStock, stockHoldKey } from './stock-hold'\nimport {\n appendLocalFulfillmentMetadata,\n localFulfillmentLocationId,\n localStockRefusalMessage,\n planLocalFulfillment,\n readLocalFulfillmentRequest,\n readLocalFulfillmentStore,\n} from './local-fulfillment'\nimport {\n applyNativeCheckoutParams,\n nativeCheckoutStripeHeaders,\n readCheckoutSessionPayload,\n resolveNativeCheckoutMode,\n} from './native-checkout'\nimport { ensureCheckoutDomain } from './payment-method-domains'\nimport { raiseCheckoutStarted } from './order-events'\nimport {\n checkoutExtrasCents,\n encodeCheckoutExtrasMetadata,\n readChosenCheckoutExtras,\n} from '@aglyn/aglyn/plugin-manager/plugin-checkout-extras'\nimport { quoteCartExtras, type CheckoutExtrasLine } from './checkout-extras'\nimport {\n boundedCreditCents,\n checkoutCreditProviderForCode,\n encodeCheckoutCreditMetadata,\n normalizeCheckoutCreditAccount,\n normalizeCheckoutCreditCode,\n} from '@aglyn/aglyn/plugin-manager/plugin-checkout-credits'\n\n/** A rewards or credit code no provider on this site takes (AGL-3640). */\nexport const CHECKOUT_CREDIT_INVALID_MESSAGE = 'That rewards code is not valid.'\n\n/** An option the buyer ticked is no longer offered (AGL-3635). */\nexport const CHECKOUT_EXTRA_UNAVAILABLE_MESSAGE =\n 'An option you picked is no longer available. Review your cart and try again.'\n\n/**\n * Cart checkout (AGL-293): the whole cart in one Stripe Checkout\n * session on the merchant's account. Every line re-prices from its\n * product doc, unavailable lines block with a visible error, the\n * platform fee sums per line by product type (AGL-278 ladder), and the\n * webhook creates one multi-line order and clears the cart.\n */\n\nexport const cartCheckoutHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n if (!process.env.STRIPE_SECRET_KEY) {\n return res\n .status(501)\n .json({ error: 'Purchases are not configured on this site.' })\n }\n const body =\n typeof req.body === 'string' ? JSON.parse(req.body) : (req.body ?? {})\n const hostId = String(body.hostId ?? '')\n const email = String(body.email ?? '').trim().toLowerCase().slice(0, 120)\n const marketingOptIn = Boolean(body.marketingOptIn)\n const giftCardCode = String(body.giftCardCode ?? '')\n .trim()\n .toUpperCase()\n .slice(0, 24)\n const couponCode = String(body.couponCode ?? '')\n .trim()\n .toUpperCase()\n .slice(0, 40)\n // A rewards or referral code another plugin keeps the account for (AGL-3640).\n const creditCode = normalizeCheckoutCreditCode(body.creditCode)\n if (!hostId) return res.status(400).json({ error: 'Missing hostId' })\n // AGL-1769: validated here even though this handler only READS the cart,\n // because it is where the raw cookie left the request — `:342` stamps it\n // into `metadata[cartId]`, and the billing webhook builds a document path\n // from that copy. Closing it at the source is what lets the webhook keep\n // taking its metadata at face value.\n const cartId = readCartId(req.cookies, hostId)\n if (!cartId) return res.status(400).json({ error: 'Your cart is empty' })\n // One checkout attempt, minted by the cart drawer (AGL-1697). Node\n // lowercases incoming headers, but read both spellings — the plugin API\n // request type makes no promise about casing.\n const idempotencyKey = String(\n req.headers['idempotency-key'] ?? req.headers['Idempotency-Key'] ?? '',\n ).trim().slice(0, 200)\n\n let claim: AttemptClaim | null = null\n /**\n * Promotion slots this attempt reserved (AGL-2453), hoisted so the catch\n * below can hand them back too. A throw after a hold is placed is exactly the\n * case where an invisible slot would sit against a merchant's cap for a day.\n */\n const heldPromotions: PromotionSlotHold[] = []\n const releasePromotionHolds = async (): Promise<void> => {\n for (const hold of heldPromotions.splice(0)) await hold.release()\n }\n /**\n * The units this attempt reserved (AGL-2356), hoisted for the same reason —\n * a throw after the hold is placed is exactly the case where a merchant's\n * last unit would sit invisibly spoken for until the TTL lapsed.\n */\n let stockHold: { holdKey: string; release: () => Promise<void> } | null = null\n let heldStockKey = ''\n const releaseStock = async (): Promise<void> => {\n const held = stockHold\n stockHold = null\n heldStockKey = ''\n if (held) await held.release()\n }\n try {\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const cartSnapshot = await hostRef.collection('carts').doc(cartId).get()\n const cart = (cartSnapshot.data() as CommerceModel.HostCart | undefined) ?? {\n lines: [],\n }\n if (cart.lines.length === 0) {\n return res.status(400).json({ error: 'Your cart is empty' })\n }\n\n const ownerOrg = await getOrgForHost(hostId)\n const ownerId = ownerOrg?.org?.ownerUid\n if (!ownerId) {\n return res.status(409).json({ error: 'This site cannot sell yet' })\n }\n // Storefront selling is the `commerce` entitlement (Starter+) — not\n // `marketplaceSelling`, which gates the marketplace marketplace (AGL-470).\n if (!Aglyn.checkEntitlement(ownerOrg.org as any, 'commerce')) {\n return res.status(403).json({ error: 'Selling is not enabled' })\n }\n const ownerProfile = await firestore\n .collection('profiles')\n .doc(String(ownerId))\n .get()\n const accountId = ownerProfile.get('stripeAccountId')\n if (\n !merchantAccountIsReady(\n {\n accountId,\n chargesEnabled: ownerProfile.get('stripeChargesEnabled'),\n accountLivemode: ownerProfile.get('stripeAccountLivemode'),\n },\n { subject: `cart checkout host ${hostId}` },\n )\n ) {\n return res\n .status(409)\n .json({ error: 'This site has not enabled payments yet' })\n }\n\n // Re-price every line from its doc; any unavailable line blocks.\n const productSnapshots = await Promise.all(\n [...new Set(cart.lines.map((line) => line.productId))].map((id) =>\n hostRef.collection('products').doc(id).get(),\n ),\n )\n const productsById = new Map(\n productSnapshots.map((snapshot) => [\n snapshot.id,\n snapshot.exists ? CommerceModel.liftLegacyProduct(snapshot.data() as any) : null,\n ]),\n )\n let itemsCents = 0\n /** Per-line values for scoped discount pricing. */\n const discountLines: { productId: string; amountCents: number }[] = []\n // Gift-card value in the basket, for the cash-out ceiling (AGL-3363).\n let giftCardCents = 0\n let feeCents = 0\n /** Whether any line carries a fee rate above zero (AGL-2232). */\n let feeApplies = false\n /**\n * The goods the shopper actually pays for, after any discount that reached\n * the session (AGL-2232). Starts as the whole basket and is lowered only\n * when a Stripe coupon is minted, which is the one reduction the fee is\n * scaled by.\n */\n let chargedItemsCents = 0\n // Weight-tier shipping rates price off this (AGL-1707). Without it every\n // `weight_tiers` rate would resolve at 0g and quote the lightest tier.\n let totalGrams = 0\n // Whether anything in this cart is posted at all (AGL-1721). Buy-now has\n // always resolved shipping for physical products only; the cart never\n // asked, so a cart of downloads could be charged to ship. It matters more\n // now that an unresolvable destination REFUSES: a shopper buying two PDFs\n // must not be asked which country to ship them to.\n let hasPhysicalLine = false\n /**\n * What this cart will RESERVE (AGL-2356), collected as the loop below\n * resolves each line's variant rather than re-derived afterwards: the\n * reservation and the price must be taken against the same variant, and a\n * second resolution is a second chance to pick a different one.\n */\n const reserveLines: StockHoldLine[] = []\n /** The basket as `checkout.started` reports it (AGL-3639). */\n const startedItems: Parameters<typeof raiseCheckoutStarted>[1]['items'] = []\n /** The basket as an extras offer prices it (AGL-3635). */\n const extrasLines: CheckoutExtrasLine[] = []\n // Cart checkout never builds subscription sessions — every line bills\n // one-time in `payment` mode (recurring products subscribe through the\n // PDP's direct checkout, AGL-303) — so the buyer-chosen billing field\n // (AGL-545) does not apply here.\n const params = new URLSearchParams({\n mode: 'payment',\n // 3-D Secure, from the one seam every card payment shares (AGL-3360).\n ...checkoutSessionCardAuthenticationParams('payment'),\n })\n cart.lines.forEach((line, index) => {\n const product = productsById.get(line.productId)\n if (!product || product.deletedAt || product.status !== 'active') {\n throw Object.assign(new Error('unavailable'), {\n visible: `\"${product?.name ?? 'A product'}\" is no longer available`,\n })\n }\n const variant = line.variantId\n ? product.variants.find((item) => item.id === line.variantId)\n : product.variants[0]\n if (!variant || !CommerceModel.canPurchase(product, variant.id, line.quantity)) {\n throw Object.assign(new Error('unavailable'), {\n visible: `\"${product.name}\" is sold out`,\n })\n }\n // A variant with no price yet (AGL-2916) is not for sale: pricing it\n // would charge nothing for it, or send the processor no amount.\n if (!CommerceModel.variantHasPrice(variant)) {\n throw Object.assign(new Error('unavailable'), {\n visible: `\"${product.name}\" is not available`,\n })\n }\n // Gift cards are a Business+ entitlement, checked per sale (AGL-470).\n if (\n product.giftCard &&\n !Aglyn.checkEntitlement(ownerOrg.org as any, 'giftCards')\n ) {\n throw Object.assign(new Error('unavailable'), {\n visible: `\"${product.name}\" is not available on this store's plan`,\n })\n }\n // Only the lines there is something to reserve ON. An untracked variant\n // and a `backorder` product have no finite shelf, so a hold could never\n // change what they answer — and collecting them would open a transaction\n // over the products of a purely digital storefront to write nothing at\n // all. The same decision `holdPromotionSlot` makes for an UNCAPPED\n // promotion: the unlimited case stays off the write path entirely.\n //\n // A pre-filter, so the authority is still the in-transaction re-check in\n // `holdStock`. The only thing it can get wrong is skipping a hold for a\n // variant that became tracked between this read and that transaction,\n // which is the behaviour that shipped before this issue.\n if (CommerceModel.stockIsReservable(product, variant.id, Date.now())) {\n reserveLines.push({\n productId: line.productId,\n variantId: variant.id,\n quantity: line.quantity,\n })\n }\n const unitCents = Math.round(Number(variant.priceUsd) * 100)\n itemsCents += unitCents * line.quantity\n extrasLines.push({\n productId: line.productId,\n name: product.name,\n ...((variant as { sku?: string }).sku ? { sku: String((variant as { sku?: string }).sku) } : {}),\n quantity: line.quantity,\n unitCents,\n physical: (product.type ?? 'physical') === 'physical',\n })\n startedItems.push({\n productId: line.productId,\n variantId: variant.id ?? null,\n name: product.name,\n sku: (variant as { sku?: string }).sku ?? null,\n quantity: line.quantity,\n unitCents,\n })\n if (product.giftCard) giftCardCents += unitCents * line.quantity\n // What THIS line is worth, so a product-scoped discount can be priced\n // against the lines it actually covers rather than the whole basket\n //.\n discountLines.push({\n productId: line.productId,\n amountCents: unitCents * line.quantity,\n })\n totalGrams += Math.max(0, Number(variant.weightGrams ?? 0)) * line.quantity\n // A missing `type` reads as physical, exactly as the fee ladder below\n // reads it — `liftLegacyProduct` only defaults the field on docs it\n // synthesizes variants for, so a part-migrated doc can reach here\n // without one and the two reads must not disagree about what it is.\n if ((product.type ?? 'physical') === 'physical') hasPhysicalLine = true\n // `?? 'physical'`, which the comment four lines up already promised and\n // the code did not deliver (AGL-2251). `resolveTransactionFeePct` keys\n // on `productType === 'physical'` and sends everything else — `undefined`\n // included — to the DIGITAL rate, so a part-migrated product doc with no\n // `type` was priced at 3%/2%/1% here while `checkout.ts` (`lifted.type ??\n // 'physical'`) priced the identical product at the physical rate. The\n // same basket cost the merchant a different fee depending on which\n // button the shopper pressed.\n const feePct = Aglyn.resolveTransactionFeePct(\n ownerOrg.org as any,\n product.type ?? 'physical',\n )\n // WHETHER a rate applies is not the same fact as what it rounds to\n // (AGL-2232), and only the sum was being kept. `checkout.ts`,\n // `draft-order.ts` and `reserve.ts` all floor at `Math.max(1, …)` when\n // `feePct > 0`; the cart floored nothing, so a Scale-plan cart of $0.30\n // digital lines rounded every line to 0, the emission guard below read\n // `feeCents > 0` as \"this plan charges nothing\", and the session went out\n // with no application fee at all.\n if (feePct > 0) feeApplies = true\n feeCents += Math.round((unitCents * line.quantity * feePct) / 100)\n params.set(`line_items[${index}][quantity]`, String(line.quantity))\n params.set(`line_items[${index}][price_data][currency]`, 'usd')\n params.set(\n `line_items[${index}][price_data][unit_amount]`,\n String(unitCents),\n )\n params.set(\n `line_items[${index}][price_data][product_data][name]`,\n `${product.name}${\n Object.keys(variant.options ?? {}).length\n ? ` (${Object.values(variant.options ?? {}).join(' / ')})`\n : ''\n }`.slice(0, 120),\n )\n })\n\n // GIFT CARDS ARE A CASH-OUT (AGL-3363): a ceiling per order, and per\n // visitor per day, lower while the workspace is young. Above the claim,\n // like the line refusals, so a refusal keeps the key.\n if (giftCardCents > 0) {\n const young = isYoungWorkspace(ownerOrg.org as { createdAt?: unknown })\n const overCeiling = CommerceModel.giftCardPurchaseRefusal({ giftCardCents, young })\n if (overCeiling) {\n throw Object.assign(new Error('unavailable'), { visible: overCeiling })\n }\n const buys = await consumeRateLimit(\n `giftcard:buy:${hostId}:${String(req.socket?.remoteAddress ?? '') || 'no-address'}`,\n {\n limit: young\n ? CommerceModel.GIFT_CARD_PURCHASE_LIMITS.perVisitorDailyYoung\n : CommerceModel.GIFT_CARD_PURCHASE_LIMITS.perVisitorDaily,\n windowMs: 24 * 60 * 60 * 1000,\n },\n )\n if (!buys.allowed) {\n throw Object.assign(new Error('unavailable'), {\n visible:\n 'Gift cards can only be bought a few times a day from one place. ' +\n 'Please try again tomorrow, or contact the shop.',\n })\n }\n }\n\n // Shipping (AGL-1707), from the settings document tax is also read from.\n // This is the ONLY thing that makes Stripe charge shipping: without\n // `shipping_options` it presents no shipping choice,\n // `total_details.amount_shipping` is 0, and the zones and rates the\n // merchant configured in the console are silently worth nothing.\n //\n // RESOLVED BEFORE ANY STRIPE CALL, ahead of the discount block below,\n // because the plan can refuse — and the discount block creates real coupon\n // objects on the merchant's account. Refusing after them would leave one\n // orphaned on every attempt.\n //\n // A merchant who has configured nothing plans to no options and gets a\n // session byte-identical to today's: no shipping charged, exactly as\n // before. Only a merchant who set rates up starts collecting them.\n const storeSettings = await hostRef\n .collection('settings')\n .doc('store')\n .get()\n // PICKUP OR THE STORE'S OWN DELIVERY (AGL-3624), declared at the cart and\n // decided here from the store's settings: the location, the zone's fee and\n // minimum, the window. Above the claim, so a refusal keeps the key and the\n // shopper's same button works once they choose again. See\n // `local-fulfillment.ts`.\n const localRequest = readLocalFulfillmentRequest(body.fulfillment)\n const localStore = hasPhysicalLine\n ? await readLocalFulfillmentStore({\n hostRef,\n storeSettings: (storeSettings.data?.() ?? null) as Record<string, unknown> | null,\n org: ownerOrg.org as { timeZone?: string },\n })\n : null\n const localPlan = localStore\n ? await planLocalFulfillment({\n hostId,\n request: localRequest,\n itemsCents,\n hasPhysicalLine,\n store: localStore,\n })\n : ({ kind: 'shipping' } as const)\n if (localPlan.kind === 'refusal') {\n return res.status(localPlan.status).json({\n error: localPlan.error,\n ...(localPlan.changed ? { fulfillmentChanged: localPlan.changed } : {}),\n })\n }\n // A live carrier rate (AGL-3612) is quoted here too, before the session,\n // for the destination the shopper declared; a store with none plans from\n // its table exactly as before. See `carrier-shipping.ts`.\n const shippingPlan: CarrierShippingPlan =\n // Collected: nothing to price, no address to ask for.\n localPlan.kind === 'pickup'\n ? { countries: [], options: [] }\n : // Driven by the store: its one fee, an address in its country only.\n localPlan.kind === 'local_delivery'\n ? { countries: [localPlan.country], options: [localPlan.option] }\n : hasPhysicalLine\n ? await planCheckoutShippingWithCarriers({\n hostId,\n settings: storeSettings.get('shipping') as\n | CommerceModel.ShippingSettings\n | undefined,\n cart: { subtotalCents: itemsCents, totalGrams },\n destination: {\n country: body.shippingCountry,\n postalCode: body.shippingPostalCode,\n },\n })\n : { countries: CommerceModel.CHECKOUT_SHIPPING_COUNTRIES, options: [] }\n // A store whose locations offer pickup has the shopper choose WHERE at\n // the cart (AGL-3624); the old unnamed `Local pickup` rate would be a\n // second, unrouted pickup inside a session the shopper chose shipping for.\n if (localPlan.kind === 'shipping' && localStore?.pickupLocations.length) {\n shippingPlan.options = shippingPlan.options.filter(\n (option) => option.rateId !== CommerceModel.LOCAL_PICKUP_RATE_ID,\n )\n }\n if (shippingPlan.needsPostalCode) {\n return res.status(400).json({\n error: CARRIER_POSTAL_CODE_MESSAGE,\n needsShippingCountry: true,\n needsShippingPostalCode: true,\n shippingCountries: [...CommerceModel.CHECKOUT_SHIPPING_COUNTRIES],\n })\n }\n // The shopper is asked, then the answer is ENFORCED (AGL-1721): a declared\n // destination narrows `allowed_countries` to itself, so the rate resolved\n // for it is the only one on the session and no other address can be\n // entered against it. Answering the 400 is not optional — a caller that\n // skips the field is refused again rather than served the union.\n if (shippingPlan.refusal === 'destination-required') {\n return res.status(400).json({\n error: 'Choose where you’re shipping to.',\n needsShippingCountry: true,\n shippingCountries: [...CommerceModel.CHECKOUT_SHIPPING_COUNTRIES],\n })\n }\n if (shippingPlan.refusal === 'destination-unserved') {\n const declared = CommerceModel.normalizeCheckoutShippingCountry(\n body.shippingCountry,\n )\n return res.status(409).json({\n error: `This store does not ship to ${\n CommerceModel.CHECKOUT_SHIPPING_COUNTRY_NAMES[declared as string] ??\n 'that country'\n }.`,\n needsShippingCountry: true,\n shippingCountries: [...CommerceModel.CHECKOUT_SHIPPING_COUNTRIES],\n })\n }\n // The merchant priced shipping and their table cannot reach this cart\n // (AGL-2232) — a 3 kg basket at a store whose weight tiers stop at 2 kg.\n // No `needsShippingCountry`: asking where it goes cannot price it, and a\n // shopper handed the country select would answer it and be refused again.\n if (shippingPlan.refusal === 'cart-unpriceable') {\n return res\n .status(409)\n .json({ error: CommerceModel.CART_UNPRICEABLE_SHIPPING_MESSAGE })\n }\n\n // OPTIONAL LINES THE BUYER TICKED (AGL-3635) — package protection — asked\n // of core's checkout-extras seam AGAIN here, never taken from the request:\n // the body names which offers, the provider names the price. Above the\n // claim, so a refusal keeps the key. An offer the buyer ticked that is no\n // longer made refuses rather than selling without it: they asked for\n // cover, and a parcel that silently has none is the worse surprise.\n const chosenExtras = readChosenCheckoutExtras(body.extras)\n const chargedExtras = chosenExtras.length\n ? (\n await quoteCartExtras({\n hostId,\n lines: extrasLines,\n destination: { country: body.shippingCountry, postalCode: body.shippingPostalCode },\n })\n ).filter((extra) => chosenExtras.includes(extra.id))\n : []\n if (chargedExtras.length !== chosenExtras.length) {\n return res.status(409).json({ error: CHECKOUT_EXTRA_UNAVAILABLE_MESSAGE, extrasChanged: true })\n }\n const extrasCents = checkoutExtrasCents(chargedExtras)\n // Each as its own line after the goods, untaxed (`txcd_00000000`, Stripe's\n // nontaxable code) and outside every per-line tax, engine and fee loop\n // below, which read `cart.lines` only: an extra is not goods, so the\n // platform's take is not charged on it.\n chargedExtras.forEach((extra, offset) => {\n const index = cart.lines.length + offset\n params.set(`line_items[${index}][quantity]`, '1')\n params.set(`line_items[${index}][price_data][currency]`, 'usd')\n params.set(`line_items[${index}][price_data][unit_amount]`, String(extra.amountCents))\n params.set(`line_items[${index}][price_data][product_data][name]`, extra.label.slice(0, 120))\n params.set(`line_items[${index}][price_data][product_data][tax_code]`, 'txcd_00000000')\n })\n for (const [key, value] of Object.entries(encodeCheckoutExtrasMetadata(chargedExtras))) {\n params.set(`metadata[${key}]`, value)\n }\n\n chargedItemsCents = itemsCents\n // Discounts engine (AGL-305): entered codes and automatic\n // promotions from hosts/{id}/discounts; the AGL-96 coupons remain a\n // fallback for unknown codes.\n const discountsSnapshot = await hostRef\n .collection('discounts')\n .limit(100)\n .get()\n const resolvedDiscount = CommerceModel.resolveDiscount(\n discountsSnapshot.docs.map((docSnapshot) => ({\n ...(docSnapshot.data() as CommerceModel.HostDiscount),\n $id: docSnapshot.id,\n })),\n {\n ...(couponCode ? { code: couponCode } : {}),\n subtotalCents: itemsCents,\n productIds: cart.lines.map((line) => line.productId),\n lines: discountLines,\n },\n )\n if (resolvedDiscount?.codeProblem) {\n return res.status(400).json({ error: resolvedDiscount.codeProblem })\n }\n\n // Point of no return (AGL-1697): everything past here creates Stripe\n // objects — up to two coupons and the session — on the merchant's live\n // account. The cart is not consumed until the webhook clears it, so\n // before the claim one cart could spawn unlimited sessions, each retry\n // leaving an orphan `checkouts/{sessionId}` doc (which feeds the AGL-323\n // abandoned-cart emails) plus a stray coupon.\n //\n // The two refusals that remain BELOW this line (invalid legacy coupon,\n // empty gift card) each release the claim: they are deterministic, the\n // shopper fixes the code and presses the same button, and because the\n // retry re-derives the same digest Stripe replays any coupon the first\n // run already minted rather than creating a twin (AGL-1714's rule).\n const claimed = await claimAttempt(firestore, {\n kind: 'commerce-cart',\n scopeId: `${hostId}:${cartId}`,\n orgId: String(ownerOrg?.org?.id ?? ''),\n key: idempotencyKey,\n busyMessage: 'This checkout is already being processed',\n })\n if ('replay' in claimed) {\n return res.status(claimed.replay.status).json(claimed.replay.body)\n }\n claim = claimed.claim\n // Derived per OBJECT, never the raw digest (AGL-1714): Stripe\n // parameter-compares a repeated key account-wide, so one digest sent to\n // `/v1/coupons` and then `checkout/sessions` would error the second call.\n const stripeKeyHeader = (object: string): Record<string, string> => {\n const derived = deriveStripeObjectKey(claimed.claim.stripeKey, object)\n return derived ? { 'Idempotency-Key': derived } : {}\n }\n\n // THE UNITS ARE RESERVED HERE (AGL-2356), not merely read in the loop\n // above.\n //\n // `canPurchase` up there is a plain `.get()` compared against a number, and\n // the webhook decremented minutes later without ever re-asking. Nothing was\n // written, so there was no document to contend on: every shopper who\n // reached this line while the shelf held one unit passed it, and they all\n // paid. This is the write that makes them serialise.\n //\n // ONE transaction across every product in the cart, so a two-product basket\n // reserves both or neither — a partial reservation would charge a shopper\n // for goods the store cannot fill, which is this issue's own defect\n // arriving through its fix. See `stock-hold.ts`.\n //\n // FIRST among the three holds this handler takes, before the promotion\n // slots and the gift card below: stock is the likeliest refusal, and\n // reserving a merchant's capped discount for a basket the store cannot fill\n // is a slot held for nothing.\n {\n const held = await holdStock({\n firestore,\n hostRef,\n holdKey: stockHoldKey(claimed.claim.stripeKey),\n lines: reserveLines,\n label: `cart ${hostId}/${cartId}`,\n // At the pickup location, or the one deliveries leave from (AGL-3624).\n locationId: localFulfillmentLocationId(localPlan),\n })\n if (!held.ok) {\n // Nothing has been minted yet, so the key goes back and the same\n // button works when the units return (AGL-1697). `sold-out` names the\n // shelf being SPOKEN FOR rather than empty — the merchant has stock and\n // another shopper is in checkout with it, and telling this one to come\n // back in a few minutes is the difference between a wait and a loss.\n await releaseStock()\n await claim.release()\n return res.status(409).json({\n error:\n held.reason === 'sold-out-at-location'\n ? localStockRefusalMessage(localPlan, held.productName)\n : held.reason === 'sold-out'\n ? held.productName\n ? `\"${held.productName}\" — ${CommerceModel.STOCK_HELD_MESSAGE}`\n : CommerceModel.STOCK_HELD_MESSAGE\n : 'Your cart is no longer available',\n })\n }\n if (held.holdKey) {\n stockHold = held\n heldStockKey = held.holdKey\n }\n }\n\n // ONE Stripe discount, and that is the whole shape of this block\n // (AGL-2112).\n //\n // A Checkout Session takes exactly one entry in `discounts`. This used to\n // be three independent blocks — an AGL-305 discount, a legacy AGL-96\n // coupon code, and an AGL-322 gift card — each minting its own Stripe\n // coupon and each doing `params.set('discounts[0][coupon]', …)`.\n // `URLSearchParams.set` REPLACES, so a cart carrying a discount AND a gift\n // card minted two real coupon objects on the merchant's account and sent\n // only the LAST one. The shopper paid full price minus the gift card; the\n // discount silently evaporated — and the webhook still burned its\n // redemption against `maxRedemptions`, so the code was spent on an order\n // that never got it.\n //\n // The fee was wrong in the same combination and in Aglyn's favour by\n // accident, then against it: `feeCents` was scaled DOWN once per block, so\n // two reductions compounded for one discount that was actually applied.\n //\n // So: resolve every reduction FIRST, add them up, and mint a single\n // `amount_off` coupon for the sum. A percentage coupon becomes its cash\n // equivalent against `itemsCents` — which is what the fee arithmetic here\n // has always assumed it was, and what `checkout.ts` already does for\n // buy-now (`amountCents * (100 - percentOff) / 100`).\n //\n // Validation still happens in the same order and still 400s the same way,\n // BEFORE anything is minted — the shopper fixes the code and presses the\n // same button, so the claim is released and the key survives (AGL-1697).\n // REDEMPTION SLOTS ARE HELD, NOT MERELY READ (AGL-2453).\n //\n // `resolveDiscount` above and the coupon `.get()` below both answered \"is\n // there a slot left?\" from a plain read, and the webhook incremented the\n // counter minutes later without ever re-asking. The gap is the whole\n // Checkout Session lifetime — up to 24 hours — so every shopper who reached\n // this line while a cap of 100 sat at 99 passed it, and the counter\n // finished at 99+N. Nothing was lost; nothing was ever CHECKED.\n //\n // The AGL-2449 shape, reused: the slot is claimed in a transaction here and\n // settled in one at the webhook. Both redemption paths hold — the typed\n // coupon and the automatic discount — because holding one of two doors onto\n // the same counter fixes neither, and the automatic path is the one a\n // shopper does not even have to opt into.\n //\n // A held slot COUNTS while its session is live and is released when the\n // session expires, is cancelled, or this handler refuses below. See\n // `model/commerce-promotions.ts` for why that is the right way round.\n const slotKey = promotionHoldKey(claim.stripeKey)\n let totalOffCents = 0\n if (resolvedDiscount && resolvedDiscount.benefit.kind !== 'none') {\n const discountRef = hostRef\n .collection('discounts')\n .doc(resolvedDiscount.discountId)\n const slot = await holdPromotionSlot({\n firestore,\n ref: discountRef,\n holdKey: slotKey,\n label: `discount ${resolvedDiscount.discountId}`,\n })\n if (!slot.ok) {\n // Refused rather than silently dropped, and that is a deliberate\n // choice for the AUTOMATIC case: this discount was in the price the\n // storefront showed, so continuing without it would charge more than\n // the shopper agreed to and tell them nothing. Nothing has been minted\n // yet, so this is retryable — and the retry prices correctly, because\n // `resolveDiscount` counts holds too and simply stops offering it.\n await claim.release()\n return res.status(409).json({\n error:\n couponCode && resolvedDiscount.discount.code\n ? CommerceModel.PROMOTION_EXHAUSTED_MESSAGE\n : CommerceModel.PROMOTION_UNAVAILABLE_MESSAGE,\n })\n }\n if (slot.holdKey) heldPromotions.push(slot)\n totalOffCents += resolvedDiscount.discountCents\n params.set('metadata[discountId]', resolvedDiscount.discountId)\n // Only when a slot was actually reserved. Its ABSENCE is what tells the\n // webhook to fall back to the unconditional increment, which is right for\n // an uncapped promotion and for a session minted before this deploy.\n if (slot.holdKey) {\n params.set('metadata[discountHoldKey]', slot.holdKey)\n }\n }\n // FREE SHIPPING IS A ZEROED RATE, NOT A COUPON.\n //\n // A session-level Stripe coupon discounts LINE ITEMS; shipping is a\n // separate `shipping_options` concept and a coupon never touches it. There\n // is also no amount to discount at this point — the shopper picks their\n // rate after the session is created — so the only construction that can be\n // exact is to offer every rate at zero.\n //\n // Every rate, not just the cheapest: `free_shipping` carries no field\n // scoping it to one, so the merchant asked for shipping to be free rather\n // than for one particular rate to be. Zeroing the dearest too also keeps\n // the fee and transfer arithmetic below honest, since both read these same\n // options and the shopper is paying nothing for carriage either way.\n const shippingOptions =\n resolvedDiscount?.freeShipping === true\n ? shippingPlan.options.map((option) => ({ ...option, amountCents: 0 }))\n : shippingPlan.options\n\n // Coupons (AGL-96 semantics): percent off the items total.\n if (couponCode && !resolvedDiscount) {\n const couponRef = hostRef.collection('coupons').doc(couponCode)\n const couponSnapshot = await couponRef.get()\n const coupon = couponSnapshot.data() as any\n const percentOff = Number(coupon?.percentOff ?? 0)\n const expired =\n coupon?.expiresAtMs != null && Number(coupon.expiresAtMs) < Date.now()\n if (\n !coupon ||\n coupon.enabled === false ||\n expired ||\n !(percentOff > 0 && percentOff <= 100)\n ) {\n // Deterministic and retryable — the shopper fixes the code and\n // presses the same button, so the key survives (AGL-1697).\n await claim.release()\n return res.status(400).json({ error: 'Invalid or expired coupon' })\n }\n // The exhaustion test moved INTO a transaction that also writes\n // (AGL-2453). It used to be a comparison against a plain `.get()` here,\n // and the webhook incremented the counter minutes later without\n // re-asking — so the check bounded nothing once two shoppers held\n // sessions at the same time.\n const slot = await holdPromotionSlot({\n firestore,\n ref: couponRef,\n holdKey: slotKey,\n label: `coupon ${couponCode}`,\n })\n if (!slot.ok) {\n await claim.release()\n return res.status(400).json({\n error:\n slot.reason === 'exhausted'\n ? CommerceModel.PROMOTION_EXHAUSTED_MESSAGE\n : 'Invalid or expired coupon',\n })\n }\n if (slot.holdKey) {\n heldPromotions.push(slot)\n params.set('metadata[couponHoldKey]', slot.holdKey)\n }\n totalOffCents += Math.round((itemsCents * percentOff) / 100)\n params.set('metadata[couponCode]', couponCode)\n }\n\n // Gift cards (AGL-322): balance applies as amount-off; the webhook settles\n // the hold this places on completion.\n //\n // The read is a TRANSACTION and it WRITES (AGL-2449). A gift card is cash,\n // and a plain `.get()` here was the last unreserved limited resource in the\n // order path: two checkouts entering the same code both read $50, both got\n // $50 off, and the webhook's bare `increment(-N)` settled the card at\n // -$50 — $100 of goods against a $50 card, and a negative row in the\n // outstanding-liability total. The increment was atomic the whole time;\n // nothing ever CHECKED.\n //\n // Unlike stock (AGL-2320), this door can still refuse: the card is applied\n // BEFORE Stripe is contacted, so no money has moved yet and a shortfall is\n // a 400 rather than a shipped parcel and an apology. That is why this\n // reserves instead of merely reporting.\n //\n // `giftCardCents` stays in the session metadata because the webhook still\n // needs to know what to settle, but it is no longer the AUTHORITY for the\n // decrement — the hold is, and it is keyed by the session id below.\n // Set only once a hold actually stands, so the refusals below can call it\n // unconditionally without knowing whether there was a gift card at all.\n let releaseGiftCardHold: () => Promise<void> = async () => undefined\n if (giftCardCode) {\n const cardRef = hostRef.collection('giftCards').doc(giftCardCode)\n // Placed before the Stripe session exists, so it is keyed by the\n // idempotency claim's derived key rather than by a session id we do not\n // have yet — the same string the session is minted under below, so a\n // retry of this attempt re-places the SAME hold rather than a second one.\n const holdKey = claim.stripeKey\n const nowMs = Date.now()\n const held = await firestore\n .runTransaction(async (transaction: any) => {\n const fresh = await transaction.get(cardRef)\n if (!fresh.exists) return null\n const card = (fresh.data() ?? {}) as CommerceModel.HostGiftCard\n const holds = CommerceModel.pruneGiftCardHolds(card.holds, nowMs)\n // This attempt's own prior hold does not stand in its way — a retry\n // must be able to re-claim what it already reserved, or the second\n // press of the same button refuses the shopper their own money.\n const mine = Number(holds[holdKey]?.cents ?? 0)\n delete holds[holdKey]\n const available = CommerceModel.giftCardAvailableCents(\n { ...card, holds },\n nowMs,\n )\n if (available <= 0 && mine <= 0) return null\n // Against what is LEFT after the discount, not against the whole\n // basket (AGL-2112). The old cap was `min(balance, itemsCents)`, so a\n // $50 card on a $100 basket already 30% off consumed $50 of card\n // against $70 of goods — the webhook decrements by exactly this\n // number, so the extra was burned off the customer's card for nothing.\n const applyCents = Math.min(\n available + mine,\n Math.max(0, itemsCents - totalOffCents),\n )\n if (applyCents <= 0) return 0\n // Lapsed holds are deleted by SENTINEL, not by writing back a pruned\n // copy of the map: `set(…, { merge: true })` merges nested maps\n // rather than replacing them, so a locally-pruned object leaves every\n // stale key exactly where it was. Correctness does not depend on this\n // — every read prunes again — but the document would otherwise grow\n // without bound, one dead key per abandoned checkout, forever.\n const swept: Record<string, unknown> = {}\n for (const stale of Object.keys(card.holds ?? {})) {\n if (stale !== holdKey && !holds[stale]) {\n swept[stale] = firebaseAdmin.firestore.FieldValue.delete()\n }\n }\n transaction.set(\n cardRef,\n {\n holds: {\n ...swept,\n [holdKey]: {\n cents: applyCents,\n expiresAtMs: nowMs + CommerceModel.GIFT_CARD_HOLD_TTL_MS,\n },\n },\n },\n { merge: true },\n )\n return applyCents\n })\n .catch((error: unknown) => {\n console.error('Gift card hold failed', giftCardCode, error)\n return null\n })\n if (held == null) {\n // Deterministic and retryable; nothing has been minted yet. Covers the\n // unknown card, the empty card, and the card whose whole balance is\n // already held by someone else's live checkout — the shopper is told\n // the same thing in each case, because \"someone is spending it right\n // now\" is not a distinction a storefront should draw.\n await claim.release()\n return res.status(400).json({ error: 'Gift card is empty or invalid' })\n }\n if (held > 0) {\n totalOffCents += held\n params.set('metadata[giftCardCode]', giftCardCode)\n params.set('metadata[giftCardCents]', String(held))\n params.set('metadata[giftCardHoldKey]', holdKey)\n // Every refusal BELOW this point releases the claim so the shopper can\n // retry; the hold has to come back with it or the retry finds the card\n // empty and the shopper is locked out of their own balance until the\n // TTL lapses. The expiry is the backstop for a crash, not the\n // mechanism for a refusal the code can see happening.\n releaseGiftCardHold = async () => {\n await cardRef\n .set(\n {\n holds: {\n [holdKey]: firebaseAdmin.firestore.FieldValue.delete(),\n },\n },\n { merge: true },\n )\n .catch((error: unknown) => {\n // Best effort: the TTL still releases it. Never let tidying up a\n // hold turn a clean 409 into a 500.\n console.error('Gift card hold release failed', giftCardCode, error)\n })\n }\n }\n }\n\n // STORE CREDIT ANOTHER PLUGIN KEEPS (AGL-3640) — a rewards balance, a\n // friend's referral credit — by its code, through core's checkout-credits\n // seam; this plugin never reads the account. Held exactly like the gift\n // card above: against what is LEFT after every other reduction, BEFORE\n // Stripe is contacted, keyed by this attempt so a retry re-places the same\n // hold. The webhook takes it in the transaction that writes the order, and\n // a session that expires lets it go.\n let releaseCreditHold: () => Promise<void> = async () => undefined\n let creditHeldCents = 0\n if (creditCode) {\n const refuseCredit = async (status: number, error: string) => {\n await releaseGiftCardHold()\n await releasePromotionHolds()\n await releaseStock()\n await claim?.release()\n return res.status(status).json({ error, creditCodeInvalid: true })\n }\n const entry = checkoutCreditProviderForCode(creditCode)\n const offered = entry\n ? await entry.provider.offered({ hostId, channel: 'online' }).catch(() => false)\n : false\n if (!entry || !offered) return refuseCredit(400, CHECKOUT_CREDIT_INVALID_MESSAGE)\n const account = normalizeCheckoutCreditAccount(\n await entry.provider\n .resolve({ hostId, code: creditCode, channel: 'online', customerEmail: email || null, staff: false })\n .catch((error: unknown) => {\n console.error('Checkout credit resolve failed', entry.providerId, error)\n return null\n }),\n )\n if ('error' in account) return refuseCredit(account.status, account.error)\n const maxCents = Math.max(0, itemsCents - totalOffCents)\n // The attempt's key, so a retry re-places the same hold; a request\n // that sent none (an older client) still needs a key of its own.\n const holdKey = claim.stripeKey || `cart:${cartId}:${randomUUID()}`\n const held = await entry.provider\n .hold({\n hostId,\n reference: account.reference,\n holdKey,\n maxCents,\n currency: 'usd',\n customerEmail: email || null,\n nowMs: Date.now(),\n })\n .catch((error: unknown) => {\n console.error('Checkout credit hold failed', entry.providerId, error)\n return { ok: false as const, status: 409, error: 'That rewards code cannot be used right now. Try again.' }\n })\n if ('error' in held) {\n return refuseCredit(held.status >= 400 && held.status < 500 ? held.status : 409, held.error)\n }\n const release = async () => {\n await entry.provider.release({ hostId, reference: account.reference, holdKey })\n }\n creditHeldCents = boundedCreditCents((held as { cents: number }).cents, maxCents)\n if (creditHeldCents > 0) {\n totalOffCents += creditHeldCents\n for (const [key, value] of Object.entries(\n encodeCheckoutCreditMetadata({\n providerId: entry.providerId,\n reference: account.reference,\n holdKey,\n amountCents: creditHeldCents,\n label: account.label,\n last4: account.last4,\n }),\n )) {\n params.set(`metadata[${key}]`, value)\n }\n releaseCreditHold = release\n } else {\n await release()\n }\n }\n\n if (totalOffCents > 0) {\n const stripeCoupon = await fetch('https://api.stripe.com/v1/coupons', {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,\n 'Content-Type': 'application/x-www-form-urlencoded',\n // ONE derived object key now, because there is one coupon. The\n // three old keys existed only because there were three mints. A\n // store-credit share names itself (AGL-3640): a balance that moved\n // between two tries is a different coupon, never a mismatch.\n ...stripeKeyHeader(creditHeldCents > 0 ? `coupon-c${creditHeldCents}` : 'coupon'),\n },\n body: new URLSearchParams({\n amount_off: String(Math.min(totalOffCents, itemsCents)),\n currency: 'usd',\n duration: 'once',\n }).toString(),\n }).then((response) => response.json())\n if (stripeCoupon?.id) {\n params.set('discounts[0][coupon]', stripeCoupon.id)\n // Scaled ONCE, by the reduction that actually reached the session.\n feeCents = Math.round(\n (feeCents * Math.max(0, itemsCents - totalOffCents)) /\n Math.max(1, itemsCents),\n )\n chargedItemsCents = Math.max(0, itemsCents - totalOffCents)\n } else if (creditHeldCents > 0) {\n // A rewards balance that did not reach the price must not be taken\n // from the member when the session is paid (AGL-3640): refuse, and\n // hand back everything this attempt held, rather than sell at full\n // price with their credit spent.\n console.error('Stripe coupon error', stripeCoupon?.error)\n await releaseGiftCardHold()\n await releaseCreditHold()\n await releasePromotionHolds()\n await releaseStock()\n await claim.release()\n return res.status(502).json({ error: 'Checkout failed' })\n }\n }\n // THE FLOOR (AGL-2232). A rate above zero on goods the shopper actually\n // pays for is a fee, however small the arithmetic makes it — the same rule\n // the other three doors already hold. Gated on `chargedItemsCents` so a\n // basket a gift card covered entirely still takes nothing: the fee is a cut\n // of the goods, and there are no goods left to cut.\n if (feeApplies && chargedItemsCents > 0 && feeCents < 1) feeCents = 1\n // STRIPE'S CARD COST, PASSED THROUGH AT COST (AGL-2152). The loop above\n // accumulates the platform's advertised TAKE per line and nothing else; on\n // a destination charge Stripe debits 2.9% + 30¢ of the whole card total\n // from the PLATFORM's balance, so a cart on any tier carrying a 0% rate\n // cost Aglyn money on every single order — and Starter's 2% is below 2.9%,\n // so it lost money too. Added ONCE per charge, not per line: the 30¢ is a\n // per-charge cost.\n //\n // The base is everything the card will be run for that can be seen from\n // here: the goods the shopper actually pays for, the DEAREST shipping\n // option on the session (the shopper picks after this and\n // `application_fee_amount` is fixed at creation), and the manual origin tax\n // rate the lines carry. A store on Stripe Tax is the one residual — its tax\n // is computed inside Stripe after the session is made.\n // The merchant's own tax service, where they connected one (AGL-3631).\n // Asked once, here, because the card-cost estimate just below and the\n // tax lines further down both need its answer. Each line goes over at\n // what Stripe will tax it at — its amount less its share of the session\n // coupon, shared the way Stripe shares it — so the rate derived from\n // the answer reproduces it. Taxed at the store's address, like the store's\n // own rate: the shopper's address arrives inside Stripe Checkout, after\n // this session exists. When the service does not answer, the store's\n // rate stands and the order says so.\n const cartEngineLines = cart.lines\n .map((line, index) => {\n const unitCents = Number(params.get(`line_items[${index}][price_data][unit_amount]`) ?? NaN)\n return {\n index,\n line,\n amountCents: Number.isFinite(unitCents) ? unitCents * line.quantity : 0,\n }\n })\n .filter((entry) => entry.amountCents > 0)\n const cartEngineDiscounts = allocateCentsByWeight(\n Math.max(0, itemsCents - chargedItemsCents),\n cartEngineLines.map((entry) => entry.amountCents),\n )\n const cartEngineNet = cartEngineLines.map(\n (entry, position) => entry.amountCents - cartEngineDiscounts[position],\n )\n const engineTax =\n chargedItemsCents > 0\n ? await quoteSaleTaxWithEngine({\n hostId,\n settings: (storeSettings.get('tax') ?? {}) as CommerceModel.TaxSettings,\n channel: 'online',\n lines: cartEngineLines.map((entry, position) => ({\n id: String(entry.index),\n productId: entry.line.productId,\n ...(entry.line.variantId ? { variantId: entry.line.variantId } : {}),\n quantity: entry.line.quantity,\n amountCents: cartEngineNet[position],\n ...(productsById.get(entry.line.productId)?.taxExempt ? { exempt: true } : {}),\n })),\n customerEmail: email || null,\n })\n : { quote: null, stamp: null }\n if (chargedItemsCents > 0) {\n const shippingCeilingCents = shippingOptions.reduce(\n (most, option) =>\n Math.max(most, Math.max(0, Number(option.amountCents ?? 0))),\n 0,\n )\n const cartTaxSettings = (storeSettings.get('tax') ??\n {}) as CommerceModel.TaxSettings\n const manualRate =\n cartTaxSettings.mode === 'manual' && !cartTaxSettings.pricesIncludeTax\n ? CommerceModel.resolveTaxRate(\n cartTaxSettings,\n cartTaxSettings.origin ?? {},\n )\n : null\n const manualTaxPct =\n manualRate && manualRate.pct > 0 ? manualRate.pct : 0\n const chargeCents =\n chargedItemsCents +\n // Stripe's cost on the extras is a real cost of this charge, passed\n // through at cost like the rest; no take is added on them (AGL-3635).\n extrasCents +\n (engineTax.quote\n ? engineTax.quote.taxCents\n : Math.round((chargedItemsCents * manualTaxPct) / 100)) +\n shippingCeilingCents\n feeCents = Math.min(\n chargeCents,\n feeCents + Aglyn.saleProcessingCostCents(chargeCents),\n )\n }\n // THE CONNECT SPLIT IS EMITTED BELOW, NOT HERE (AGL-1956). It used to be\n // written at this point, which is ABOVE the tax decision — and the shape\n // of the split now depends on that decision: a Stripe Tax sale must send a\n // fixed `transfer_data[amount]` instead of `application_fee_amount`,\n // because the fee form transfers `amount_total − fee` and `amount_total`\n // carries tax that AGLYN owes. `feeCents` is final as of this line; only\n // its emission moved. See `commerce-connect-transfer.ts`.\n //\n // Address collection feeds order shipping + destination tax later, and is\n // the enforcing half of the plan above (AGL-1721) — emitted from the\n // plan's own country list, never a default, so it cannot widen past the\n // destinations the declared rates were resolved for. The emission is\n // shared with buy-now (AGL-1720).\n CommerceModel.appendShippingAddressCollectionParams(\n params,\n shippingPlan.countries,\n )\n CommerceModel.appendCheckoutShippingParams(params, shippingOptions)\n\n // Taxes (AGL-1953). This block used to be the `stripe` line alone, and a\n // `manual`-mode store — the mode the AGL-285 zone editor leaves a merchant\n // in by default — collected tax on a buy-now sale and NOTHING on a cart\n // sale of the same goods. Same store, same shopper, same items, two totals\n // depending on which button was pressed, with the merchant left owing the\n // destination tax they never charged.\n //\n // The manual tax rides a real Stripe Tax Rate on each taxable line rather\n // than buy-now's `line_items[1]` product line (AGL-1711). That is not a\n // gratuitous difference: THIS path applies its discounts, coupons and gift\n // cards as session-level Stripe coupons, which spread across every line,\n // so a fake tax line would be discounted along with the goods. Stripe\n // applies a tax rate AFTER the discount — measured, see\n // `manual-tax-rate.ts` — so the arithmetic stays right and\n // `total_details.amount_tax` becomes real, which is what lets the webhook\n // record the tax with no metadata snapshot to keep in step.\n //\n // `automatic_tax.enabled` stays false on a manual sale, so\n // `storefront-tax.ts` still classifies it `manual` and it is never summed\n // into the Aglyn-liable figure (AGL-1904).\n const taxSettings = (storeSettings.get('tax') ?? {}) as CommerceModel.TaxSettings\n // AGL-1999: an unset tax mode is NOT a decision, and it must not sell.\n // `mode` was `undefined` for every store whose owner never opened the\n // Taxes card — the default state of every new storefront — and each\n // branch below tested for a specific string, so no branch was taken, no\n // tax was computed, and the shopper was charged an untaxed total. See\n // `commerce-tax-decision.ts` for why refusing beats defaulting to either\n // mode. `mode: 'none'` is the explicit opt-out and passes straight\n // through.\n const taxDecision = CommerceModel.storefrontTaxDecision({\n settings: taxSettings,\n })\n if (taxDecision.kind === 'undecided') {\n // BELOW the claim on this path, so release it: the merchant fixes the\n // setting and the shopper retries under the same key (AGL-1697).\n await releaseGiftCardHold()\n await releaseCreditHold()\n await releasePromotionHolds()\n await releaseStock()\n await claim.release()\n return res\n .status(409)\n .json({ error: CommerceModel.STOREFRONT_TAX_UNDECIDED_MESSAGE })\n }\n const taxMisconfigured =\n CommerceModel.storefrontTaxMisconfiguration(taxSettings)\n if (taxMisconfigured) {\n // AGL-2145: the merchant DECIDED to collect and the sale would have\n // charged zero anyway — an unfinished settings card, not a\n // jurisdiction miss. Refused in the same words and the same place as\n // the undecided case above.\n // Below the claim here too, so release it for the same reason.\n await releaseGiftCardHold()\n await releaseCreditHold()\n await releasePromotionHolds()\n await releaseStock()\n await claim.release()\n return res.status(409).json({ error: taxMisconfigured })\n }\n if (taxDecision.kind === 'stripe-automatic') {\n params.set('automatic_tax[enabled]', 'true')\n } else if (engineTax.quote) {\n // The tax service's cents per line as one rate per line (AGL-3631),\n // applied after the coupon exactly like the store's own rate below. One\n // Tax Rate per distinct percentage, cached under the host like the\n // store's, so a store's usual rates are minted once.\n const engineAnswer = new Map(engineTax.quote.lines.map((line) => [line.id, line.taxCents]))\n const percentages = engineLineTaxPercentages(\n cartEngineLines.map((entry, position) => ({\n amountCents: cartEngineNet[position],\n taxCents: engineAnswer.get(String(entry.index)) ?? 0,\n })),\n 0,\n )\n for (let position = 0; position < cartEngineLines.length; position++) {\n const pct = percentages[position]\n if (pct === null) continue\n const taxRateId = await resolveManualTaxRateId({\n hostRef,\n taxPct: pct,\n taxLabel: 'Sales tax',\n headers: stripeKeyHeader(`tax-rate-engine-${Math.round(pct * 10_000)}`),\n })\n if (!taxRateId) {\n // The same visible refusal as the store's own rate: never an\n // untaxed session.\n await releaseGiftCardHold()\n await releaseCreditHold()\n await releasePromotionHolds()\n await releaseStock()\n await claim.release()\n return res.status(502).json({ error: 'Checkout failed' })\n }\n params.set(`line_items[${cartEngineLines[position].index}][tax_rates][0]`, taxRateId)\n }\n } else if (taxSettings.mode === 'manual' && !taxSettings.pricesIncludeTax) {\n // Origin-based, exactly as buy-now resolves it: the cart collects the\n // shopper's address inside Stripe Checkout, so there is no destination\n // to resolve against before the session exists. Destination-based manual\n // tax is AGL-296's checkout, and taxing by origin here is what makes the\n // two paths agree — which is the whole point of this issue.\n //\n // `pricesIncludeTax` stores charge no separate tax on ANY path (buy-now\n // skips it the same way); the tax is already inside the displayed price.\n const rate = CommerceModel.resolveTaxRate(\n taxSettings,\n taxSettings.origin ?? {},\n )\n // Tax-exempt products are excluded per LINE, which a single session-wide\n // figure could not express — a cart mixing a taxable chair with an exempt\n // download must tax only the chair.\n const taxableIndexes = cart.lines\n .map((line, index) => ({ line, index }))\n .filter(({ line }) => !productsById.get(line.productId)?.taxExempt)\n .map(({ index }) => index)\n if (rate && rate.pct > 0 && taxableIndexes.length > 0) {\n const taxRateId = await resolveManualTaxRateId({\n hostRef,\n taxPct: rate.pct,\n taxLabel: rate.label || `Tax (${rate.pct}%)`,\n headers: stripeKeyHeader('tax-rate'),\n })\n if (!taxRateId) {\n // A VISIBLE refusal, never a fallback to an untaxed session — that\n // silent under-collection is this issue. Nothing was sold, so the\n // key goes back and the same button works once Stripe does\n // (AGL-1697); the retry re-derives the same digest, so Stripe\n // replays any rate the first run did mint.\n await releaseGiftCardHold()\n await releaseCreditHold()\n await releasePromotionHolds()\n await releaseStock()\n await claim.release()\n return res.status(502).json({ error: 'Checkout failed' })\n }\n for (const index of taxableIndexes) {\n params.set(`line_items[${index}][tax_rates][0]`, taxRateId)\n }\n }\n }\n\n // The site's own page, never a header's say-so (AGL-3363): a caller\n // writes `Referer` and `Host`, and this URL is where Stripe sends the\n // payer — and what the receipt's download links are built from.\n const returnSite = await readSiteReturnHost(hostRef)\n const backUrl = siteReturnUrl({\n candidates: [String(req.headers.referer ?? ''), `https://${req.headers.host}`],\n site: returnSite,\n requestHost: String(req.headers.host ?? ''),\n })\n const separator = backUrl.includes('?') ? '&' : '?'\n // `{CHECKOUT_SESSION_ID}` is substituted by Stripe on redirect (AGL-1641).\n // Without it the return URL said only that SOMETHING succeeded, so the\n // storefront could not name the order it had just completed — which is why\n // `purchase` could not be reported at all. It is also the order doc id and\n // becomes the GA `transaction_id`, so GA4's own de-duplication makes a\n // refresh of this page harmless.\n params.set(\n 'success_url',\n `${backUrl}${separator}order=success&session_id={CHECKOUT_SESSION_ID}`,\n )\n params.set('cancel_url', `${backUrl}${separator}order=canceled`)\n if (email) params.set('customer_email', email)\n // THE CONNECT SPLIT (AGL-1956), emitted here because it needs the tax\n // decision taken above. On a Stripe Tax cart the tax is computed against\n // AGLYN's registrations and is Aglyn's to remit (AGL-1904), so the\n // merchant is paid a FIXED `transfer_data[amount]` and no\n // `application_fee_amount` is sent at all — the fee form would have Stripe\n // transfer `amount_total − fee`, handing the state's money to the\n // merchant. A manual-rate or untaxed cart keeps the fee form byte for\n // byte. See `commerce-connect-transfer.ts` for why only one of the two\n // knobs may ever be present.\n const cartTaxOwner =\n taxDecision.kind === 'stripe-automatic' ? 'platform' : 'merchant'\n const cartShippingFloorCents = CommerceModel.shippingFloorCents(\n shippingOptions,\n )\n Object.entries(\n CommerceModel.destinationChargeParams({\n accountId: String(accountId),\n feeCents,\n taxOwner: cartTaxOwner,\n // The extras are the merchant's to keep and remit (AGL-3635).\n merchantGoodsCents: chargedItemsCents + extrasCents,\n shippingFloorCents: cartShippingFloorCents,\n }),\n ).forEach(([key, value]) => params.set(key, value))\n params.set('metadata[type]', 'commerce-cart')\n // How the order reaches the buyer (AGL-3624): the webhook routes it.\n if (localPlan.kind === 'pickup' || localPlan.kind === 'local_delivery') {\n appendLocalFulfillmentMetadata(params, localPlan.metadata)\n }\n for (const [key, value] of Object.entries(taxEngineSessionMetadata(engineTax.stamp))) {\n params.set(key, value)\n }\n // The postal code live carrier rates were quoted for (AGL-3612), so the\n // order can show it beside the address the shopper then entered.\n if (shippingPlan.quotedPostalCode) {\n params.set('metadata[shippingQuotePostalCode]', shippingPlan.quotedPostalCode)\n }\n params.set('metadata[hostId]', hostId)\n params.set('metadata[cartId]', cartId)\n params.set('metadata[feeCents]', String(Math.max(0, feeCents)))\n // Only on the shape where `transfer.amount` is no longer `charge.amount`,\n // so the merchant's actual payout stops being derivable from the charge\n // (AGL-1956). The shipping baseline is the other half: the difference\n // between it and the option the shopper chose is owed back to the merchant.\n if (cartTaxOwner === 'platform') {\n params.set(\n 'metadata[transferCents]',\n String(\n CommerceModel.platformLiableTransferCents({\n feeCents,\n merchantGoodsCents: chargedItemsCents + extrasCents,\n shippingFloorCents: cartShippingFloorCents,\n }),\n ),\n )\n params.set(\n 'metadata[transferShippingCents]',\n String(cartShippingFloorCents),\n )\n }\n // Set only when units were actually reserved (AGL-2356). Its ABSENCE is\n // what tells the webhook there is nothing to release — right for a cart of\n // untracked or backorder lines, and right for a session minted before this\n // deploy, neither of which reserved anything.\n if (heldStockKey) params.set('metadata[stockHoldKey]', heldStockKey)\n // A HALF-HOUR SESSION, AND IT IS THE HOLD'S WHOLE SAFETY ARGUMENT\n // (AGL-2356). Stripe's default is 24 hours, and a hold must outlive any\n // session that can still be paid — so the default would mean one abandoned\n // cart standing on a merchant's last unit for a day, which is exactly the\n // unbounded, invisible UNDER-sell this issue was held back for. Shortening\n // the SESSION is what bounds it; see `CHECKOUT_SESSION_TTL_MS` for why the\n // figure is 31 minutes and not Stripe's advertised 30-minute floor.\n params.set(\n 'expires_at',\n String(\n Math.floor((Date.now() + CommerceModel.CHECKOUT_SESSION_TTL_MS) / 1000),\n ),\n )\n\n // The payment methods the merchant switched off (AGL-3629): exclusions\n // only, on the hosted page and the in-page one alike.\n const paymentMethodControls =\n CommerceModel.resolveStorefrontPaymentMethodControls(\n CommerceModel.normalizeStorefrontPaymentMethodSettings(\n storeSettings.get('paymentMethods'),\n ),\n )\n CommerceModel.appendStorefrontPaymentMethodParams(\n params,\n paymentMethodControls,\n )\n\n // The Payment Element (AGL-1944), LAST and touching nothing above it. Every\n // figure the shopper is charged — line prices, the discount coupon, the tax\n // construction, the shipping rates, the fee — was decided before this line\n // and is identical on both checkout paths. This swaps the redirect's URL\n // pair for `ui_mode` + `return_url` and stops. Off unless the flag is on for\n // this org AND a publishable key exists; see `resolveNativeCheckoutMode`.\n const nativeMode = await resolveNativeCheckoutMode(\n String(ownerOrg?.org?.id ?? ''),\n )\n if (nativeMode.native) {\n applyNativeCheckoutParams(\n params,\n `${backUrl}${separator}order=success&session_id={CHECKOUT_SESSION_ID}`,\n )\n }\n // Apple Pay shows on a registered domain only (AGL-3629): registered\n // beside the session create, inside a budget, never failing the sale.\n const domainRegistration = nativeMode.native\n ? ensureCheckoutDomain({ pageUrl: backUrl, site: returnSite, hostId }).catch(\n () => null,\n )\n : Promise.resolve(null)\n const response = await fetch(\n 'https://api.stripe.com/v1/checkout/sessions',\n {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,\n 'Content-Type': 'application/x-www-form-urlencoded',\n // The half that costs real money (AGL-1697): Stripe replays the\n // existing session for a repeated key instead of opening a second\n // one, covering the window where the claim is written but the\n // response never arrives.\n // A tax service's answer can differ between two tries under one\n // key, so the key names it and a changed answer opens a new session\n // rather than a Stripe parameter mismatch (AGL-3631).\n // Likewise an extra's price (AGL-3635): a re-quote that moved opens\n // a new session instead of a parameter mismatch.\n ...stripeKeyHeader(\n `${\n engineTax.stamp\n ? `session-tax-${engineTax.quote?.taxCents ?? 'own'}-${engineTax.stamp.status}`\n : 'session'\n }${extrasCents > 0 ? `-x${extrasCents}` : ''}${creditHeldCents > 0 ? `-c${creditHeldCents}` : ''}`,\n ),\n // Empty on the hosted path (AGL-1944).\n ...nativeCheckoutStripeHeaders(nativeMode),\n },\n body: params.toString(),\n },\n )\n const session = (await response.json()) as {\n url?: string\n id?: string\n client_secret?: string\n error?: any\n }\n // `!session.url` was the liveness check and a `ui_mode` session has no url,\n // so it moves with the mode (AGL-1944) — otherwise every successful native\n // session reads as a Stripe failure: a 502 at the shopper, a released claim,\n // and a real Checkout Session left open on the merchant's account.\n await domainRegistration\n const payload = response.ok\n ? readCheckoutSessionPayload(\n session,\n nativeMode,\n paymentMethodControls.wallets,\n )\n : null\n if (!payload) {\n console.error('Stripe cart checkout error', session.error)\n // The checkout did not happen — hand the key back so one flaky moment\n // does not lock this cart out. The retry re-derives the same derived\n // keys, so Stripe replays whatever objects the first run did create.\n await releaseGiftCardHold()\n await releaseCreditHold()\n await releasePromotionHolds()\n await releaseStock()\n await claim.release()\n return res.status(502).json({ error: 'Checkout failed' })\n }\n // THE SESSION NOW EXISTS, SO THE SLOTS BELONG TO IT (AGL-2453). Past this\n // line a hold must survive: the shopper can still pay, and the webhook\n // settles against the reservation. A catch that released one here would\n // leave a paid order whose redemption is never counted — the same\n // over-redemption this fix closes, arriving through the release path.\n heldPromotions.length = 0\n // And so do the units (AGL-2356), for the stronger version of the same\n // reason: releasing here would hand the last unit to the next shopper while\n // this one still holds a payable session, which is the oversell itself.\n stockHold = null\n // Recoverable checkout (AGL-296): email captured before the redirect\n // makes abandonment actionable (AGL-323 sends the recovery emails). Written\n // for the native path too, and it matters MORE there: a shopper who\n // abandons an in-page form never leaves the store, so this row is the only\n // trace that a cart reached checkout at all.\n await hostRef\n .collection('checkouts')\n .doc(String(session.id))\n .set({\n cartId,\n ...(email ? { email } : {}),\n ...(marketingOptIn ? { marketingOptIn: true } : {}),\n itemsCents,\n resumeUrl: backUrl,\n status: 'open',\n // Explicit, so the recovery queue can COUNT it (AGL-3321): a query\n // cannot ask for a checkout that lacks an email or a reminder.\n recoveryState: checkoutRecoveryState({ email }),\n createdAtMs: Date.now(),\n })\n .catch(() => undefined)\n // The fact a merchant's own email platform starts an abandoned-cart flow\n // from (AGL-3639); nothing subscribes unless a site connected one.\n if (email) {\n await raiseCheckoutStarted(String(hostId), {\n id: String(session.id),\n email,\n marketingOptIn,\n currency: 'usd',\n itemsCents,\n resumeUrl: backUrl ?? null,\n items: startedItems,\n })\n }\n // The shopper's advertising consent, for the order this session may\n // become (AGL-3694) — see `checkout.ts`. Never fails the checkout.\n await recordAdvertisingOrderConsent({\n hostId: String(hostId),\n orderKey: String(session.id ?? ''),\n wire: body.adConsent,\n ...advertisingRequestFacts(req.headers as never),\n })\n await claim.record(200, payload)\n return res.status(200).json(payload)\n } catch (error: any) {\n if (error?.visible) {\n // Line-availability refusals throw from the pricing loop, which sits\n // ABOVE the claim — so there is nothing to release and the key\n // survives, as a deterministic refusal should (AGL-1697).\n await releasePromotionHolds()\n await releaseStock()\n await claim?.release()\n return res.status(409).json({ error: error.visible })\n }\n console.error(error)\n // Release on the way out so a transient failure does not strand the key\n // (AGL-1691's rule) — nor a merchant's promotion slot (AGL-2453).\n await releasePromotionHolds()\n await releaseStock()\n await claim?.release()\n return res.status(500).json({ error: 'Checkout failed' })\n }\n}\n"],"names":["randomUUID","readSiteReturnHost","siteReturnUrl","advertisingRequestFacts","recordAdvertisingOrderConsent","Aglyn","CommerceModel","checkoutRecoveryState","claimAttempt","deriveStripeObjectKey","consumeRateLimit","firebaseAdmin","getOrgForHost","isYoungWorkspace","merchantAccountIsReady","checkoutSessionCardAuthenticationParams","CARRIER_POSTAL_CODE_MESSAGE","planCheckoutShippingWithCarriers","readCartId","resolveManualTaxRateId","allocateCentsByWeight","engineLineTaxPercentages","taxEngineSessionMetadata","quoteSaleTaxWithEngine","holdPromotionSlot","promotionHoldKey","holdStock","stockHoldKey","appendLocalFulfillmentMetadata","localFulfillmentLocationId","localStockRefusalMessage","planLocalFulfillment","readLocalFulfillmentRequest","readLocalFulfillmentStore","applyNativeCheckoutParams","nativeCheckoutStripeHeaders","readCheckoutSessionPayload","resolveNativeCheckoutMode","ensureCheckoutDomain","raiseCheckoutStarted","checkoutExtrasCents","encodeCheckoutExtrasMetadata","readChosenCheckoutExtras","quoteCartExtras","boundedCreditCents","checkoutCreditProviderForCode","encodeCheckoutCreditMetadata","normalizeCheckoutCreditAccount","normalizeCheckoutCreditCode","CHECKOUT_CREDIT_INVALID_MESSAGE","CHECKOUT_EXTRA_UNAVAILABLE_MESSAGE","cartCheckoutHandler","req","res","body","method","status","json","error","process","env","STRIPE_SECRET_KEY","JSON","parse","hostId","String","email","trim","toLowerCase","slice","marketingOptIn","Boolean","giftCardCode","toUpperCase","couponCode","creditCode","cartId","cookies","idempotencyKey","headers","claim","heldPromotions","releasePromotionHolds","hold","splice","release","stockHold","heldStockKey","releaseStock","held","cartSnapshot","storeSettings","session","ownerOrg","engineTax","firestore","app","hostRef","collection","doc","get","cart","data","lines","length","ownerId","org","ownerUid","checkEntitlement","ownerProfile","accountId","chargesEnabled","accountLivemode","subject","productSnapshots","Promise","all","Set","map","line","productId","id","productsById","Map","snapshot","exists","liftLegacyProduct","itemsCents","discountLines","giftCardCents","feeCents","feeApplies","chargedItemsCents","totalGrams","hasPhysicalLine","reserveLines","startedItems","extrasLines","params","URLSearchParams","mode","forEach","index","product","variant","deletedAt","Object","assign","Error","visible","name","variantId","variants","find","item","canPurchase","quantity","variantHasPrice","giftCard","stockIsReservable","Date","now","push","unitCents","Math","round","Number","priceUsd","sku","physical","type","amountCents","max","weightGrams","feePct","resolveTransactionFeePct","set","keys","options","values","join","young","overCeiling","giftCardPurchaseRefusal","buys","socket","remoteAddress","limit","GIFT_CARD_PURCHASE_LIMITS","perVisitorDailyYoung","perVisitorDaily","windowMs","allowed","localRequest","fulfillment","localStore","localPlan","request","store","kind","changed","fulfillmentChanged","shippingPlan","countries","country","option","settings","subtotalCents","destination","shippingCountry","postalCode","shippingPostalCode","CHECKOUT_SHIPPING_COUNTRIES","pickupLocations","filter","rateId","LOCAL_PICKUP_RATE_ID","needsPostalCode","needsShippingCountry","needsShippingPostalCode","shippingCountries","refusal","declared","normalizeCheckoutShippingCountry","CHECKOUT_SHIPPING_COUNTRY_NAMES","CART_UNPRICEABLE_SHIPPING_MESSAGE","chosenExtras","extras","chargedExtras","extra","includes","extrasChanged","extrasCents","offset","label","key","value","entries","discountsSnapshot","resolvedDiscount","resolveDiscount","docs","docSnapshot","$id","code","productIds","codeProblem","claimed","scopeId","orgId","busyMessage","replay","stripeKeyHeader","object","derived","stripeKey","holdKey","locationId","ok","reason","productName","STOCK_HELD_MESSAGE","slotKey","totalOffCents","benefit","discountRef","discountId","slot","ref","discount","PROMOTION_EXHAUSTED_MESSAGE","PROMOTION_UNAVAILABLE_MESSAGE","discountCents","shippingOptions","freeShipping","couponRef","couponSnapshot","coupon","percentOff","expired","expiresAtMs","enabled","releaseGiftCardHold","undefined","cardRef","nowMs","runTransaction","transaction","fresh","card","holds","pruneGiftCardHolds","mine","cents","available","giftCardAvailableCents","applyCents","min","swept","stale","FieldValue","delete","GIFT_CARD_HOLD_TTL_MS","merge","catch","console","releaseCreditHold","creditHeldCents","refuseCredit","creditCodeInvalid","entry","offered","provider","channel","account","resolve","customerEmail","staff","providerId","maxCents","reference","currency","last4","stripeCoupon","fetch","Authorization","amount_off","duration","toString","then","response","cartEngineLines","NaN","isFinite","cartEngineDiscounts","cartEngineNet","position","taxExempt","exempt","quote","stamp","cartTaxSettings","shippingCeilingCents","reduce","most","manualRate","pricesIncludeTax","resolveTaxRate","origin","manualTaxPct","pct","chargeCents","taxCents","saleProcessingCostCents","appendShippingAddressCollectionParams","appendCheckoutShippingParams","taxSettings","taxDecision","storefrontTaxDecision","STOREFRONT_TAX_UNDECIDED_MESSAGE","taxMisconfigured","storefrontTaxMisconfiguration","engineAnswer","percentages","taxRateId","taxPct","taxLabel","rate","taxableIndexes","returnSite","backUrl","candidates","referer","host","site","requestHost","separator","cartTaxOwner","cartShippingFloorCents","shippingFloorCents","destinationChargeParams","taxOwner","merchantGoodsCents","metadata","quotedPostalCode","platformLiableTransferCents","floor","CHECKOUT_SESSION_TTL_MS","paymentMethodControls","resolveStorefrontPaymentMethodControls","normalizeStorefrontPaymentMethodSettings","appendStorefrontPaymentMethodParams","nativeMode","native","domainRegistration","pageUrl","payload","wallets","resumeUrl","recoveryState","createdAtMs","items","orderKey","wire","adConsent","record"],"mappings":"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"}
@@ -28,6 +28,12 @@ export interface PublicCatalogItem {
28
28
  imageUrl?: string;
29
29
  soldOut: boolean;
30
30
  tags?: string[];
31
+ /**
32
+ * Listed before any variant has a price (AGL-3676): the card says "Price
33
+ * coming soon" instead of a price, and nothing sells it until one is set.
34
+ * Absent for every product with a price.
35
+ */
36
+ priceComingSoon?: true;
31
37
  }
32
38
  /** Host category surfaced to grids for filter chips (AGL-561). */
33
39
  export interface PublicCatalogCategory {