@aglyn/plugins-commerce 1.0.0-beta.232 → 1.0.0-beta.233
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +13 -13
- package/src/lib/components/cart-extras.d.ts +6 -0
- package/src/lib/components/cart-extras.js +4 -1
- package/src/lib/components/cart-extras.js.map +1 -1
- package/src/lib/components/cart-fulfillment.d.ts +117 -0
- package/src/lib/components/cart-fulfillment.js +367 -0
- package/src/lib/components/cart-fulfillment.js.map +1 -0
- package/src/lib/components/cart.js +52 -10
- package/src/lib/components/cart.js.map +1 -1
- package/src/lib/components/commerce-console-page.js +42 -1
- package/src/lib/components/commerce-console-page.js.map +1 -1
- package/src/lib/components/console/host-orders-card.component.js +38 -10
- package/src/lib/components/console/host-orders-card.component.js.map +1 -1
- package/src/lib/components/console/local-delivery-settings-card.component.d.ts +37 -0
- package/src/lib/components/console/local-delivery-settings-card.component.js +542 -0
- package/src/lib/components/console/local-delivery-settings-card.component.js.map +1 -0
- package/src/lib/components/console/locations-card.component.js +177 -3
- package/src/lib/components/console/locations-card.component.js.map +1 -1
- package/src/lib/components/console/order-detail-dialog.component.js +10 -0
- package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
- package/src/lib/components/console/order-local-fulfillment-panel.component.d.ts +44 -0
- package/src/lib/components/console/order-local-fulfillment-panel.component.js +265 -0
- package/src/lib/components/console/order-local-fulfillment-panel.component.js.map +1 -0
- package/src/lib/components/console/order-offline-notice.component.d.ts +12 -0
- package/src/lib/components/console/order-offline-notice.component.js +53 -0
- package/src/lib/components/console/order-offline-notice.component.js.map +1 -0
- package/src/lib/components/console/pickup-delivery-queue-card.component.d.ts +35 -0
- package/src/lib/components/console/pickup-delivery-queue-card.component.js +222 -0
- package/src/lib/components/console/pickup-delivery-queue-card.component.js.map +1 -0
- package/src/lib/components/console/pos/pos-api.d.ts +24 -1
- package/src/lib/components/console/pos/pos-api.js +13 -0
- package/src/lib/components/console/pos/pos-api.js.map +1 -1
- package/src/lib/components/console/pos/pos-item-dialog.component.d.ts +12 -0
- package/src/lib/components/console/pos/pos-item-dialog.component.js +10 -8
- package/src/lib/components/console/pos/pos-item-dialog.component.js.map +1 -1
- package/src/lib/components/console/pos/pos-kiosk-queue.component.d.ts +22 -0
- package/src/lib/components/console/pos/pos-kiosk-queue.component.js +168 -0
- package/src/lib/components/console/pos/pos-kiosk-queue.component.js.map +1 -0
- package/src/lib/components/console/pos/pos-tender-dialogs.component.d.ts +18 -0
- package/src/lib/components/console/pos/pos-tender-dialogs.component.js +116 -1
- package/src/lib/components/console/pos/pos-tender-dialogs.component.js.map +1 -1
- package/src/lib/components/console/pos/pos-tender-panel.component.d.ts +8 -0
- package/src/lib/components/console/pos/pos-tender-panel.component.js +77 -18
- package/src/lib/components/console/pos/pos-tender-panel.component.js.map +1 -1
- package/src/lib/components/console/pos/use-pos-display.d.ts +5 -2
- package/src/lib/components/console/pos/use-pos-display.js +3 -2
- package/src/lib/components/console/pos/use-pos-display.js.map +1 -1
- package/src/lib/components/console/pos-devices-card.component.js +2 -2
- package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
- package/src/lib/components/console/pos-display/pairing-screen.d.ts +5 -2
- package/src/lib/components/console/pos-display/pairing-screen.js +7 -5
- package/src/lib/components/console/pos-display/pairing-screen.js.map +1 -1
- package/src/lib/components/console/pos-display/pos-display-api.d.ts +7 -1
- package/src/lib/components/console/pos-display/pos-display-api.js +7 -2
- package/src/lib/components/console/pos-display/pos-display-api.js.map +1 -1
- package/src/lib/components/console/pos-kiosk/kiosk-checkout.d.ts +41 -0
- package/src/lib/components/console/pos-kiosk/kiosk-checkout.js +275 -0
- package/src/lib/components/console/pos-kiosk/kiosk-checkout.js.map +1 -0
- package/src/lib/components/console/pos-kiosk/kiosk-menu.d.ts +32 -0
- package/src/lib/components/console/pos-kiosk/kiosk-menu.js +344 -0
- package/src/lib/components/console/pos-kiosk/kiosk-menu.js.map +1 -0
- package/src/lib/components/console/pos-kiosk/kiosk-staff.d.ts +12 -0
- package/src/lib/components/console/pos-kiosk/kiosk-staff.js +257 -0
- package/src/lib/components/console/pos-kiosk/kiosk-staff.js.map +1 -0
- package/src/lib/components/console/pos-kiosk/pos-kiosk-api.d.ts +114 -0
- package/src/lib/components/console/pos-kiosk/pos-kiosk-api.js +189 -0
- package/src/lib/components/console/pos-kiosk/pos-kiosk-api.js.map +1 -0
- package/src/lib/components/console/pos-kiosk/use-kiosk-idle.d.ts +16 -0
- package/src/lib/components/console/pos-kiosk/use-kiosk-idle.js +80 -0
- package/src/lib/components/console/pos-kiosk/use-kiosk-idle.js.map +1 -0
- package/src/lib/components/console/pos-kiosk-page.component.d.ts +28 -0
- package/src/lib/components/console/pos-kiosk-page.component.js +791 -0
- package/src/lib/components/console/pos-kiosk-page.component.js.map +1 -0
- package/src/lib/components/console/pos-offline/pos-offline-api.d.ts +44 -0
- package/src/lib/components/console/pos-offline/pos-offline-api.js +69 -0
- package/src/lib/components/console/pos-offline/pos-offline-api.js.map +1 -0
- package/src/lib/components/console/pos-offline/pos-offline-banner.component.d.ts +7 -0
- package/src/lib/components/console/pos-offline/pos-offline-banner.component.js +135 -0
- package/src/lib/components/console/pos-offline/pos-offline-banner.component.js.map +1 -0
- package/src/lib/components/console/pos-offline/pos-offline-checkout.component.d.ts +32 -0
- package/src/lib/components/console/pos-offline/pos-offline-checkout.component.js +287 -0
- package/src/lib/components/console/pos-offline/pos-offline-checkout.component.js.map +1 -0
- package/src/lib/components/console/pos-offline/pos-offline-store.d.ts +63 -0
- package/src/lib/components/console/pos-offline/pos-offline-store.js +145 -0
- package/src/lib/components/console/pos-offline/pos-offline-store.js.map +1 -0
- package/src/lib/components/console/pos-offline/register-offline.d.ts +24 -0
- package/src/lib/components/console/pos-offline/register-offline.js +24 -0
- package/src/lib/components/console/pos-offline/register-offline.js.map +1 -0
- package/src/lib/components/console/pos-offline/use-pos-offline.d.ts +85 -0
- package/src/lib/components/console/pos-offline/use-pos-offline.js +542 -0
- package/src/lib/components/console/pos-offline/use-pos-offline.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-shift-history-card.component.js +72 -17
- package/src/lib/components/console/pos-ops/pos-shift-history-card.component.js.map +1 -1
- package/src/lib/components/console/pos-page.component.js +174 -27
- package/src/lib/components/console/pos-page.component.js.map +1 -1
- package/src/lib/components/console/products-hub-card.component.js +35 -10
- package/src/lib/components/console/products-hub-card.component.js.map +1 -1
- package/src/lib/components/console/shipping-settings-card.component.js +1 -1
- package/src/lib/components/console/shipping-settings-card.component.js.map +1 -1
- package/src/lib/components/console/stock-movements-card.component.js +48 -16
- package/src/lib/components/console/stock-movements-card.component.js.map +1 -1
- package/src/lib/components/console/store-zones.d.ts +8 -1
- package/src/lib/components/console/store-zones.js +11 -1
- package/src/lib/components/console/store-zones.js.map +1 -1
- package/src/lib/components/order-status.js +66 -0
- package/src/lib/components/order-status.js.map +1 -1
- package/src/lib/components/product-detail.js +42 -15
- package/src/lib/components/product-detail.js.map +1 -1
- package/src/lib/constants/local-fulfillment-list-query.d.ts +48 -0
- package/src/lib/constants/local-fulfillment-list-query.js +84 -0
- package/src/lib/constants/local-fulfillment-list-query.js.map +1 -0
- package/src/lib/constants/orders-list-query.d.ts +7 -2
- package/src/lib/constants/orders-list-query.js +89 -8
- package/src/lib/constants/orders-list-query.js.map +1 -1
- package/src/lib/constants/pos-shift-list-query.d.ts +22 -0
- package/src/lib/constants/pos-shift-list-query.js +115 -0
- package/src/lib/constants/pos-shift-list-query.js.map +1 -0
- package/src/lib/constants/product-list-query.d.ts +6 -1
- package/src/lib/constants/product-list-query.js +77 -8
- package/src/lib/constants/product-list-query.js.map +1 -1
- package/src/lib/constants/stock-movement-query.d.ts +3 -2
- package/src/lib/constants/stock-movement-query.js +52 -8
- package/src/lib/constants/stock-movement-query.js.map +1 -1
- package/src/lib/model/buyer-notifications.d.ts +5 -3
- package/src/lib/model/buyer-notifications.js +25 -4
- package/src/lib/model/buyer-notifications.js.map +1 -1
- package/src/lib/model/commerce-orders.d.ts +59 -0
- package/src/lib/model/commerce-orders.js.map +1 -1
- package/src/lib/model/commerce-pos-kiosk.d.ts +175 -0
- package/src/lib/model/commerce-pos-kiosk.js +147 -0
- package/src/lib/model/commerce-pos-kiosk.js.map +1 -0
- package/src/lib/model/commerce-pos-offline.d.ts +234 -0
- package/src/lib/model/commerce-pos-offline.js +301 -0
- package/src/lib/model/commerce-pos-offline.js.map +1 -0
- package/src/lib/model/commerce-pos-ops.d.ts +16 -6
- package/src/lib/model/commerce-pos-ops.js +12 -4
- package/src/lib/model/commerce-pos-ops.js.map +1 -1
- package/src/lib/model/commerce-pos.d.ts +13 -1
- package/src/lib/model/commerce-pos.js +5 -4
- package/src/lib/model/commerce-pos.js.map +1 -1
- package/src/lib/model/commerce-stock-holds.d.ts +24 -3
- package/src/lib/model/commerce-stock-holds.js +26 -8
- package/src/lib/model/commerce-stock-holds.js.map +1 -1
- package/src/lib/model/commerce.d.ts +3 -0
- package/src/lib/model/commerce.js.map +1 -1
- package/src/lib/model/index.d.ts +4 -0
- package/src/lib/model/index.js +4 -0
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/model/local-fulfillment-settings.d.ts +178 -0
- package/src/lib/model/local-fulfillment-settings.js +361 -0
- package/src/lib/model/local-fulfillment-settings.js.map +1 -0
- package/src/lib/model/order-list-fields.d.ts +10 -1
- package/src/lib/model/order-list-fields.js +1 -0
- package/src/lib/model/order-list-fields.js.map +1 -1
- package/src/lib/model/order-local-fulfillment.d.ts +131 -0
- package/src/lib/model/order-local-fulfillment.js +125 -0
- package/src/lib/model/order-local-fulfillment.js.map +1 -0
- package/src/lib/model/order-shipping-export.js +4 -0
- package/src/lib/model/order-shipping-export.js.map +1 -1
- package/src/lib/model/order-status-view.d.ts +20 -1
- package/src/lib/model/order-status-view.js +60 -6
- package/src/lib/model/order-status-view.js.map +1 -1
- package/src/lib/plugin-config.js +6 -2
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +8 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/pos-kiosk-config.d.ts +26 -0
- package/src/lib/pos-kiosk-config.js +59 -0
- package/src/lib/pos-kiosk-config.js.map +1 -0
- package/src/lib/server/api-v1/openapi.js +8 -0
- package/src/lib/server/api-v1/openapi.js.map +1 -1
- package/src/lib/server/api-v1/order-view.d.ts +19 -0
- package/src/lib/server/api-v1/order-view.js +35 -0
- package/src/lib/server/api-v1/order-view.js.map +1 -1
- package/src/lib/server/billing-webhook.js +238 -87
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/cart-checkout.d.ts +2 -0
- package/src/lib/server/cart-checkout.js +173 -13
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/checkout-extras.js +18 -4
- package/src/lib/server/checkout-extras.js.map +1 -1
- package/src/lib/server/checkout.js +71 -14
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/local-fulfillment-status.d.ts +86 -0
- package/src/lib/server/local-fulfillment-status.js +379 -0
- package/src/lib/server/local-fulfillment-status.js.map +1 -0
- package/src/lib/server/local-fulfillment.d.ts +190 -0
- package/src/lib/server/local-fulfillment.js +580 -0
- package/src/lib/server/local-fulfillment.js.map +1 -0
- package/src/lib/server/membership-register.js +3 -1
- package/src/lib/server/membership-register.js.map +1 -1
- package/src/lib/server/order-notifications.js +61 -0
- package/src/lib/server/order-notifications.js.map +1 -1
- package/src/lib/server/pos-display.d.ts +27 -1
- package/src/lib/server/pos-display.js +50 -9
- package/src/lib/server/pos-display.js.map +1 -1
- package/src/lib/server/pos-kiosk-principal.d.ts +29 -0
- package/src/lib/server/pos-kiosk-principal.js +42 -0
- package/src/lib/server/pos-kiosk-principal.js.map +1 -0
- package/src/lib/server/pos-kiosk.d.ts +54 -0
- package/src/lib/server/pos-kiosk.js +972 -0
- package/src/lib/server/pos-kiosk.js.map +1 -0
- package/src/lib/server/pos-offline-sync.d.ts +85 -0
- package/src/lib/server/pos-offline-sync.js +765 -0
- package/src/lib/server/pos-offline-sync.js.map +1 -0
- package/src/lib/server/pos-order.js +22 -4
- package/src/lib/server/pos-order.js.map +1 -1
- package/src/lib/server/pos-payment.d.ts +6 -0
- package/src/lib/server/pos-payment.js +225 -6
- package/src/lib/server/pos-payment.js.map +1 -1
- package/src/lib/server/pos-print.js +5 -0
- package/src/lib/server/pos-print.js.map +1 -1
- package/src/lib/server/pos-return.js +27 -0
- package/src/lib/server/pos-return.js.map +1 -1
- package/src/lib/server/pos-sale.d.ts +6 -0
- package/src/lib/server/pos-sale.js +2 -0
- package/src/lib/server/pos-sale.js.map +1 -1
- package/src/lib/server/pos-shift.js +8 -1
- package/src/lib/server/pos-shift.js.map +1 -1
- package/src/lib/server/refund.js +3 -0
- package/src/lib/server/refund.js.map +1 -1
- package/src/lib/server/stock-hold.d.ts +9 -1
- package/src/lib/server/stock-hold.js +15 -7
- package/src/lib/server/stock-hold.js.map +1 -1
- package/src/lib/server.js +16 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/tenant-emails.js +161 -0
- package/src/lib/tenant-emails.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/checkout-extras.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { PluginApiHandler } from '@aglyn/aglyn/server'\nimport {\n hasPluginCheckoutExtras,\n quotePluginCheckoutExtras,\n type QuotedPluginCheckoutExtra,\n} from '@aglyn/aglyn/plugin-manager/plugin-checkout-extras'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { isDocumentId } from '@aglyn/tenant-data-admin/server/document-id'\nimport * as CommerceModel from '../model'\nimport { readCartId } from './cart-cookie'\n\n/**\n * Optional lines another plugin offers at the cart (AGL-3635) — package\n * protection is the first — through core's checkout-extras seam. Commerce\n * names no insurer: it asks the seam, shows each offer as a box, and charges\n * the ones the buyer ticked as ordinary lines on the session.\n *\n * Asked only for a basket that ships: an extra is about a parcel, and a cart\n * of downloads has none.\n */\n\n/** How long the cart and checkout wait for an offer before selling without it. */\nexport const CHECKOUT_EXTRAS_TIMEOUT_MS = 2_500\n\n/** One cart line, priced, as the extras quote reads it. */\nexport interface CheckoutExtrasLine {\n productId: string\n name: string\n sku?: string\n quantity: number\n unitCents: number\n physical: boolean\n}\n\n/** Every offer for a basket, or none. Never throws. */\nexport async function quoteCartExtras(input: {\n hostId: string\n lines: CheckoutExtrasLine[]\n destination?: { country?: string; postalCode?: string }\n}): Promise<QuotedPluginCheckoutExtra[]> {\n if (!hasPluginCheckoutExtras()) return []\n const lines = input.lines.filter((line) => line.quantity > 0 && line.unitCents >= 0)\n if (!lines.some((line) => line.physical)) return []\n const itemsCents = lines.reduce((sum, line) => sum + line.unitCents * line.quantity, 0)\n if (itemsCents <= 0) return []\n const country = String(input.destination?.country ?? '').trim().toUpperCase().slice(0, 2)\n const postalCode = String(input.destination?.postalCode ?? '').trim().slice(0, 12)\n return quotePluginCheckoutExtras(\n {\n hostId: input.hostId,\n // Every storefront session is charged in dollars today (`cart-checkout.ts`).\n currency: 'usd',\n itemsCents,\n lines: lines.map((line) => ({\n itemId: line.productId,\n name: line.name,\n ...(line.sku ? { sku: line.sku } : {}),\n quantity: line.quantity,\n unitCents: line.unitCents,\n ships: line.physical,\n })),\n ...(country || postalCode\n ? { destination: { ...(country ? { country } : {}), ...(postalCode ? { postalCode } : {}) } }\n : {}),\n },\n { timeoutMs: CHECKOUT_EXTRAS_TIMEOUT_MS },\n )\n}\n\n/** What the cart shows of an offer: never the quote id. */\nexport interface CartExtraView {\n id: string\n label: string\n description?: string\n amountCents: number\n defaultSelected: boolean\n termsUrl?: string\n}\n\nexport function cartExtraView(extra: QuotedPluginCheckoutExtra): CartExtraView {\n return {\n id: extra.id,\n label: extra.label,\n ...(extra.description ? { description: extra.description } : {}),\n amountCents: extra.amountCents,\n defaultSelected: extra.defaultSelected,\n ...(extra.termsUrl ? { termsUrl: extra.termsUrl } : {}),\n }\n}\n\n/**\n * `GET /api/commerce/cart-extras?hostId=…` — the offers for the visitor's own\n * cart (cookie). Asked by the cart when it is SHOWN, never by the badge on\n * every page. A store with no offering plugin answers `[]` without reading a\n * document. `private, no-store`: the answer is this basket's.\n */\nexport const cartExtrasHandler: PluginApiHandler = async (req, res) => {\n res.setHeader('Cache-Control', 'private, no-store')\n if (req.method !== 'GET') return res.status(405).json({ error: 'Method not allowed' })\n const hostId = String(req.query.hostId ?? '')\n if (!isDocumentId(hostId)) return res.status(400).json({ error: 'Missing or invalid hostId' })\n if (!hasPluginCheckoutExtras()) return res.status(200).json({ extras: [] })\n const cartId = readCartId(req.cookies, hostId)\n if (!cartId) return res.status(200).json({ extras: [] })\n try {\n const hostRef = firebaseAdmin.app().firestore().collection('hosts').doc(hostId)\n const cart = ((await hostRef.collection('carts').doc(cartId).get()).data() as CommerceModel.HostCart | undefined) ?? {\n lines: [],\n }\n const lines = await resolveCartExtrasLines(hostRef, cart.lines ?? [])\n const extras = await quoteCartExtras({\n hostId,\n lines,\n destination: {\n country: String(req.query.country ?? ''),\n postalCode: String(req.query.postalCode ?? ''),\n },\n })\n return res.status(200).json({ extras: extras.map(cartExtraView) })\n } catch (error) {\n console.error('cart extras failed', error)\n // An offer is never worth a broken cart.\n return res.status(200).json({ extras: [] })\n }\n}\n\n/** The cart's purchasable lines, priced from the product documents. */\nasync function resolveCartExtrasLines(\n hostRef: FirebaseFirestore.DocumentReference,\n cartLines: CommerceModel.CartLine[],\n): Promise<CheckoutExtrasLine[]> {\n const ids = [...new Set(cartLines.map((line) => line.productId))].filter((id) => isDocumentId(id))\n const snapshots = await Promise.all(ids.map((id) => hostRef.collection('products').doc(id).get()))\n const products = new Map(\n snapshots.map((snapshot) => [\n snapshot.id,\n snapshot.exists ? CommerceModel.liftLegacyProduct(snapshot.data() as never) : null,\n ]),\n )\n const lines: CheckoutExtrasLine[] = []\n for (const line of cartLines) {\n const product = products.get(line.productId)\n if (!product || product.deletedAt || product.status !== 'active') continue\n const variant = line.variantId\n ? product.variants.find((item) => item.id === line.variantId)\n : product.variants[0]\n if (!variant || !CommerceModel.variantHasPrice(variant)) continue\n lines.push({\n productId: line.productId,\n name: product.name,\n ...(variant.sku ? { sku: variant.sku } : {}),\n quantity: Math.max(0, Math.round(Number(line.quantity) || 0)),\n unitCents: Math.round(Number(variant.priceUsd) * 100),\n physical: (product.type ?? 'physical') === 'physical',\n })\n }\n return lines\n}\n"],"names":["hasPluginCheckoutExtras","quotePluginCheckoutExtras","firebaseAdmin","isDocumentId","CommerceModel","readCartId","CHECKOUT_EXTRAS_TIMEOUT_MS","quoteCartExtras","input","lines","filter","line","quantity","unitCents","some","physical","itemsCents","reduce","sum","country","String","destination","trim","toUpperCase","slice","postalCode","hostId","currency","map","itemId","productId","name","sku","ships","timeoutMs","cartExtraView","extra","id","label","description","amountCents","defaultSelected","termsUrl","cartExtrasHandler","req","res","setHeader","method","status","json","error","query","extras","cartId","cookies","cart","hostRef","app","firestore","collection","doc","get","data","resolveCartExtrasLines","console","cartLines","ids","Set","snapshots","Promise","all","products","Map","snapshot","exists","liftLegacyProduct","product","deletedAt","variant","variantId","variants","find","item","variantHasPrice","push","Math","max","round","Number","priceUsd","type"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAGD,SACEA,uBAAuB,EACvBC,yBAAyB,QAEpB,qDAAoD;AAC3D,SAASC,aAAa,QAAQ,2BAA0B;AACxD,SAASC,YAAY,QAAQ,8CAA6C;AAC1E,YAAYC,mBAAmB,oBAAU;AACzC,SAASC,UAAU,QAAQ,mBAAe;AAE1C;;;;;;;;CAQC,GAED,gFAAgF,GAChF,OAAO,MAAMC,6BAA6B,KAAK;AAY/C,qDAAqD,GACrD,OAAO,eAAeC,gBAAgBC,KAIrC;;QAMwBA,oBACGA;IAN1B,IAAI,CAACR,2BAA2B,OAAO,EAAE;IACzC,MAAMS,QAAQD,MAAMC,KAAK,CAACC,MAAM,CAAC,CAACC,OAASA,KAAKC,QAAQ,GAAG,KAAKD,KAAKE,SAAS,IAAI;IAClF,IAAI,CAACJ,MAAMK,IAAI,CAAC,CAACH,OAASA,KAAKI,QAAQ,GAAG,OAAO,EAAE;IACnD,MAAMC,aAAaP,MAAMQ,MAAM,CAAC,CAACC,KAAKP,OAASO,MAAMP,KAAKE,SAAS,GAAGF,KAAKC,QAAQ,EAAE;IACrF,IAAII,cAAc,GAAG,OAAO,EAAE;IAC9B,MAAMG,UAAUC,gBAAOZ,qBAAAA,MAAMa,WAAW,qBAAjBb,mBAAmBW,OAAO,mBAAI,IAAIG,IAAI,GAAGC,WAAW,GAAGC,KAAK,CAAC,GAAG;IACvF,MAAMC,aAAaL,iBAAOZ,sBAAAA,MAAMa,WAAW,qBAAjBb,oBAAmBiB,UAAU,oBAAI,IAAIH,IAAI,GAAGE,KAAK,CAAC,GAAG;IAC/E,OAAOvB,0BACL;QACEyB,QAAQlB,MAAMkB,MAAM;QACpB,6EAA6E;QAC7EC,UAAU;QACVX;QACAP,OAAOA,MAAMmB,GAAG,CAAC,CAACjB,OAAU;gBAC1BkB,QAAQlB,KAAKmB,SAAS;gBACtBC,MAAMpB,KAAKoB,IAAI;eACXpB,KAAKqB,GAAG,GAAG;gBAAEA,KAAKrB,KAAKqB,GAAG;YAAC,IAAI,CAAC;gBACpCpB,UAAUD,KAAKC,QAAQ;gBACvBC,WAAWF,KAAKE,SAAS;gBACzBoB,OAAOtB,KAAKI,QAAQ;;OAElBI,WAAWM,aACX;QAAEJ,aAAa,aAAMF,UAAU;YAAEA;QAAQ,IAAI,CAAC,GAAQM,aAAa;YAAEA;QAAW,IAAI,CAAC;IAAK,IAC1F,CAAC,IAEP;QAAES,WAAW5B;IAA2B;AAE5C;AAYA,OAAO,SAAS6B,cAAcC,KAAgC;IAC5D,OAAO;QACLC,IAAID,MAAMC,EAAE;QACZC,OAAOF,MAAME,KAAK;OACdF,MAAMG,WAAW,GAAG;QAAEA,aAAaH,MAAMG,WAAW;IAAC,IAAI,CAAC;QAC9DC,aAAaJ,MAAMI,WAAW;QAC9BC,iBAAiBL,MAAMK,eAAe;OAClCL,MAAMM,QAAQ,GAAG;QAAEA,UAAUN,MAAMM,QAAQ;IAAC,IAAI,CAAC;AAEzD;AAEA;;;;;CAKC,GACD,OAAO,MAAMC,oBAAsC,OAAOC,KAAKC;QAGvCD;IAFtBC,IAAIC,SAAS,CAAC,iBAAiB;IAC/B,IAAIF,IAAIG,MAAM,KAAK,OAAO,OAAOF,IAAIG,MAAM,CAAC,KAAKC,IAAI,CAAC;QAAEC,OAAO;IAAqB;IACpF,MAAMxB,SAASN,QAAOwB,oBAAAA,IAAIO,KAAK,CAACzB,MAAM,YAAhBkB,oBAAoB;IAC1C,IAAI,CAACzC,aAAauB,SAAS,OAAOmB,IAAIG,MAAM,CAAC,KAAKC,IAAI,CAAC;QAAEC,OAAO;IAA4B;IAC5F,IAAI,CAAClD,2BAA2B,OAAO6C,IAAIG,MAAM,CAAC,KAAKC,IAAI,CAAC;QAAEG,QAAQ,EAAE;IAAC;IACzE,MAAMC,SAAShD,WAAWuC,IAAIU,OAAO,EAAE5B;IACvC,IAAI,CAAC2B,QAAQ,OAAOR,IAAIG,MAAM,CAAC,KAAKC,IAAI,CAAC;QAAEG,QAAQ,EAAE;IAAC;IACtD,IAAI;YAEY,OAGsCG,aAKhCX,oBACGA;QAVvB,MAAMY,UAAUtD,cAAcuD,GAAG,GAAGC,SAAS,GAAGC,UAAU,CAAC,SAASC,GAAG,CAAClC;QACxE,MAAM6B,QAAQ,QAAA,AAAC,CAAA,MAAMC,QAAQG,UAAU,CAAC,SAASC,GAAG,CAACP,QAAQQ,GAAG,EAAC,EAAGC,IAAI,cAA1D,QAAuG;YACnHrD,OAAO,EAAE;QACX;QACA,MAAMA,QAAQ,MAAMsD,uBAAuBP,UAASD,cAAAA,KAAK9C,KAAK,YAAV8C,cAAc,EAAE;QACpE,MAAMH,SAAS,MAAM7C,gBAAgB;YACnCmB;YACAjB;YACAY,aAAa;gBACXF,SAASC,QAAOwB,qBAAAA,IAAIO,KAAK,CAAChC,OAAO,YAAjByB,qBAAqB;gBACrCnB,YAAYL,QAAOwB,wBAAAA,IAAIO,KAAK,CAAC1B,UAAU,YAApBmB,wBAAwB;YAC7C;QACF;QACA,OAAOC,IAAIG,MAAM,CAAC,KAAKC,IAAI,CAAC;YAAEG,QAAQA,OAAOxB,GAAG,CAACO;QAAe;IAClE,EAAE,OAAOe,OAAO;QACdc,QAAQd,KAAK,CAAC,sBAAsBA;QACpC,yCAAyC;QACzC,OAAOL,IAAIG,MAAM,CAAC,KAAKC,IAAI,CAAC;YAAEG,QAAQ,EAAE;QAAC;IAC3C;AACF,EAAC;AAED,qEAAqE,GACrE,eAAeW,uBACbP,OAA4C,EAC5CS,SAAmC;IAEnC,MAAMC,MAAM;WAAI,IAAIC,IAAIF,UAAUrC,GAAG,CAAC,CAACjB,OAASA,KAAKmB,SAAS;KAAG,CAACpB,MAAM,CAAC,CAAC2B,KAAOlC,aAAakC;IAC9F,MAAM+B,YAAY,MAAMC,QAAQC,GAAG,CAACJ,IAAItC,GAAG,CAAC,CAACS,KAAOmB,QAAQG,UAAU,CAAC,YAAYC,GAAG,CAACvB,IAAIwB,GAAG;IAC9F,MAAMU,WAAW,IAAIC,IACnBJ,UAAUxC,GAAG,CAAC,CAAC6C,WAAa;YAC1BA,SAASpC,EAAE;YACXoC,SAASC,MAAM,GAAGtE,cAAcuE,iBAAiB,CAACF,SAASX,IAAI,MAAe;SAC/E;IAEH,MAAMrD,QAA8B,EAAE;IACtC,KAAK,MAAME,QAAQsD,UAAW;YAafW;QAZb,MAAMA,UAAUL,SAASV,GAAG,CAAClD,KAAKmB,SAAS;QAC3C,IAAI,CAAC8C,WAAWA,QAAQC,SAAS,IAAID,QAAQ5B,MAAM,KAAK,UAAU;QAClE,MAAM8B,UAAUnE,KAAKoE,SAAS,GAC1BH,QAAQI,QAAQ,CAACC,IAAI,CAAC,CAACC,OAASA,KAAK7C,EAAE,KAAK1B,KAAKoE,SAAS,IAC1DH,QAAQI,QAAQ,CAAC,EAAE;QACvB,IAAI,CAACF,WAAW,CAAC1E,cAAc+E,eAAe,CAACL,UAAU;QACzDrE,MAAM2E,IAAI,CAAC;YACTtD,WAAWnB,KAAKmB,SAAS;YACzBC,MAAM6C,QAAQ7C,IAAI;WACd+C,QAAQ9C,GAAG,GAAG;YAAEA,KAAK8C,QAAQ9C,GAAG;QAAC,IAAI,CAAC;YAC1CpB,UAAUyE,KAAKC,GAAG,CAAC,GAAGD,KAAKE,KAAK,CAACC,OAAO7E,KAAKC,QAAQ,KAAK;YAC1DC,WAAWwE,KAAKE,KAAK,CAACC,OAAOV,QAAQW,QAAQ,IAAI;YACjD1E,UAAU,EAAC6D,gBAAAA,QAAQc,IAAI,YAAZd,gBAAgB,gBAAgB;;IAE/C;IACA,OAAOnE;AACT"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/checkout-extras.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { PluginApiHandler } from '@aglyn/aglyn/server'\nimport {\n hasPluginCheckoutExtras,\n quotePluginCheckoutExtras,\n type QuotedPluginCheckoutExtra,\n} from '@aglyn/aglyn/plugin-manager/plugin-checkout-extras'\nimport {\n hasPluginCheckoutCredits,\n offeredCheckoutCredits,\n} from '@aglyn/aglyn/plugin-manager/plugin-checkout-credits'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport { isDocumentId } from '@aglyn/tenant-data-admin/server/document-id'\nimport * as CommerceModel from '../model'\nimport { readCartId } from './cart-cookie'\n\n/**\n * Optional lines another plugin offers at the cart (AGL-3635) — package\n * protection is the first — through core's checkout-extras seam. Commerce\n * names no insurer: it asks the seam, shows each offer as a box, and charges\n * the ones the buyer ticked as ordinary lines on the session.\n *\n * Asked only for a basket that ships: an extra is about a parcel, and a cart\n * of downloads has none.\n */\n\n/** How long the cart and checkout wait for an offer before selling without it. */\nexport const CHECKOUT_EXTRAS_TIMEOUT_MS = 2_500\n\n/** One cart line, priced, as the extras quote reads it. */\nexport interface CheckoutExtrasLine {\n productId: string\n name: string\n sku?: string\n quantity: number\n unitCents: number\n physical: boolean\n}\n\n/** Every offer for a basket, or none. Never throws. */\nexport async function quoteCartExtras(input: {\n hostId: string\n lines: CheckoutExtrasLine[]\n destination?: { country?: string; postalCode?: string }\n}): Promise<QuotedPluginCheckoutExtra[]> {\n if (!hasPluginCheckoutExtras()) return []\n const lines = input.lines.filter((line) => line.quantity > 0 && line.unitCents >= 0)\n if (!lines.some((line) => line.physical)) return []\n const itemsCents = lines.reduce((sum, line) => sum + line.unitCents * line.quantity, 0)\n if (itemsCents <= 0) return []\n const country = String(input.destination?.country ?? '').trim().toUpperCase().slice(0, 2)\n const postalCode = String(input.destination?.postalCode ?? '').trim().slice(0, 12)\n return quotePluginCheckoutExtras(\n {\n hostId: input.hostId,\n // Every storefront session is charged in dollars today (`cart-checkout.ts`).\n currency: 'usd',\n itemsCents,\n lines: lines.map((line) => ({\n itemId: line.productId,\n name: line.name,\n ...(line.sku ? { sku: line.sku } : {}),\n quantity: line.quantity,\n unitCents: line.unitCents,\n ships: line.physical,\n })),\n ...(country || postalCode\n ? { destination: { ...(country ? { country } : {}), ...(postalCode ? { postalCode } : {}) } }\n : {}),\n },\n { timeoutMs: CHECKOUT_EXTRAS_TIMEOUT_MS },\n )\n}\n\n/** What the cart shows of an offer: never the quote id. */\nexport interface CartExtraView {\n id: string\n label: string\n description?: string\n amountCents: number\n defaultSelected: boolean\n termsUrl?: string\n}\n\nexport function cartExtraView(extra: QuotedPluginCheckoutExtra): CartExtraView {\n return {\n id: extra.id,\n label: extra.label,\n ...(extra.description ? { description: extra.description } : {}),\n amountCents: extra.amountCents,\n defaultSelected: extra.defaultSelected,\n ...(extra.termsUrl ? { termsUrl: extra.termsUrl } : {}),\n }\n}\n\n/**\n * `GET /api/commerce/cart-extras?hostId=…` — the offers for the visitor's own\n * cart (cookie). Asked by the cart when it is SHOWN, never by the badge on\n * every page. A store with no offering plugin answers `[]` without reading a\n * document. `private, no-store`: the answer is this basket's.\n */\nexport const cartExtrasHandler: PluginApiHandler = async (req, res) => {\n res.setHeader('Cache-Control', 'private, no-store')\n if (req.method !== 'GET') return res.status(405).json({ error: 'Method not allowed' })\n const hostId = String(req.query.hostId ?? '')\n if (!isDocumentId(hostId)) return res.status(400).json({ error: 'Missing or invalid hostId' })\n // Which store-credit codes the cart takes (AGL-3640) — rewards, referral\n // credit — by provider and name only, so the cart draws the field.\n const credits = hasPluginCheckoutCredits()\n ? (await offeredCheckoutCredits({ hostId, channel: 'online' })).map((credit) => ({\n providerId: credit.providerId,\n label: credit.label,\n }))\n : []\n if (!hasPluginCheckoutExtras()) return res.status(200).json({ extras: [], credits })\n const cartId = readCartId(req.cookies, hostId)\n if (!cartId) return res.status(200).json({ extras: [], credits })\n try {\n const hostRef = firebaseAdmin.app().firestore().collection('hosts').doc(hostId)\n const cart = ((await hostRef.collection('carts').doc(cartId).get()).data() as CommerceModel.HostCart | undefined) ?? {\n lines: [],\n }\n const lines = await resolveCartExtrasLines(hostRef, cart.lines ?? [])\n const extras = await quoteCartExtras({\n hostId,\n lines,\n destination: {\n country: String(req.query.country ?? ''),\n postalCode: String(req.query.postalCode ?? ''),\n },\n })\n return res.status(200).json({ extras: extras.map(cartExtraView), credits })\n } catch (error) {\n console.error('cart extras failed', error)\n // An offer is never worth a broken cart.\n return res.status(200).json({ extras: [], credits })\n }\n}\n\n/** The cart's purchasable lines, priced from the product documents. */\nasync function resolveCartExtrasLines(\n hostRef: FirebaseFirestore.DocumentReference,\n cartLines: CommerceModel.CartLine[],\n): Promise<CheckoutExtrasLine[]> {\n const ids = [...new Set(cartLines.map((line) => line.productId))].filter((id) => isDocumentId(id))\n const snapshots = await Promise.all(ids.map((id) => hostRef.collection('products').doc(id).get()))\n const products = new Map(\n snapshots.map((snapshot) => [\n snapshot.id,\n snapshot.exists ? CommerceModel.liftLegacyProduct(snapshot.data() as never) : null,\n ]),\n )\n const lines: CheckoutExtrasLine[] = []\n for (const line of cartLines) {\n const product = products.get(line.productId)\n if (!product || product.deletedAt || product.status !== 'active') continue\n const variant = line.variantId\n ? product.variants.find((item) => item.id === line.variantId)\n : product.variants[0]\n if (!variant || !CommerceModel.variantHasPrice(variant)) continue\n lines.push({\n productId: line.productId,\n name: product.name,\n ...(variant.sku ? { sku: variant.sku } : {}),\n quantity: Math.max(0, Math.round(Number(line.quantity) || 0)),\n unitCents: Math.round(Number(variant.priceUsd) * 100),\n physical: (product.type ?? 'physical') === 'physical',\n })\n }\n return lines\n}\n"],"names":["hasPluginCheckoutExtras","quotePluginCheckoutExtras","hasPluginCheckoutCredits","offeredCheckoutCredits","firebaseAdmin","isDocumentId","CommerceModel","readCartId","CHECKOUT_EXTRAS_TIMEOUT_MS","quoteCartExtras","input","lines","filter","line","quantity","unitCents","some","physical","itemsCents","reduce","sum","country","String","destination","trim","toUpperCase","slice","postalCode","hostId","currency","map","itemId","productId","name","sku","ships","timeoutMs","cartExtraView","extra","id","label","description","amountCents","defaultSelected","termsUrl","cartExtrasHandler","req","res","setHeader","method","status","json","error","query","credits","channel","credit","providerId","extras","cartId","cookies","cart","hostRef","app","firestore","collection","doc","get","data","resolveCartExtrasLines","console","cartLines","ids","Set","snapshots","Promise","all","products","Map","snapshot","exists","liftLegacyProduct","product","deletedAt","variant","variantId","variants","find","item","variantHasPrice","push","Math","max","round","Number","priceUsd","type"],"mappings":"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"}
|
|
@@ -28,6 +28,7 @@ import { taxEngineSessionMetadata } from "../model/commerce-tax-engine.js";
|
|
|
28
28
|
import { quoteSaleTaxWithEngine } from "./tax-engine-quote.js";
|
|
29
29
|
import { holdPromotionSlot, promotionHoldKey } from "./promotion-hold.js";
|
|
30
30
|
import { holdStock, stockHoldKey } from "./stock-hold.js";
|
|
31
|
+
import { appendLocalFulfillmentMetadata, localFulfillmentLocationId, localStockRefusalMessage, planLocalFulfillment, readLocalFulfillmentRequest, readLocalFulfillmentStore } from "./local-fulfillment.js";
|
|
31
32
|
import { applyNativeCheckoutParams, nativeCheckoutStripeHeaders, readCheckoutSessionPayload, resolveNativeCheckoutMode } from "./native-checkout.js";
|
|
32
33
|
import { ensureCheckoutDomain } from "./payment-method-domains.js";
|
|
33
34
|
/**
|
|
@@ -106,7 +107,7 @@ import { ensureCheckoutDomain } from "./payment-method-domains.js";
|
|
|
106
107
|
if (held) await held.release();
|
|
107
108
|
};
|
|
108
109
|
try {
|
|
109
|
-
var _variant_priceUsd, _req_headers_referer, _req_headers_host, _storeSettings_get,
|
|
110
|
+
var _variant_priceUsd, _req_headers_referer, _req_headers_host, _storeSettings_get, _lifted_type, _ref1, _ref2, _lifted_name, _ref3, _lifted_type1, _variant_weightGrams, _lifted_type2, _lifted_type3, _product_name, _ref4;
|
|
110
111
|
var _ownerOrg_org, _ownerOrg_org1, _ownerOrg_org2;
|
|
111
112
|
const firestore = firebaseAdmin.app().firestore();
|
|
112
113
|
const hostRef = firestore.collection('hosts').doc(hostId);
|
|
@@ -324,10 +325,10 @@ import { ensureCheckoutDomain } from "./payment-method-domains.js";
|
|
|
324
325
|
// hub did not already answer for this code.
|
|
325
326
|
let appliedCoupon = '';
|
|
326
327
|
if (couponCode && !resolvedDiscount) {
|
|
327
|
-
var
|
|
328
|
+
var _ref5;
|
|
328
329
|
const couponSnapshot = await hostRef.collection('coupons').doc(couponCode).get();
|
|
329
330
|
const coupon = couponSnapshot.data();
|
|
330
|
-
const percentOff = Number((
|
|
331
|
+
const percentOff = Number((_ref5 = coupon == null ? void 0 : coupon.percentOff) != null ? _ref5 : 0);
|
|
331
332
|
const expired = (coupon == null ? void 0 : coupon.expiresAtMs) != null && Number(coupon.expiresAtMs) < Date.now();
|
|
332
333
|
// A PRE-FILTER, no longer the enforcement (AGL-2453). It answers early so
|
|
333
334
|
// an obviously spent code 400s before a claim is minted, and it counts
|
|
@@ -381,6 +382,31 @@ import { ensureCheckoutDomain } from "./payment-method-domains.js";
|
|
|
381
382
|
// checkout, AGL-296). Exempt products skip both.
|
|
382
383
|
const storeSettings = await hostRef.collection('settings').doc('store').get();
|
|
383
384
|
const taxSettings = (_storeSettings_get = storeSettings.get('tax')) != null ? _storeSettings_get : {};
|
|
385
|
+
// PICKUP OR THE STORE'S OWN DELIVERY (AGL-3624), for a one-time physical
|
|
386
|
+
// sale, decided from the store's settings exactly as the cart decides it.
|
|
387
|
+
// Above the claim, so a refusal keeps the key. See `local-fulfillment.ts`.
|
|
388
|
+
const localRequest = readLocalFulfillmentRequest(body.fulfillment);
|
|
389
|
+
const localPlan = localRequest && ((_lifted_type = lifted.type) != null ? _lifted_type : 'physical') === 'physical' && !isSubscription ? await planLocalFulfillment({
|
|
390
|
+
hostId,
|
|
391
|
+
request: localRequest,
|
|
392
|
+
itemsCents: listUnitAmountCents * quantity,
|
|
393
|
+
hasPhysicalLine: true,
|
|
394
|
+
store: await readLocalFulfillmentStore({
|
|
395
|
+
hostRef,
|
|
396
|
+
storeSettings: (_ref1 = storeSettings.data == null ? void 0 : storeSettings.data.call(storeSettings)) != null ? _ref1 : null,
|
|
397
|
+
org: ownerOrg.org,
|
|
398
|
+
host: (_ref2 = hostSnapshot.data == null ? void 0 : hostSnapshot.data.call(hostSnapshot)) != null ? _ref2 : null
|
|
399
|
+
})
|
|
400
|
+
}) : {
|
|
401
|
+
kind: 'shipping'
|
|
402
|
+
};
|
|
403
|
+
if (localPlan.kind === 'refusal') {
|
|
404
|
+
return res.status(localPlan.status).json(_extends({
|
|
405
|
+
error: localPlan.error
|
|
406
|
+
}, localPlan.changed ? {
|
|
407
|
+
fulfillmentChanged: localPlan.changed
|
|
408
|
+
} : {}));
|
|
409
|
+
}
|
|
384
410
|
// AGL-1999: an unset tax mode is NOT a decision, and it must not sell.
|
|
385
411
|
// `mode` was `undefined` for every store whose owner never opened the
|
|
386
412
|
// Taxes card — the default state of every new storefront — and each
|
|
@@ -488,7 +514,7 @@ import { ensureCheckoutDomain } from "./payment-method-domains.js";
|
|
|
488
514
|
const claimed = await claimAttempt(firestore, {
|
|
489
515
|
kind: 'commerce-checkout',
|
|
490
516
|
scopeId: hostId,
|
|
491
|
-
orgId: String((
|
|
517
|
+
orgId: String((_ref3 = ownerOrg == null ? void 0 : (_ownerOrg_org1 = ownerOrg.org) == null ? void 0 : _ownerOrg_org1.id) != null ? _ref3 : ''),
|
|
492
518
|
key: idempotencyKey,
|
|
493
519
|
busyMessage: 'This checkout is already being processed'
|
|
494
520
|
});
|
|
@@ -535,9 +561,12 @@ import { ensureCheckoutDomain } from "./payment-method-domains.js";
|
|
|
535
561
|
quantity
|
|
536
562
|
}
|
|
537
563
|
] : [],
|
|
538
|
-
label: `buy-now ${hostId}/${productId}
|
|
564
|
+
label: `buy-now ${hostId}/${productId}`,
|
|
565
|
+
// At the pickup location, or the one deliveries leave from (AGL-3624).
|
|
566
|
+
locationId: localFulfillmentLocationId(localPlan)
|
|
539
567
|
});
|
|
540
568
|
if (!held.ok) {
|
|
569
|
+
var _lifted_name1;
|
|
541
570
|
// Nothing has been minted yet, so the key goes back and the same
|
|
542
571
|
// button works when the units return (AGL-1697). `sold-out` is named
|
|
543
572
|
// separately from an empty shelf: the merchant HAS stock and someone
|
|
@@ -546,7 +575,7 @@ import { ensureCheckoutDomain } from "./payment-method-domains.js";
|
|
|
546
575
|
await releaseStock();
|
|
547
576
|
await claim.release();
|
|
548
577
|
return res.status(409).json({
|
|
549
|
-
error: held.reason === 'sold-out' ? CommerceModel.STOCK_HELD_MESSAGE : 'Sold out'
|
|
578
|
+
error: held.reason === 'sold-out-at-location' ? localStockRefusalMessage(localPlan, String((_lifted_name1 = lifted.name) != null ? _lifted_name1 : '')) : held.reason === 'sold-out' ? CommerceModel.STOCK_HELD_MESSAGE : 'Sold out'
|
|
550
579
|
});
|
|
551
580
|
}
|
|
552
581
|
if (held.holdKey) {
|
|
@@ -596,13 +625,13 @@ import { ensureCheckoutDomain } from "./payment-method-domains.js";
|
|
|
596
625
|
// paths quietly diverge. Behaviour here is unchanged.
|
|
597
626
|
let recurringTaxRateId = '';
|
|
598
627
|
if (isSubscription && taxCents > 0) {
|
|
599
|
-
var
|
|
600
|
-
recurringTaxRateId = (
|
|
628
|
+
var _ref6;
|
|
629
|
+
recurringTaxRateId = (_ref6 = await resolveManualTaxRateId({
|
|
601
630
|
hostRef,
|
|
602
631
|
taxPct,
|
|
603
632
|
taxLabel,
|
|
604
633
|
headers: stripeKeyHeader('tax-rate')
|
|
605
|
-
})) != null ?
|
|
634
|
+
})) != null ? _ref6 : '';
|
|
606
635
|
if (!recurringTaxRateId) {
|
|
607
636
|
// Nothing was sold — hand the key back so the same button works
|
|
608
637
|
// once Stripe does (AGL-1697).
|
|
@@ -661,10 +690,21 @@ import { ensureCheckoutDomain } from "./payment-method-domains.js";
|
|
|
661
690
|
// `shipping_rate_data` carries no country. `planCheckoutShipping` pairs
|
|
662
691
|
// the two lists, and refuses rather than guessing.
|
|
663
692
|
const shippingSettings = storeSettings.get('shipping');
|
|
664
|
-
const shipsPhysically = ((
|
|
693
|
+
const shipsPhysically = ((_lifted_type1 = lifted.type) != null ? _lifted_type1 : 'physical') === 'physical' && !isSubscription;
|
|
665
694
|
// Live carrier rates (AGL-3612) are quoted here, before the session,
|
|
666
695
|
// for the destination the shopper declared; see `carrier-shipping.ts`.
|
|
667
|
-
const shippingPlan =
|
|
696
|
+
const shippingPlan = // Collected: nothing to price, no address (AGL-3624).
|
|
697
|
+
localPlan.kind === 'pickup' ? {
|
|
698
|
+
countries: [],
|
|
699
|
+
options: []
|
|
700
|
+
} : localPlan.kind === 'local_delivery' ? {
|
|
701
|
+
countries: [
|
|
702
|
+
localPlan.country
|
|
703
|
+
],
|
|
704
|
+
options: [
|
|
705
|
+
localPlan.option
|
|
706
|
+
]
|
|
707
|
+
} : shipsPhysically ? await planCheckoutShippingWithCarriers({
|
|
668
708
|
hostId,
|
|
669
709
|
settings: shippingSettings,
|
|
670
710
|
cart: _extends({
|
|
@@ -681,6 +721,19 @@ import { ensureCheckoutDomain } from "./payment-method-domains.js";
|
|
|
681
721
|
countries: CommerceModel.CHECKOUT_SHIPPING_COUNTRIES,
|
|
682
722
|
options: []
|
|
683
723
|
};
|
|
724
|
+
// The unnamed `Local pickup` rate gives way to the store's pickup
|
|
725
|
+
// locations, which the shopper chooses before checkout (AGL-3624).
|
|
726
|
+
if (localPlan.kind === 'shipping' && (shippingSettings == null ? void 0 : shippingSettings.localPickup) && shippingPlan.options.some((option)=>option.rateId === CommerceModel.LOCAL_PICKUP_RATE_ID)) {
|
|
727
|
+
var _ref7;
|
|
728
|
+
const { pickupLocations } = await readLocalFulfillmentStore({
|
|
729
|
+
hostRef,
|
|
730
|
+
storeSettings: (_ref7 = storeSettings.data == null ? void 0 : storeSettings.data.call(storeSettings)) != null ? _ref7 : null,
|
|
731
|
+
host: null
|
|
732
|
+
});
|
|
733
|
+
if (pickupLocations.length) {
|
|
734
|
+
shippingPlan.options = shippingPlan.options.filter((option)=>option.rateId !== CommerceModel.LOCAL_PICKUP_RATE_ID);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
684
737
|
if (shippingPlan.needsPostalCode) {
|
|
685
738
|
await releaseCouponSlot();
|
|
686
739
|
await releaseStock();
|
|
@@ -769,8 +822,8 @@ import { ensureCheckoutDomain } from "./payment-method-domains.js";
|
|
|
769
822
|
// out on `subscription_data` further down, and `feeCents` here is what
|
|
770
823
|
// that percent takes of the opening cycle's goods — the figure
|
|
771
824
|
// `metadata[feeCents]` records as what Aglyn keeps.
|
|
772
|
-
const subscriptionFeePercent = isSubscription ? Aglyn.resolveSubscriptionFeePercent(ownerOrg.org, (
|
|
773
|
-
const feeCents = isSubscription ? subscriptionFeePercent > 0 ? Math.max(1, Math.round(amountCents * subscriptionFeePercent / 100)) : 0 : Aglyn.resolveTransactionFeeCents(ownerOrg.org, (
|
|
825
|
+
const subscriptionFeePercent = isSubscription ? Aglyn.resolveSubscriptionFeePercent(ownerOrg.org, (_lifted_type2 = lifted.type) != null ? _lifted_type2 : 'physical', amountCents) : 0;
|
|
826
|
+
const feeCents = isSubscription ? subscriptionFeePercent > 0 ? Math.max(1, Math.round(amountCents * subscriptionFeePercent / 100)) : 0 : Aglyn.resolveTransactionFeeCents(ownerOrg.org, (_lifted_type3 = lifted.type) != null ? _lifted_type3 : 'physical', amountCents, amountCents + taxCents + shippingCeilingCents);
|
|
774
827
|
// The unit price Stripe actually charges — the coupon is priced INTO it
|
|
775
828
|
// rather than sent as a Stripe discount, which is why `amount_discount` is
|
|
776
829
|
// 0 on every buy-now session and the discount has to ride the metadata
|
|
@@ -922,6 +975,10 @@ import { ensureCheckoutDomain } from "./payment-method-domains.js";
|
|
|
922
975
|
// to the one this handler built before shipping existed on it. Buy-now has
|
|
923
976
|
// never collected an address, so declaring one unconditionally would put a
|
|
924
977
|
// shipping form in front of every shopper on a store that charges none.
|
|
978
|
+
// How the order reaches the buyer (AGL-3624): the webhook routes it.
|
|
979
|
+
if (localPlan.kind === 'pickup' || localPlan.kind === 'local_delivery') {
|
|
980
|
+
appendLocalFulfillmentMetadata(params, localPlan.metadata);
|
|
981
|
+
}
|
|
925
982
|
if (shippingPlan.options.length > 0) {
|
|
926
983
|
CommerceModel.appendShippingAddressCollectionParams(params, shippingPlan.countries);
|
|
927
984
|
CommerceModel.appendCheckoutShippingParams(params, // Free shipping is a zeroed rate, never a coupon — see the cart's note
|
|
@@ -947,7 +1004,7 @@ import { ensureCheckoutDomain } from "./payment-method-domains.js";
|
|
|
947
1004
|
//
|
|
948
1005
|
// Off unless the flag is on for this org AND a publishable key exists, so
|
|
949
1006
|
// the default remains the redirect — see `resolveNativeCheckoutMode`.
|
|
950
|
-
const nativeMode = await resolveNativeCheckoutMode(String((
|
|
1007
|
+
const nativeMode = await resolveNativeCheckoutMode(String((_ref4 = ownerOrg == null ? void 0 : (_ownerOrg_org2 = ownerOrg.org) == null ? void 0 : _ownerOrg_org2.id) != null ? _ref4 : ''));
|
|
951
1008
|
if (nativeMode.native) {
|
|
952
1009
|
applyNativeCheckoutParams(params, `${backUrl}${separator}order=success&session_id={CHECKOUT_SESSION_ID}`);
|
|
953
1010
|
}
|