@aglyn/plugins-commerce 1.0.0-beta.291 → 1.0.0-beta.293
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/package.json +13 -13
- package/src/lib/components/cart-fulfillment.d.ts +3 -0
- package/src/lib/components/cart-fulfillment.js.map +1 -1
- package/src/lib/components/cart.js +14 -4
- package/src/lib/components/cart.js.map +1 -1
- package/src/lib/components/console/host-quotes-card.component.js +11 -9
- package/src/lib/components/console/host-quotes-card.component.js.map +1 -1
- package/src/lib/components/console/invoice-send.component.js +1 -1
- package/src/lib/components/console/invoice-send.component.js.map +1 -1
- package/src/lib/components/console/pos/pos-item-dialog.component.js +3 -2
- package/src/lib/components/console/pos/pos-item-dialog.component.js.map +1 -1
- package/src/lib/components/console/pos-kiosk-page.component.js +2 -1
- package/src/lib/components/console/pos-kiosk-page.component.js.map +1 -1
- package/src/lib/components/console/pos-page.component.js +1 -1
- package/src/lib/components/console/pos-page.component.js.map +1 -1
- package/src/lib/components/console/product-add-ons-fields.component.d.ts +41 -0
- package/src/lib/components/console/product-add-ons-fields.component.js +356 -0
- package/src/lib/components/console/product-add-ons-fields.component.js.map +1 -0
- package/src/lib/components/console/product-editor-dialog.component.js +31 -5
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/product-register-fields.component.d.ts +4 -5
- package/src/lib/components/console/product-register-fields.component.js +8 -197
- package/src/lib/components/console/product-register-fields.component.js.map +1 -1
- package/src/lib/components/console/products-hub-zone.component.js +6 -0
- package/src/lib/components/console/products-hub-zone.component.js.map +1 -1
- package/src/lib/components/console/quote-pay-link.component.d.ts +8 -1
- package/src/lib/components/console/quote-pay-link.component.js +29 -3
- package/src/lib/components/console/quote-pay-link.component.js.map +1 -1
- package/src/lib/components/console/quote-send.component.js +1 -4
- package/src/lib/components/console/quote-send.component.js.map +1 -1
- package/src/lib/components/product-add-ons.d.ts +28 -0
- package/src/lib/components/product-add-ons.js +136 -0
- package/src/lib/components/product-add-ons.js.map +1 -0
- package/src/lib/components/product-detail.d.ts +16 -0
- package/src/lib/components/product-detail.js +112 -19
- package/src/lib/components/product-detail.js.map +1 -1
- package/src/lib/components/product-grid.d.ts +8 -2
- package/src/lib/components/product-grid.js +6 -1
- package/src/lib/components/product-grid.js.map +1 -1
- package/src/lib/declarations.console-server.js +15 -0
- package/src/lib/declarations.console-server.js.map +1 -1
- package/src/lib/model/commerce-cart.d.ts +15 -3
- package/src/lib/model/commerce-cart.js +10 -3
- package/src/lib/model/commerce-cart.js.map +1 -1
- package/src/lib/model/commerce-invoices.d.ts +7 -0
- package/src/lib/model/commerce-invoices.js +11 -0
- package/src/lib/model/commerce-invoices.js.map +1 -1
- package/src/lib/model/commerce-orders.d.ts +6 -3
- package/src/lib/model/commerce-orders.js +3 -0
- package/src/lib/model/commerce-orders.js.map +1 -1
- package/src/lib/model/commerce-pos-kiosk.d.ts +1 -1
- package/src/lib/model/commerce-pos-kiosk.js +3 -2
- package/src/lib/model/commerce-pos-kiosk.js.map +1 -1
- package/src/lib/model/commerce-quote-email.d.ts +14 -0
- package/src/lib/model/commerce-quote-email.js +25 -9
- package/src/lib/model/commerce-quote-email.js.map +1 -1
- package/src/lib/model/commerce-quote-proofs.d.ts +7 -0
- package/src/lib/model/commerce-quote-proofs.js +15 -0
- package/src/lib/model/commerce-quote-proofs.js.map +1 -1
- package/src/lib/model/commerce-quotes.js +1 -1
- package/src/lib/model/commerce-quotes.js.map +1 -1
- package/src/lib/model/commerce.d.ts +18 -0
- package/src/lib/model/commerce.js +26 -0
- package/src/lib/model/commerce.js.map +1 -1
- package/src/lib/model/product-modifiers.d.ts +74 -4
- package/src/lib/model/product-modifiers.js +131 -10
- package/src/lib/model/product-modifiers.js.map +1 -1
- package/src/lib/server/billing-webhook.js +64 -43
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/cart-checkout.js +14 -2
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/cart.d.ts +5 -0
- package/src/lib/server/cart.js +41 -4
- package/src/lib/server/cart.js.map +1 -1
- package/src/lib/server/catalog.d.ts +6 -0
- package/src/lib/server/catalog.js +2 -0
- package/src/lib/server/catalog.js.map +1 -1
- package/src/lib/server/checkout-extras.js +3 -1
- package/src/lib/server/checkout-extras.js.map +1 -1
- package/src/lib/server/checkout.js +28 -3
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/local-fulfillment.js +5 -1
- package/src/lib/server/local-fulfillment.js.map +1 -1
- package/src/lib/server/pos-offline-sync.js +1 -1
- package/src/lib/server/pos-offline-sync.js.map +1 -1
- package/src/lib/server/pos-order.js +1 -1
- package/src/lib/server/pos-order.js.map +1 -1
- package/src/lib/server/product.d.ts +26 -0
- package/src/lib/server/product.js +18 -0
- package/src/lib/server/product.js.map +1 -1
- package/src/lib/server/products-write.js +5 -2
- package/src/lib/server/products-write.js.map +1 -1
- package/src/lib/server/quote-email.js.map +1 -1
- package/src/lib/server/quote-link-token.d.ts +4 -2
- package/src/lib/server/quote-link-token.js +4 -2
- package/src/lib/server/quote-link-token.js.map +1 -1
- package/src/lib/server/quote-pay.d.ts +5 -1
- package/src/lib/server/quote-pay.js +5 -2
- package/src/lib/server/quote-pay.js.map +1 -1
- package/src/lib/server/quote-proofs.d.ts +9 -1
- package/src/lib/server/quote-proofs.js +92 -23
- package/src/lib/server/quote-proofs.js.map +1 -1
- package/src/lib/server/quote-upload-keys.d.ts +50 -0
- package/src/lib/server/quote-upload-keys.js +86 -0
- package/src/lib/server/quote-upload-keys.js.map +1 -0
- package/src/lib/server/quote-upload-sweep.d.ts +30 -0
- package/src/lib/server/quote-upload-sweep.js +117 -0
- package/src/lib/server/quote-upload-sweep.js.map +1 -0
- package/src/lib/utils/add-to-cart.d.ts +8 -0
- package/src/lib/utils/add-to-cart.js +10 -2
- package/src/lib/utils/add-to-cart.js.map +1 -1
- package/src/lib/utils/quote-email-content.d.ts +1 -0
- package/src/lib/utils/quote-email-content.js +7 -4
- package/src/lib/utils/quote-email-content.js.map +1 -1
|
@@ -461,6 +461,8 @@ const money = (cents)=>CommerceModel.formatOrderMoney(cents, 'USD');
|
|
|
461
461
|
productId: buyNowProductId
|
|
462
462
|
}, typeof body.variantId === 'string' && body.variantId ? {
|
|
463
463
|
variantId: body.variantId
|
|
464
|
+
} : {}, Array.isArray(body.modifiers) ? {
|
|
465
|
+
modifiers: CommerceModel.modifierSelectionFrom(body.modifiers)
|
|
464
466
|
} : {}, {
|
|
465
467
|
quantity: Math.min(99, Math.max(1, Math.round(Number(body.quantity) || 1)))
|
|
466
468
|
})
|
|
@@ -487,7 +489,9 @@ const money = (cents)=>CommerceModel.formatOrderMoney(cents, 'USD');
|
|
|
487
489
|
if (!(snapshot == null ? void 0 : snapshot.exists)) continue;
|
|
488
490
|
const product = CommerceModel.liftLegacyProduct(snapshot.data());
|
|
489
491
|
const variant = line.variantId ? product.variants.find((item)=>item.id === line.variantId) : product.variants[0];
|
|
490
|
-
|
|
492
|
+
// With its add-ons (AGL-4100), as checkout prices the line.
|
|
493
|
+
const chosen = CommerceModel.resolveWebsiteLineModifiers(product, line.modifiers, variant == null ? void 0 : variant.id);
|
|
494
|
+
itemsCents += (Math.round(Number((_ref1 = variant == null ? void 0 : variant.priceUsd) != null ? _ref1 : 0) * 100) + (chosen.ok ? chosen.extraCents : 0)) * (Number(line.quantity) || 0);
|
|
491
495
|
if (((_product_type = product.type) != null ? _product_type : 'physical') === 'physical') hasPhysicalLine = true;
|
|
492
496
|
}
|
|
493
497
|
if (!hasPhysicalLine) return res.status(200).json(nothing);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/local-fulfillment.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 { resolveSiteTimeZone } from '@aglyn/aglyn/app-utils/collection-entry-date'\nimport { normalizePhone } from '@aglyn/aglyn/foundation/definitions/contact.types'\nimport { pluginSmsMessaging } from '@aglyn/aglyn/plugin-manager/plugin-sms-messaging'\nimport { pluginShippingRateQuoter } from '@aglyn/aglyn/plugin-manager/plugin-shipping-rates'\nimport type { PluginApiHandler } from '@aglyn/aglyn/server'\nimport * as Aglyn from '@aglyn/aglyn/server'\nimport { consumeRateLimit, firebaseAdmin, getOrgForHost } 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 * Buy online, pick up in store, and local delivery at checkout (AGL-3624).\n *\n * The buyer chooses at the CART, before payment — the only place the store\n * still controls what is offered: a Stripe Checkout Session prices shipping\n * from the options it is created with and asks nothing back. So the cart\n * declares one of three ways, and this module turns the declaration into the\n * session it needs, or refuses it in words the cart can show:\n *\n * - `pickup` at one location: no shipping charged, no address collected,\n * the stock reserved at that location, and the order routed there.\n * - `local_delivery`: the store's OWN driver (not a marketplace courier —\n * that is the delivery-apps plugin, which never reads this). The postal\n * code — or, for a distance zone, the address — picks a zone, the zone\n * prices the fee and enforces its minimum, and the buyer books a window.\n * The session carries the fee as its one shipping option and collects an\n * address only in the store's country.\n * - `shipping`: everything as it was.\n *\n * A DECLARATION IS A REQUEST, NEVER A PRICE. The cart sends a location id, a\n * postal code and a window; the fee, the minimum and whether the window is\n * still open are all decided here from the store's own settings, on every\n * attempt.\n *\n * DISTANCE ZONES need both ends on a map, and the repository has no geocoder\n * of its own: the destination is placed by the shipping plugin's address\n * check (core's `core.shipping-rate-quoter` seam) when its provider returns a\n * position. Where none does, a distance zone matches nothing and the buyer is\n * told the store does not deliver there; postal-code zones need nothing.\n */\n\n/** What the cart declares. */\nexport interface LocalFulfillmentRequest {\n method: CommerceModel.OrderFulfillmentMethod\n locationId?: string\n postalCode?: string\n windowStartMs?: number\n /** The street address, asked only when the store has distance zones. */\n address?: { line1?: string; line2?: string; city?: string; state?: string; postalCode?: string }\n /**\n * The mobile number the buyer gave AT THE CART for texts about this pickup\n * or delivery (AGL-3624), in E.164. The only phone an online order's texts\n * go to: asked for that purpose, never taken from an address.\n */\n textPhone?: string\n}\n\n/** Reads the cart's declaration; `null` when it made none (a shipped order). */\nexport function readLocalFulfillmentRequest(value: unknown): LocalFulfillmentRequest | null {\n if (!value || typeof value !== 'object') return null\n const raw = value as Record<string, unknown>\n const method = raw['method']\n if (method !== 'pickup' && method !== 'local_delivery') return null\n const text = (input: unknown, max: number) => String(input ?? '').trim().slice(0, max)\n const request: LocalFulfillmentRequest = { method }\n const phone = normalizePhone(text(raw['textPhone'], 32))\n if (phone) request.textPhone = phone\n if (method === 'pickup') {\n request.locationId = text(raw['locationId'], 120)\n return request\n }\n const postalCode = text(raw['postalCode'], 16)\n if (postalCode) request.postalCode = postalCode\n const start = Math.round(Number(raw['windowStartMs']))\n if (Number.isFinite(start) && start > 0) request.windowStartMs = start\n const address = raw['address']\n if (address && typeof address === 'object') {\n const parts = address as Record<string, unknown>\n request.address = {\n line1: text(parts['line1'], 120),\n line2: text(parts['line2'], 120),\n city: text(parts['city'], 80),\n state: text(parts['state'], 40),\n postalCode: text(parts['postalCode'], 16) || postalCode,\n }\n }\n return request\n}\n\n/** The store's pickup locations and delivery settings, read once per attempt. */\nexport interface LocalFulfillmentStore {\n pickupLocations: CommerceModel.PickupLocationOption[]\n delivery: CommerceModel.LocalDeliverySettings\n timeZone: string\n}\n\n/** Reads what checkout and the cart need. Never throws: a failed read offers nothing. */\nexport async function readLocalFulfillmentStore(input: {\n hostRef: any\n /** `settings/store` as already read, to save a read. */\n storeSettings?: Record<string, unknown> | null\n org?: { timeZone?: string } | null\n host?: { timeZone?: string } | null\n}): Promise<LocalFulfillmentStore> {\n const { hostRef } = input\n const [locationsSnapshot, settings, host] = await Promise.all([\n hostRef\n .collection('locations')\n .limit(CommerceModel.PICKUP_LOCATIONS_MAX)\n .get()\n .catch(() => null),\n input.storeSettings !== undefined\n ? Promise.resolve(input.storeSettings)\n : hostRef\n .collection('settings')\n .doc('store')\n .get()\n .then((snapshot: any) => (snapshot?.data?.() ?? null) as Record<string, unknown> | null)\n .catch(() => null),\n input.host !== undefined\n ? Promise.resolve(input.host)\n : hostRef\n .get()\n .then((snapshot: any) => (snapshot?.data?.() ?? null) as { timeZone?: string } | null)\n .catch(() => null),\n ])\n const locations = (locationsSnapshot?.docs ?? []).map((snapshot: any) => ({\n ...(snapshot.data() as CommerceModel.InventoryLocation),\n id: String(snapshot.id),\n }))\n return {\n pickupLocations: CommerceModel.pickupLocationOptions(locations),\n delivery: CommerceModel.normalizeLocalDeliverySettings(settings?.['localDelivery']),\n timeZone: resolveSiteTimeZone(input.org ?? null, host ?? null),\n }\n}\n\n/** How long checkout waits for an address to be placed on a map. */\nexport const LOCAL_DELIVERY_GEOCODE_TIMEOUT_MS = 3_000\n\n/**\n * Places an address on the map through the shipping plugin's address check,\n * or answers `null`: no plugin, not available on this site, no position in\n * the answer, or slower than the timeout. Never throws.\n */\nexport async function placeDeliveryAddress(\n hostId: string,\n address: { line1?: string; line2?: string; city?: string; state?: string; postalCode?: string; country: string },\n): Promise<CommerceModel.LocalDeliveryCoordinates | null> {\n if (!address.line1 || !address.postalCode) return null\n const quoter = pluginShippingRateQuoter()\n if (!quoter?.validateAddress) return null\n let timer: ReturnType<typeof setTimeout> | undefined\n try {\n const placed = await Promise.race([\n (async () => {\n if (!(await quoter.available(hostId))) return null\n const check = await quoter.validateAddress?.(hostId, {\n line1: address.line1,\n ...(address.line2 ? { line2: address.line2 } : {}),\n ...(address.city ? { city: address.city } : {}),\n ...(address.state ? { state: address.state } : {}),\n postalCode: address.postalCode,\n country: address.country,\n } as never)\n return check?.verdict === 'invalid' ? null : (check?.coordinates ?? null)\n })().catch(() => null),\n new Promise<null>((resolve) => {\n timer = setTimeout(() => resolve(null), LOCAL_DELIVERY_GEOCODE_TIMEOUT_MS)\n }),\n ])\n return placed ?? null\n } finally {\n if (timer) clearTimeout(timer)\n }\n}\n\n/** The shipping option id a local delivery fee is declared under. */\nexport const LOCAL_DELIVERY_RATE_ID = 'local-delivery'\n\nexport type LocalFulfillmentPlan =\n | { kind: 'shipping' }\n | {\n kind: 'refusal'\n status: 400 | 409\n error: string\n /** What the cart should re-ask: the options changed under the buyer. */\n changed?: 'pickup' | 'delivery'\n }\n | {\n kind: 'pickup'\n location: CommerceModel.PickupLocationOption\n metadata: Record<string, string>\n }\n | {\n kind: 'local_delivery'\n zone: CommerceModel.LocalDeliveryZone\n window: CommerceModel.LocalDeliveryWindow\n feeCents: number\n country: string\n locationId?: string\n option: CommerceModel.ResolvedShippingRate\n metadata: Record<string, string>\n }\n\nconst money = (cents: number) => CommerceModel.formatOrderMoney(cents, 'USD')\n\n/**\n * Turns the cart's declaration into what the session needs, or a refusal.\n * Taken BEFORE any reservation or Stripe call, so a refusal costs nothing\n * and the buyer's same button works once they choose again.\n */\nexport async function planLocalFulfillment(input: {\n hostId: string\n request: LocalFulfillmentRequest | null\n /** The goods, pre-discount — what a minimum and a free-over threshold read. */\n itemsCents: number\n hasPhysicalLine: boolean\n store: LocalFulfillmentStore\n nowMs?: number\n}): Promise<LocalFulfillmentPlan> {\n const { request, store } = input\n // A cart of downloads is not collected or driven anywhere.\n if (!request || request.method === 'shipping' || !input.hasPhysicalLine) return { kind: 'shipping' }\n const nowMs = input.nowMs ?? Date.now()\n\n if (request.method === 'pickup') {\n const location = store.pickupLocations.find((entry) => entry.id === request.locationId)\n if (!location) {\n return {\n kind: 'refusal',\n status: 409,\n error: 'That pickup location is no longer available. Choose another.',\n changed: 'pickup',\n }\n }\n return {\n kind: 'pickup',\n location,\n metadata: {\n fulfillment: 'pickup',\n pickupLocationId: location.id,\n ...(request.textPhone ? { orderTextPhone: request.textPhone } : {}),\n },\n }\n }\n\n const delivery = store.delivery\n if (!CommerceModel.localDeliveryOffered(delivery)) {\n return {\n kind: 'refusal',\n status: 409,\n error: 'Local delivery is not available right now. Choose shipping or pickup.',\n changed: 'delivery',\n }\n }\n const country = String(delivery.country)\n const postalCode = CommerceModel.normalizePostcode(\n request.postalCode ?? request.address?.postalCode,\n country,\n )\n if (!postalCode) {\n return { kind: 'refusal', status: 400, error: 'Enter your postal code for delivery.' }\n }\n let zone = CommerceModel.matchLocalDeliveryZone(delivery, { country, postalCode })\n if (!zone && CommerceModel.localDeliveryHasRadiusZones(delivery)) {\n if (!request.address?.line1) {\n return { kind: 'refusal', status: 400, error: 'Enter your street address for delivery.' }\n }\n const coordinates = await placeDeliveryAddress(input.hostId, {\n ...request.address,\n postalCode,\n country,\n })\n zone = CommerceModel.matchLocalDeliveryZone(delivery, { country, postalCode, coordinates })\n }\n if (!zone) {\n return {\n kind: 'refusal',\n status: 409,\n error: `We don’t deliver to ${postalCode}. Choose shipping or pickup instead.`,\n }\n }\n const shortfall = CommerceModel.localDeliveryMinimumShortfall(zone, input.itemsCents)\n if (shortfall > 0) {\n return {\n kind: 'refusal',\n status: 409,\n error:\n `Delivery to ${postalCode} needs an order of at least ${money(zone.minimumCents ?? 0)}. ` +\n `Add ${money(shortfall)} more, or choose shipping or pickup.`,\n }\n }\n const window = CommerceModel.findLocalDeliveryWindow(delivery, request.windowStartMs, {\n nowMs,\n timeZone: store.timeZone,\n })\n if (!window) {\n return {\n kind: 'refusal',\n status: 409,\n error: 'That delivery time is no longer available. Choose another.',\n changed: 'delivery',\n }\n }\n const feeCents = CommerceModel.localDeliveryFeeCents(zone, input.itemsCents)\n const label = CommerceModel.formatLocalDeliveryWindow(window, store.timeZone)\n return {\n kind: 'local_delivery',\n zone,\n window,\n feeCents,\n country,\n ...(delivery.locationId ? { locationId: delivery.locationId } : {}),\n option: {\n rateId: LOCAL_DELIVERY_RATE_ID,\n name: `Local delivery — ${label}`.slice(0, 100),\n amountCents: feeCents,\n },\n metadata: {\n fulfillment: 'local_delivery',\n deliveryZoneId: zone.id,\n deliveryWindowStartMs: String(window.startMs),\n deliveryWindowEndMs: String(window.endMs),\n deliveryPostalCode: postalCode,\n deliveryFeeCents: String(feeCents),\n ...(delivery.locationId ? { deliveryLocationId: delivery.locationId } : {}),\n ...(request.textPhone ? { orderTextPhone: request.textPhone } : {}),\n },\n }\n}\n\n/** Sets a plan's metadata on a session's form body. */\nexport function appendLocalFulfillmentMetadata(params: URLSearchParams, metadata: Record<string, string>): void {\n for (const [key, value] of Object.entries(metadata)) {\n params.set(`metadata[${key}]`, String(value).slice(0, 480))\n }\n}\n\n/** The location a plan takes its stock from, if any. */\nexport function localFulfillmentLocationId(plan: LocalFulfillmentPlan): string | undefined {\n if (plan.kind === 'pickup') return plan.location.id\n if (plan.kind === 'local_delivery') return plan.locationId\n return undefined\n}\n\n/** A stock refusal for a plan that reserves at one location (AGL-3624). */\nexport function localStockRefusalMessage(plan: LocalFulfillmentPlan, productName: string): string {\n const what = productName ? `\"${productName}\"` : 'An item in your cart'\n if (plan.kind === 'pickup') {\n return `${what} is not in stock at ${plan.location.name}. Choose another location, or have it shipped.`\n }\n return `${what} is not in stock for local delivery. Choose pickup at another location, or have it shipped.`\n}\n\n// ---------------------------------------------------------------------------\n// The order, as the webhook records it\n// ---------------------------------------------------------------------------\n\n/** What a paid session's declaration becomes on its order. */\nexport interface OrderLocalFulfillment {\n fields: {\n fulfillmentMethod: CommerceModel.OrderFulfillmentMethod\n pickup?: CommerceModel.OrderPickup\n localDelivery?: CommerceModel.OrderLocalDelivery\n locationId?: string\n /** The number the buyer asked to be texted at, at the cart. */\n customerPhone?: string\n } & CommerceModel.OrderLocalFulfillmentListFields\n /** The line the receipt adds, so the buyer knows where and when. */\n summary: string\n /** The address entered at payment is outside the zone the fee was charged for. */\n outsideZone: boolean\n}\n\n/**\n * Reads a paid session's `fulfillment` metadata into order fields, or `null`\n * for a shipped order. The location and zone are re-read so the order keeps\n * their names as they were when the buyer paid.\n */\nexport async function orderLocalFulfillmentFromSession(input: {\n hostId: string\n hostRef: any\n metadata: Record<string, unknown> | null | undefined\n /** The address the buyer entered at payment, when the session collected one. */\n shippingAddress?: { postalCode?: string | null; country?: string | null } | null\n createdAtMs: number\n}): Promise<OrderLocalFulfillment | null> {\n const metadata = input.metadata ?? {}\n const method = metadata['fulfillment']\n if (method !== 'pickup' && method !== 'local_delivery') return null\n const textPhone = normalizePhone(String(metadata['orderTextPhone'] ?? ''))\n const phone = textPhone ? { customerPhone: textPhone } : {}\n const org = await getOrgForHost(input.hostId).catch(() => null)\n const store = await readLocalFulfillmentStore({\n hostRef: input.hostRef,\n org: (org?.org ?? null) as { timeZone?: string } | null,\n })\n\n if (method === 'pickup') {\n const locationId = String(metadata['pickupLocationId'] ?? '')\n let location = store.pickupLocations.find((entry) => entry.id === locationId)\n // A location renamed or switched off after the buyer paid: the order\n // still goes there, under the name the location doc now has.\n if (!location && locationId) {\n const snapshot = await input.hostRef\n .collection('locations')\n .doc(locationId)\n .get()\n .catch(() => null)\n const data = (snapshot?.exists ? snapshot.data() : null) as CommerceModel.InventoryLocation | null\n location = {\n id: locationId,\n name: String(data?.name || 'the store'),\n ...(data\n ? { address: CommerceModel.formatPostalAddressLine(data.postalAddress, data.address) || undefined }\n : {}),\n }\n }\n if (!location) return null\n const pickup: CommerceModel.OrderPickup = {\n locationId: location.id,\n locationName: location.name,\n ...(location.address ? { address: location.address } : {}),\n ...(location.instructions ? { instructions: location.instructions } : {}),\n ...(location.hours ? { hours: location.hours } : {}),\n status: 'preparing',\n updatedAtMs: input.createdAtMs,\n }\n const order = { fulfillmentMethod: 'pickup' as const, pickup, createdAtMs: input.createdAtMs }\n const list = CommerceModel.orderLocalFulfillmentListFields(order) as CommerceModel.OrderLocalFulfillmentListFields\n const ready = CommerceModel.pickupReadyWithinLabel(location.readyWithinMinutes)\n return {\n fields: { fulfillmentMethod: 'pickup', pickup, locationId: location.id, ...phone, ...list },\n summary: [\n `Pickup at ${location.name}${location.address ? `, ${location.address}` : ''}.`,\n `We’ll let you know when it’s ready${ready ? ` (${ready.toLowerCase()})` : ''}.`,\n location.instructions ?? '',\n ]\n .filter(Boolean)\n .join(' '),\n outsideZone: false,\n }\n }\n\n const zoneId = String(metadata['deliveryZoneId'] ?? '')\n const zone = (store.delivery.zones ?? []).find((entry) => entry.id === zoneId)\n const windowStartMs = Number(metadata['deliveryWindowStartMs']) || 0\n const windowEndMs = Number(metadata['deliveryWindowEndMs']) || windowStartMs\n const declared = String(metadata['deliveryPostalCode'] ?? '')\n const country = store.delivery.country ?? String(input.shippingAddress?.country ?? '')\n const entered = CommerceModel.normalizePostcode(input.shippingAddress?.postalCode, country)\n // The fee was charged for the code the buyer declared. The address they\n // then typed at payment must be in a zone too — a zone of ANY fee, since\n // the store delivers there — or the store is told.\n const outsideZone = Boolean(\n entered &&\n entered !== declared &&\n !CommerceModel.matchLocalDeliveryZone(store.delivery, { country, postalCode: entered }),\n )\n const windowLabel = windowStartMs\n ? CommerceModel.formatLocalDeliveryWindow({ startMs: windowStartMs, endMs: windowEndMs }, store.timeZone)\n : ''\n const locationId = String(metadata['deliveryLocationId'] ?? '') || undefined\n const localDelivery: CommerceModel.OrderLocalDelivery = {\n zoneId,\n zoneName: zone?.name ?? 'Local delivery',\n feeCents: Math.max(0, Math.round(Number(metadata['deliveryFeeCents']) || 0)),\n windowStartMs,\n windowEndMs,\n ...(windowLabel ? { windowLabel } : {}),\n ...(declared ? { postalCode: declared } : {}),\n ...(locationId ? { locationId } : {}),\n status: 'scheduled',\n ...(outsideZone ? { addressOutsideZone: true } : {}),\n updatedAtMs: input.createdAtMs,\n }\n const order = { fulfillmentMethod: 'local_delivery' as const, localDelivery, createdAtMs: input.createdAtMs }\n const list = CommerceModel.orderLocalFulfillmentListFields(order) as CommerceModel.OrderLocalFulfillmentListFields\n return {\n fields: {\n fulfillmentMethod: 'local_delivery',\n localDelivery,\n ...(locationId ? { locationId } : {}),\n ...phone,\n ...list,\n },\n summary: [\n `Local delivery${windowLabel ? `: ${windowLabel}` : ''}.`,\n store.delivery.instructions ?? '',\n ]\n .filter(Boolean)\n .join(' '),\n outsideZone,\n }\n}\n\n// ---------------------------------------------------------------------------\n// The cart's question: what can this basket be?\n// ---------------------------------------------------------------------------\n\n/** Distance zones place an address through a provider: a visitor may ask this often. */\nconst PLACE_LIMIT = { limit: 20, windowMs: 60 * 60 * 1000 }\n\n/**\n * `POST commerce/local-fulfillment-options` (AGL-3624): what the cart offers\n * besides shipping — the pickup locations and, with a postal code, the\n * delivery zone's fee, minimum and the windows open to book.\n *\n * Public, because a shopper asks it; it returns only what a storefront would\n * print anyway (location names, addresses, hours, fees). It reads the\n * visitor's own cart through the cart cookie and offers nothing for a\n * basket with nothing physical in it.\n */\nexport const localFulfillmentOptionsHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST' && req.method !== 'GET') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n let body: Record<string, any>\n try {\n body =\n req.method === 'POST'\n ? typeof req.body === 'string'\n ? JSON.parse(req.body || '{}')\n : (req.body ?? {})\n : ((req.query ?? {}) as Record<string, any>)\n } catch {\n return res.status(400).json({ error: 'Invalid request' })\n }\n const hostId = String(body.hostId ?? '')\n if (!isDocumentId(hostId)) return res.status(400).json({ error: 'Missing or invalid hostId' })\n const nothing = { pickup: [], delivery: null }\n try {\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const ownerOrg = await getOrgForHost(hostId).catch(() => null)\n if (!ownerOrg?.org || !Aglyn.checkEntitlement(ownerOrg.org as any, 'commerce')) {\n return res.status(200).json(nothing)\n }\n // The visitor's own basket decides whether there is anything to collect\n // — or, from a product's Buy button, that one product (AGL-3624).\n const buyNowProductId = typeof body.productId === 'string' ? body.productId : ''\n let cart: Pick<CommerceModel.HostCart, 'lines'>\n if (buyNowProductId) {\n if (!isDocumentId(buyNowProductId)) return res.status(400).json({ error: 'Invalid productId' })\n cart = {\n lines: [\n {\n productId: buyNowProductId,\n ...(typeof body.variantId === 'string' && body.variantId ? { variantId: body.variantId } : {}),\n quantity: Math.min(99, Math.max(1, Math.round(Number(body.quantity) || 1))),\n },\n ],\n }\n } else {\n const cartId = readCartId(req.cookies, hostId)\n const cartSnapshot = cartId ? await hostRef.collection('carts').doc(cartId).get().catch(() => null) : null\n cart = (cartSnapshot?.data?.() as CommerceModel.HostCart | undefined) ?? { lines: [] }\n }\n const productIds = [...new Set((cart.lines ?? []).map((line) => line.productId))].slice(0, 50)\n const products = await Promise.all(\n productIds.map((id) =>\n hostRef\n .collection('products')\n .doc(id)\n .get()\n .catch(() => null),\n ),\n )\n let itemsCents = 0\n let hasPhysicalLine = false\n for (const line of cart.lines ?? []) {\n const snapshot = products[productIds.indexOf(line.productId)]\n if (!snapshot?.exists) continue\n const product = CommerceModel.liftLegacyProduct(snapshot.data() as any)\n const variant = line.variantId\n ? product.variants.find((item) => item.id === line.variantId)\n : product.variants[0]\n itemsCents += Math.round(Number(variant?.priceUsd ?? 0) * 100) * (Number(line.quantity) || 0)\n if ((product.type ?? 'physical') === 'physical') hasPhysicalLine = true\n }\n if (!hasPhysicalLine) return res.status(200).json(nothing)\n\n const store = await readLocalFulfillmentStore({\n hostRef,\n org: ownerOrg.org as { timeZone?: string },\n })\n const nowMs = Date.now()\n let delivery: Record<string, unknown> | null = null\n if (CommerceModel.localDeliveryOffered(store.delivery)) {\n const settings = store.delivery\n const country = String(settings.country)\n const windows = CommerceModel.upcomingLocalDeliveryWindows(settings, { nowMs, timeZone: store.timeZone }).map(\n (window) => ({\n ...window,\n label: CommerceModel.formatLocalDeliveryWindow(window, store.timeZone),\n }),\n )\n const radius = CommerceModel.localDeliveryHasRadiusZones(settings)\n delivery = {\n country,\n ...(settings.instructions ? { instructions: settings.instructions } : {}),\n needsAddress: radius,\n windows,\n }\n const request = readLocalFulfillmentRequest({\n method: 'local_delivery',\n postalCode: body.postalCode,\n address: body.address,\n })\n const postalCode = CommerceModel.normalizePostcode(request?.postalCode ?? request?.address?.postalCode, country)\n if (postalCode) {\n let zone = CommerceModel.matchLocalDeliveryZone(settings, { country, postalCode })\n if (!zone && radius && request?.address?.line1) {\n const visitor = String(req.socket?.remoteAddress ?? '') || 'no-address'\n const allowed = await consumeRateLimit(`local-delivery:place:${hostId}:${visitor}`, PLACE_LIMIT)\n .then((result) => result.allowed)\n .catch(() => false)\n const coordinates = allowed\n ? await placeDeliveryAddress(hostId, { ...request.address, postalCode, country })\n : null\n zone = CommerceModel.matchLocalDeliveryZone(settings, { country, postalCode, coordinates })\n }\n delivery['quote'] = zone\n ? {\n zoneId: zone.id,\n zoneName: zone.name,\n feeCents: CommerceModel.localDeliveryFeeCents(zone, itemsCents),\n ...(zone.minimumCents ? { minimumCents: zone.minimumCents } : {}),\n ...(zone.freeOverCents ? { freeOverCents: zone.freeOverCents } : {}),\n shortfallCents: CommerceModel.localDeliveryMinimumShortfall(zone, itemsCents),\n }\n : { unavailable: `We don’t deliver to ${postalCode}.` }\n }\n }\n // Whether the cart may offer texts about the pickup or delivery: the\n // platform can send them and the store has not turned them off.\n const toggles = (await hostRef\n .collection('settings')\n .doc('store')\n .get()\n .then((snapshot: any) => snapshot?.get?.('buyerNotifications'))\n .catch(() => null)) as unknown\n const texts = Boolean(\n pluginSmsMessaging()?.isConfigured() && CommerceModel.buyerNotificationEnabled(toggles, 'texts'),\n )\n return res.status(200).json({ pickup: store.pickupLocations, delivery, texts })\n } catch (error) {\n console.error('[local-fulfillment] options failed', hostId, error)\n return res.status(200).json(nothing)\n }\n}\n"],"names":["resolveSiteTimeZone","normalizePhone","pluginSmsMessaging","pluginShippingRateQuoter","Aglyn","consumeRateLimit","firebaseAdmin","getOrgForHost","isDocumentId","CommerceModel","readCartId","readLocalFulfillmentRequest","value","raw","method","text","input","max","String","trim","slice","request","phone","textPhone","locationId","postalCode","start","Math","round","Number","isFinite","windowStartMs","address","parts","line1","line2","city","state","readLocalFulfillmentStore","hostRef","locationsSnapshot","settings","host","Promise","all","collection","limit","PICKUP_LOCATIONS_MAX","get","catch","storeSettings","undefined","resolve","doc","then","snapshot","data","locations","docs","map","id","pickupLocations","pickupLocationOptions","delivery","normalizeLocalDeliverySettings","timeZone","org","LOCAL_DELIVERY_GEOCODE_TIMEOUT_MS","placeDeliveryAddress","hostId","quoter","validateAddress","timer","placed","race","available","check","country","verdict","coordinates","setTimeout","clearTimeout","LOCAL_DELIVERY_RATE_ID","money","cents","formatOrderMoney","planLocalFulfillment","store","hasPhysicalLine","kind","nowMs","Date","now","location","find","entry","status","error","changed","metadata","fulfillment","pickupLocationId","orderTextPhone","localDeliveryOffered","normalizePostcode","zone","matchLocalDeliveryZone","localDeliveryHasRadiusZones","shortfall","localDeliveryMinimumShortfall","itemsCents","minimumCents","window","findLocalDeliveryWindow","feeCents","localDeliveryFeeCents","label","formatLocalDeliveryWindow","option","rateId","name","amountCents","deliveryZoneId","deliveryWindowStartMs","startMs","deliveryWindowEndMs","endMs","deliveryPostalCode","deliveryFeeCents","deliveryLocationId","appendLocalFulfillmentMetadata","params","key","Object","entries","set","localFulfillmentLocationId","plan","localStockRefusalMessage","productName","what","orderLocalFulfillmentFromSession","customerPhone","exists","formatPostalAddressLine","postalAddress","pickup","locationName","instructions","hours","updatedAtMs","createdAtMs","order","fulfillmentMethod","list","orderLocalFulfillmentListFields","ready","pickupReadyWithinLabel","readyWithinMinutes","fields","summary","toLowerCase","filter","Boolean","join","outsideZone","zoneId","zones","windowEndMs","declared","shippingAddress","entered","windowLabel","localDelivery","zoneName","addressOutsideZone","PLACE_LIMIT","windowMs","localFulfillmentOptionsHandler","req","res","body","json","JSON","parse","query","nothing","cart","firestore","app","ownerOrg","checkEntitlement","buyNowProductId","productId","lines","variantId","quantity","min","cartSnapshot","cartId","cookies","productIds","Set","line","products","product","indexOf","liftLegacyProduct","variant","variants","item","priceUsd","type","windows","upcomingLocalDeliveryWindows","radius","needsAddress","visitor","socket","remoteAddress","allowed","result","freeOverCents","shortfallCents","unavailable","toggles","texts","isConfigured","buyerNotificationEnabled","console"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/local-fulfillment.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 { resolveSiteTimeZone } from '@aglyn/aglyn/app-utils/collection-entry-date'\nimport { normalizePhone } from '@aglyn/aglyn/foundation/definitions/contact.types'\nimport { pluginSmsMessaging } from '@aglyn/aglyn/plugin-manager/plugin-sms-messaging'\nimport { pluginShippingRateQuoter } from '@aglyn/aglyn/plugin-manager/plugin-shipping-rates'\nimport type { PluginApiHandler } from '@aglyn/aglyn/server'\nimport * as Aglyn from '@aglyn/aglyn/server'\nimport { consumeRateLimit, firebaseAdmin, getOrgForHost } 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 * Buy online, pick up in store, and local delivery at checkout (AGL-3624).\n *\n * The buyer chooses at the CART, before payment — the only place the store\n * still controls what is offered: a Stripe Checkout Session prices shipping\n * from the options it is created with and asks nothing back. So the cart\n * declares one of three ways, and this module turns the declaration into the\n * session it needs, or refuses it in words the cart can show:\n *\n * - `pickup` at one location: no shipping charged, no address collected,\n * the stock reserved at that location, and the order routed there.\n * - `local_delivery`: the store's OWN driver (not a marketplace courier —\n * that is the delivery-apps plugin, which never reads this). The postal\n * code — or, for a distance zone, the address — picks a zone, the zone\n * prices the fee and enforces its minimum, and the buyer books a window.\n * The session carries the fee as its one shipping option and collects an\n * address only in the store's country.\n * - `shipping`: everything as it was.\n *\n * A DECLARATION IS A REQUEST, NEVER A PRICE. The cart sends a location id, a\n * postal code and a window; the fee, the minimum and whether the window is\n * still open are all decided here from the store's own settings, on every\n * attempt.\n *\n * DISTANCE ZONES need both ends on a map, and the repository has no geocoder\n * of its own: the destination is placed by the shipping plugin's address\n * check (core's `core.shipping-rate-quoter` seam) when its provider returns a\n * position. Where none does, a distance zone matches nothing and the buyer is\n * told the store does not deliver there; postal-code zones need nothing.\n */\n\n/** What the cart declares. */\nexport interface LocalFulfillmentRequest {\n method: CommerceModel.OrderFulfillmentMethod\n locationId?: string\n postalCode?: string\n windowStartMs?: number\n /** The street address, asked only when the store has distance zones. */\n address?: { line1?: string; line2?: string; city?: string; state?: string; postalCode?: string }\n /**\n * The mobile number the buyer gave AT THE CART for texts about this pickup\n * or delivery (AGL-3624), in E.164. The only phone an online order's texts\n * go to: asked for that purpose, never taken from an address.\n */\n textPhone?: string\n}\n\n/** Reads the cart's declaration; `null` when it made none (a shipped order). */\nexport function readLocalFulfillmentRequest(value: unknown): LocalFulfillmentRequest | null {\n if (!value || typeof value !== 'object') return null\n const raw = value as Record<string, unknown>\n const method = raw['method']\n if (method !== 'pickup' && method !== 'local_delivery') return null\n const text = (input: unknown, max: number) => String(input ?? '').trim().slice(0, max)\n const request: LocalFulfillmentRequest = { method }\n const phone = normalizePhone(text(raw['textPhone'], 32))\n if (phone) request.textPhone = phone\n if (method === 'pickup') {\n request.locationId = text(raw['locationId'], 120)\n return request\n }\n const postalCode = text(raw['postalCode'], 16)\n if (postalCode) request.postalCode = postalCode\n const start = Math.round(Number(raw['windowStartMs']))\n if (Number.isFinite(start) && start > 0) request.windowStartMs = start\n const address = raw['address']\n if (address && typeof address === 'object') {\n const parts = address as Record<string, unknown>\n request.address = {\n line1: text(parts['line1'], 120),\n line2: text(parts['line2'], 120),\n city: text(parts['city'], 80),\n state: text(parts['state'], 40),\n postalCode: text(parts['postalCode'], 16) || postalCode,\n }\n }\n return request\n}\n\n/** The store's pickup locations and delivery settings, read once per attempt. */\nexport interface LocalFulfillmentStore {\n pickupLocations: CommerceModel.PickupLocationOption[]\n delivery: CommerceModel.LocalDeliverySettings\n timeZone: string\n}\n\n/** Reads what checkout and the cart need. Never throws: a failed read offers nothing. */\nexport async function readLocalFulfillmentStore(input: {\n hostRef: any\n /** `settings/store` as already read, to save a read. */\n storeSettings?: Record<string, unknown> | null\n org?: { timeZone?: string } | null\n host?: { timeZone?: string } | null\n}): Promise<LocalFulfillmentStore> {\n const { hostRef } = input\n const [locationsSnapshot, settings, host] = await Promise.all([\n hostRef\n .collection('locations')\n .limit(CommerceModel.PICKUP_LOCATIONS_MAX)\n .get()\n .catch(() => null),\n input.storeSettings !== undefined\n ? Promise.resolve(input.storeSettings)\n : hostRef\n .collection('settings')\n .doc('store')\n .get()\n .then((snapshot: any) => (snapshot?.data?.() ?? null) as Record<string, unknown> | null)\n .catch(() => null),\n input.host !== undefined\n ? Promise.resolve(input.host)\n : hostRef\n .get()\n .then((snapshot: any) => (snapshot?.data?.() ?? null) as { timeZone?: string } | null)\n .catch(() => null),\n ])\n const locations = (locationsSnapshot?.docs ?? []).map((snapshot: any) => ({\n ...(snapshot.data() as CommerceModel.InventoryLocation),\n id: String(snapshot.id),\n }))\n return {\n pickupLocations: CommerceModel.pickupLocationOptions(locations),\n delivery: CommerceModel.normalizeLocalDeliverySettings(settings?.['localDelivery']),\n timeZone: resolveSiteTimeZone(input.org ?? null, host ?? null),\n }\n}\n\n/** How long checkout waits for an address to be placed on a map. */\nexport const LOCAL_DELIVERY_GEOCODE_TIMEOUT_MS = 3_000\n\n/**\n * Places an address on the map through the shipping plugin's address check,\n * or answers `null`: no plugin, not available on this site, no position in\n * the answer, or slower than the timeout. Never throws.\n */\nexport async function placeDeliveryAddress(\n hostId: string,\n address: { line1?: string; line2?: string; city?: string; state?: string; postalCode?: string; country: string },\n): Promise<CommerceModel.LocalDeliveryCoordinates | null> {\n if (!address.line1 || !address.postalCode) return null\n const quoter = pluginShippingRateQuoter()\n if (!quoter?.validateAddress) return null\n let timer: ReturnType<typeof setTimeout> | undefined\n try {\n const placed = await Promise.race([\n (async () => {\n if (!(await quoter.available(hostId))) return null\n const check = await quoter.validateAddress?.(hostId, {\n line1: address.line1,\n ...(address.line2 ? { line2: address.line2 } : {}),\n ...(address.city ? { city: address.city } : {}),\n ...(address.state ? { state: address.state } : {}),\n postalCode: address.postalCode,\n country: address.country,\n } as never)\n return check?.verdict === 'invalid' ? null : (check?.coordinates ?? null)\n })().catch(() => null),\n new Promise<null>((resolve) => {\n timer = setTimeout(() => resolve(null), LOCAL_DELIVERY_GEOCODE_TIMEOUT_MS)\n }),\n ])\n return placed ?? null\n } finally {\n if (timer) clearTimeout(timer)\n }\n}\n\n/** The shipping option id a local delivery fee is declared under. */\nexport const LOCAL_DELIVERY_RATE_ID = 'local-delivery'\n\nexport type LocalFulfillmentPlan =\n | { kind: 'shipping' }\n | {\n kind: 'refusal'\n status: 400 | 409\n error: string\n /** What the cart should re-ask: the options changed under the buyer. */\n changed?: 'pickup' | 'delivery'\n }\n | {\n kind: 'pickup'\n location: CommerceModel.PickupLocationOption\n metadata: Record<string, string>\n }\n | {\n kind: 'local_delivery'\n zone: CommerceModel.LocalDeliveryZone\n window: CommerceModel.LocalDeliveryWindow\n feeCents: number\n country: string\n locationId?: string\n option: CommerceModel.ResolvedShippingRate\n metadata: Record<string, string>\n }\n\nconst money = (cents: number) => CommerceModel.formatOrderMoney(cents, 'USD')\n\n/**\n * Turns the cart's declaration into what the session needs, or a refusal.\n * Taken BEFORE any reservation or Stripe call, so a refusal costs nothing\n * and the buyer's same button works once they choose again.\n */\nexport async function planLocalFulfillment(input: {\n hostId: string\n request: LocalFulfillmentRequest | null\n /** The goods, pre-discount — what a minimum and a free-over threshold read. */\n itemsCents: number\n hasPhysicalLine: boolean\n store: LocalFulfillmentStore\n nowMs?: number\n}): Promise<LocalFulfillmentPlan> {\n const { request, store } = input\n // A cart of downloads is not collected or driven anywhere.\n if (!request || request.method === 'shipping' || !input.hasPhysicalLine) return { kind: 'shipping' }\n const nowMs = input.nowMs ?? Date.now()\n\n if (request.method === 'pickup') {\n const location = store.pickupLocations.find((entry) => entry.id === request.locationId)\n if (!location) {\n return {\n kind: 'refusal',\n status: 409,\n error: 'That pickup location is no longer available. Choose another.',\n changed: 'pickup',\n }\n }\n return {\n kind: 'pickup',\n location,\n metadata: {\n fulfillment: 'pickup',\n pickupLocationId: location.id,\n ...(request.textPhone ? { orderTextPhone: request.textPhone } : {}),\n },\n }\n }\n\n const delivery = store.delivery\n if (!CommerceModel.localDeliveryOffered(delivery)) {\n return {\n kind: 'refusal',\n status: 409,\n error: 'Local delivery is not available right now. Choose shipping or pickup.',\n changed: 'delivery',\n }\n }\n const country = String(delivery.country)\n const postalCode = CommerceModel.normalizePostcode(\n request.postalCode ?? request.address?.postalCode,\n country,\n )\n if (!postalCode) {\n return { kind: 'refusal', status: 400, error: 'Enter your postal code for delivery.' }\n }\n let zone = CommerceModel.matchLocalDeliveryZone(delivery, { country, postalCode })\n if (!zone && CommerceModel.localDeliveryHasRadiusZones(delivery)) {\n if (!request.address?.line1) {\n return { kind: 'refusal', status: 400, error: 'Enter your street address for delivery.' }\n }\n const coordinates = await placeDeliveryAddress(input.hostId, {\n ...request.address,\n postalCode,\n country,\n })\n zone = CommerceModel.matchLocalDeliveryZone(delivery, { country, postalCode, coordinates })\n }\n if (!zone) {\n return {\n kind: 'refusal',\n status: 409,\n error: `We don’t deliver to ${postalCode}. Choose shipping or pickup instead.`,\n }\n }\n const shortfall = CommerceModel.localDeliveryMinimumShortfall(zone, input.itemsCents)\n if (shortfall > 0) {\n return {\n kind: 'refusal',\n status: 409,\n error:\n `Delivery to ${postalCode} needs an order of at least ${money(zone.minimumCents ?? 0)}. ` +\n `Add ${money(shortfall)} more, or choose shipping or pickup.`,\n }\n }\n const window = CommerceModel.findLocalDeliveryWindow(delivery, request.windowStartMs, {\n nowMs,\n timeZone: store.timeZone,\n })\n if (!window) {\n return {\n kind: 'refusal',\n status: 409,\n error: 'That delivery time is no longer available. Choose another.',\n changed: 'delivery',\n }\n }\n const feeCents = CommerceModel.localDeliveryFeeCents(zone, input.itemsCents)\n const label = CommerceModel.formatLocalDeliveryWindow(window, store.timeZone)\n return {\n kind: 'local_delivery',\n zone,\n window,\n feeCents,\n country,\n ...(delivery.locationId ? { locationId: delivery.locationId } : {}),\n option: {\n rateId: LOCAL_DELIVERY_RATE_ID,\n name: `Local delivery — ${label}`.slice(0, 100),\n amountCents: feeCents,\n },\n metadata: {\n fulfillment: 'local_delivery',\n deliveryZoneId: zone.id,\n deliveryWindowStartMs: String(window.startMs),\n deliveryWindowEndMs: String(window.endMs),\n deliveryPostalCode: postalCode,\n deliveryFeeCents: String(feeCents),\n ...(delivery.locationId ? { deliveryLocationId: delivery.locationId } : {}),\n ...(request.textPhone ? { orderTextPhone: request.textPhone } : {}),\n },\n }\n}\n\n/** Sets a plan's metadata on a session's form body. */\nexport function appendLocalFulfillmentMetadata(params: URLSearchParams, metadata: Record<string, string>): void {\n for (const [key, value] of Object.entries(metadata)) {\n params.set(`metadata[${key}]`, String(value).slice(0, 480))\n }\n}\n\n/** The location a plan takes its stock from, if any. */\nexport function localFulfillmentLocationId(plan: LocalFulfillmentPlan): string | undefined {\n if (plan.kind === 'pickup') return plan.location.id\n if (plan.kind === 'local_delivery') return plan.locationId\n return undefined\n}\n\n/** A stock refusal for a plan that reserves at one location (AGL-3624). */\nexport function localStockRefusalMessage(plan: LocalFulfillmentPlan, productName: string): string {\n const what = productName ? `\"${productName}\"` : 'An item in your cart'\n if (plan.kind === 'pickup') {\n return `${what} is not in stock at ${plan.location.name}. Choose another location, or have it shipped.`\n }\n return `${what} is not in stock for local delivery. Choose pickup at another location, or have it shipped.`\n}\n\n// ---------------------------------------------------------------------------\n// The order, as the webhook records it\n// ---------------------------------------------------------------------------\n\n/** What a paid session's declaration becomes on its order. */\nexport interface OrderLocalFulfillment {\n fields: {\n fulfillmentMethod: CommerceModel.OrderFulfillmentMethod\n pickup?: CommerceModel.OrderPickup\n localDelivery?: CommerceModel.OrderLocalDelivery\n locationId?: string\n /** The number the buyer asked to be texted at, at the cart. */\n customerPhone?: string\n } & CommerceModel.OrderLocalFulfillmentListFields\n /** The line the receipt adds, so the buyer knows where and when. */\n summary: string\n /** The address entered at payment is outside the zone the fee was charged for. */\n outsideZone: boolean\n}\n\n/**\n * Reads a paid session's `fulfillment` metadata into order fields, or `null`\n * for a shipped order. The location and zone are re-read so the order keeps\n * their names as they were when the buyer paid.\n */\nexport async function orderLocalFulfillmentFromSession(input: {\n hostId: string\n hostRef: any\n metadata: Record<string, unknown> | null | undefined\n /** The address the buyer entered at payment, when the session collected one. */\n shippingAddress?: { postalCode?: string | null; country?: string | null } | null\n createdAtMs: number\n}): Promise<OrderLocalFulfillment | null> {\n const metadata = input.metadata ?? {}\n const method = metadata['fulfillment']\n if (method !== 'pickup' && method !== 'local_delivery') return null\n const textPhone = normalizePhone(String(metadata['orderTextPhone'] ?? ''))\n const phone = textPhone ? { customerPhone: textPhone } : {}\n const org = await getOrgForHost(input.hostId).catch(() => null)\n const store = await readLocalFulfillmentStore({\n hostRef: input.hostRef,\n org: (org?.org ?? null) as { timeZone?: string } | null,\n })\n\n if (method === 'pickup') {\n const locationId = String(metadata['pickupLocationId'] ?? '')\n let location = store.pickupLocations.find((entry) => entry.id === locationId)\n // A location renamed or switched off after the buyer paid: the order\n // still goes there, under the name the location doc now has.\n if (!location && locationId) {\n const snapshot = await input.hostRef\n .collection('locations')\n .doc(locationId)\n .get()\n .catch(() => null)\n const data = (snapshot?.exists ? snapshot.data() : null) as CommerceModel.InventoryLocation | null\n location = {\n id: locationId,\n name: String(data?.name || 'the store'),\n ...(data\n ? { address: CommerceModel.formatPostalAddressLine(data.postalAddress, data.address) || undefined }\n : {}),\n }\n }\n if (!location) return null\n const pickup: CommerceModel.OrderPickup = {\n locationId: location.id,\n locationName: location.name,\n ...(location.address ? { address: location.address } : {}),\n ...(location.instructions ? { instructions: location.instructions } : {}),\n ...(location.hours ? { hours: location.hours } : {}),\n status: 'preparing',\n updatedAtMs: input.createdAtMs,\n }\n const order = { fulfillmentMethod: 'pickup' as const, pickup, createdAtMs: input.createdAtMs }\n const list = CommerceModel.orderLocalFulfillmentListFields(order) as CommerceModel.OrderLocalFulfillmentListFields\n const ready = CommerceModel.pickupReadyWithinLabel(location.readyWithinMinutes)\n return {\n fields: { fulfillmentMethod: 'pickup', pickup, locationId: location.id, ...phone, ...list },\n summary: [\n `Pickup at ${location.name}${location.address ? `, ${location.address}` : ''}.`,\n `We’ll let you know when it’s ready${ready ? ` (${ready.toLowerCase()})` : ''}.`,\n location.instructions ?? '',\n ]\n .filter(Boolean)\n .join(' '),\n outsideZone: false,\n }\n }\n\n const zoneId = String(metadata['deliveryZoneId'] ?? '')\n const zone = (store.delivery.zones ?? []).find((entry) => entry.id === zoneId)\n const windowStartMs = Number(metadata['deliveryWindowStartMs']) || 0\n const windowEndMs = Number(metadata['deliveryWindowEndMs']) || windowStartMs\n const declared = String(metadata['deliveryPostalCode'] ?? '')\n const country = store.delivery.country ?? String(input.shippingAddress?.country ?? '')\n const entered = CommerceModel.normalizePostcode(input.shippingAddress?.postalCode, country)\n // The fee was charged for the code the buyer declared. The address they\n // then typed at payment must be in a zone too — a zone of ANY fee, since\n // the store delivers there — or the store is told.\n const outsideZone = Boolean(\n entered &&\n entered !== declared &&\n !CommerceModel.matchLocalDeliveryZone(store.delivery, { country, postalCode: entered }),\n )\n const windowLabel = windowStartMs\n ? CommerceModel.formatLocalDeliveryWindow({ startMs: windowStartMs, endMs: windowEndMs }, store.timeZone)\n : ''\n const locationId = String(metadata['deliveryLocationId'] ?? '') || undefined\n const localDelivery: CommerceModel.OrderLocalDelivery = {\n zoneId,\n zoneName: zone?.name ?? 'Local delivery',\n feeCents: Math.max(0, Math.round(Number(metadata['deliveryFeeCents']) || 0)),\n windowStartMs,\n windowEndMs,\n ...(windowLabel ? { windowLabel } : {}),\n ...(declared ? { postalCode: declared } : {}),\n ...(locationId ? { locationId } : {}),\n status: 'scheduled',\n ...(outsideZone ? { addressOutsideZone: true } : {}),\n updatedAtMs: input.createdAtMs,\n }\n const order = { fulfillmentMethod: 'local_delivery' as const, localDelivery, createdAtMs: input.createdAtMs }\n const list = CommerceModel.orderLocalFulfillmentListFields(order) as CommerceModel.OrderLocalFulfillmentListFields\n return {\n fields: {\n fulfillmentMethod: 'local_delivery',\n localDelivery,\n ...(locationId ? { locationId } : {}),\n ...phone,\n ...list,\n },\n summary: [\n `Local delivery${windowLabel ? `: ${windowLabel}` : ''}.`,\n store.delivery.instructions ?? '',\n ]\n .filter(Boolean)\n .join(' '),\n outsideZone,\n }\n}\n\n// ---------------------------------------------------------------------------\n// The cart's question: what can this basket be?\n// ---------------------------------------------------------------------------\n\n/** Distance zones place an address through a provider: a visitor may ask this often. */\nconst PLACE_LIMIT = { limit: 20, windowMs: 60 * 60 * 1000 }\n\n/**\n * `POST commerce/local-fulfillment-options` (AGL-3624): what the cart offers\n * besides shipping — the pickup locations and, with a postal code, the\n * delivery zone's fee, minimum and the windows open to book.\n *\n * Public, because a shopper asks it; it returns only what a storefront would\n * print anyway (location names, addresses, hours, fees). It reads the\n * visitor's own cart through the cart cookie and offers nothing for a\n * basket with nothing physical in it.\n */\nexport const localFulfillmentOptionsHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST' && req.method !== 'GET') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n let body: Record<string, any>\n try {\n body =\n req.method === 'POST'\n ? typeof req.body === 'string'\n ? JSON.parse(req.body || '{}')\n : (req.body ?? {})\n : ((req.query ?? {}) as Record<string, any>)\n } catch {\n return res.status(400).json({ error: 'Invalid request' })\n }\n const hostId = String(body.hostId ?? '')\n if (!isDocumentId(hostId)) return res.status(400).json({ error: 'Missing or invalid hostId' })\n const nothing = { pickup: [], delivery: null }\n try {\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const ownerOrg = await getOrgForHost(hostId).catch(() => null)\n if (!ownerOrg?.org || !Aglyn.checkEntitlement(ownerOrg.org as any, 'commerce')) {\n return res.status(200).json(nothing)\n }\n // The visitor's own basket decides whether there is anything to collect\n // — or, from a product's Buy button, that one product (AGL-3624).\n const buyNowProductId = typeof body.productId === 'string' ? body.productId : ''\n let cart: Pick<CommerceModel.HostCart, 'lines'>\n if (buyNowProductId) {\n if (!isDocumentId(buyNowProductId)) return res.status(400).json({ error: 'Invalid productId' })\n cart = {\n lines: [\n {\n productId: buyNowProductId,\n ...(typeof body.variantId === 'string' && body.variantId ? { variantId: body.variantId } : {}),\n // The add-ons chosen on the page (AGL-4100), priced below.\n ...(Array.isArray(body.modifiers)\n ? { modifiers: CommerceModel.modifierSelectionFrom(body.modifiers) }\n : {}),\n quantity: Math.min(99, Math.max(1, Math.round(Number(body.quantity) || 1))),\n },\n ],\n }\n } else {\n const cartId = readCartId(req.cookies, hostId)\n const cartSnapshot = cartId ? await hostRef.collection('carts').doc(cartId).get().catch(() => null) : null\n cart = (cartSnapshot?.data?.() as CommerceModel.HostCart | undefined) ?? { lines: [] }\n }\n const productIds = [...new Set((cart.lines ?? []).map((line) => line.productId))].slice(0, 50)\n const products = await Promise.all(\n productIds.map((id) =>\n hostRef\n .collection('products')\n .doc(id)\n .get()\n .catch(() => null),\n ),\n )\n let itemsCents = 0\n let hasPhysicalLine = false\n for (const line of cart.lines ?? []) {\n const snapshot = products[productIds.indexOf(line.productId)]\n if (!snapshot?.exists) continue\n const product = CommerceModel.liftLegacyProduct(snapshot.data() as any)\n const variant = line.variantId\n ? product.variants.find((item) => item.id === line.variantId)\n : product.variants[0]\n // With its add-ons (AGL-4100), as checkout prices the line.\n const chosen = CommerceModel.resolveWebsiteLineModifiers(product, line.modifiers, variant?.id)\n itemsCents +=\n (Math.round(Number(variant?.priceUsd ?? 0) * 100) + (chosen.ok ? chosen.extraCents : 0)) *\n (Number(line.quantity) || 0)\n if ((product.type ?? 'physical') === 'physical') hasPhysicalLine = true\n }\n if (!hasPhysicalLine) return res.status(200).json(nothing)\n\n const store = await readLocalFulfillmentStore({\n hostRef,\n org: ownerOrg.org as { timeZone?: string },\n })\n const nowMs = Date.now()\n let delivery: Record<string, unknown> | null = null\n if (CommerceModel.localDeliveryOffered(store.delivery)) {\n const settings = store.delivery\n const country = String(settings.country)\n const windows = CommerceModel.upcomingLocalDeliveryWindows(settings, { nowMs, timeZone: store.timeZone }).map(\n (window) => ({\n ...window,\n label: CommerceModel.formatLocalDeliveryWindow(window, store.timeZone),\n }),\n )\n const radius = CommerceModel.localDeliveryHasRadiusZones(settings)\n delivery = {\n country,\n ...(settings.instructions ? { instructions: settings.instructions } : {}),\n needsAddress: radius,\n windows,\n }\n const request = readLocalFulfillmentRequest({\n method: 'local_delivery',\n postalCode: body.postalCode,\n address: body.address,\n })\n const postalCode = CommerceModel.normalizePostcode(request?.postalCode ?? request?.address?.postalCode, country)\n if (postalCode) {\n let zone = CommerceModel.matchLocalDeliveryZone(settings, { country, postalCode })\n if (!zone && radius && request?.address?.line1) {\n const visitor = String(req.socket?.remoteAddress ?? '') || 'no-address'\n const allowed = await consumeRateLimit(`local-delivery:place:${hostId}:${visitor}`, PLACE_LIMIT)\n .then((result) => result.allowed)\n .catch(() => false)\n const coordinates = allowed\n ? await placeDeliveryAddress(hostId, { ...request.address, postalCode, country })\n : null\n zone = CommerceModel.matchLocalDeliveryZone(settings, { country, postalCode, coordinates })\n }\n delivery['quote'] = zone\n ? {\n zoneId: zone.id,\n zoneName: zone.name,\n feeCents: CommerceModel.localDeliveryFeeCents(zone, itemsCents),\n ...(zone.minimumCents ? { minimumCents: zone.minimumCents } : {}),\n ...(zone.freeOverCents ? { freeOverCents: zone.freeOverCents } : {}),\n shortfallCents: CommerceModel.localDeliveryMinimumShortfall(zone, itemsCents),\n }\n : { unavailable: `We don’t deliver to ${postalCode}.` }\n }\n }\n // Whether the cart may offer texts about the pickup or delivery: the\n // platform can send them and the store has not turned them off.\n const toggles = (await hostRef\n .collection('settings')\n .doc('store')\n .get()\n .then((snapshot: any) => snapshot?.get?.('buyerNotifications'))\n .catch(() => null)) as unknown\n const texts = Boolean(\n pluginSmsMessaging()?.isConfigured() && CommerceModel.buyerNotificationEnabled(toggles, 'texts'),\n )\n return res.status(200).json({ pickup: store.pickupLocations, delivery, texts })\n } catch (error) {\n console.error('[local-fulfillment] options failed', hostId, error)\n return res.status(200).json(nothing)\n }\n}\n"],"names":["resolveSiteTimeZone","normalizePhone","pluginSmsMessaging","pluginShippingRateQuoter","Aglyn","consumeRateLimit","firebaseAdmin","getOrgForHost","isDocumentId","CommerceModel","readCartId","readLocalFulfillmentRequest","value","raw","method","text","input","max","String","trim","slice","request","phone","textPhone","locationId","postalCode","start","Math","round","Number","isFinite","windowStartMs","address","parts","line1","line2","city","state","readLocalFulfillmentStore","hostRef","locationsSnapshot","settings","host","Promise","all","collection","limit","PICKUP_LOCATIONS_MAX","get","catch","storeSettings","undefined","resolve","doc","then","snapshot","data","locations","docs","map","id","pickupLocations","pickupLocationOptions","delivery","normalizeLocalDeliverySettings","timeZone","org","LOCAL_DELIVERY_GEOCODE_TIMEOUT_MS","placeDeliveryAddress","hostId","quoter","validateAddress","timer","placed","race","available","check","country","verdict","coordinates","setTimeout","clearTimeout","LOCAL_DELIVERY_RATE_ID","money","cents","formatOrderMoney","planLocalFulfillment","store","hasPhysicalLine","kind","nowMs","Date","now","location","find","entry","status","error","changed","metadata","fulfillment","pickupLocationId","orderTextPhone","localDeliveryOffered","normalizePostcode","zone","matchLocalDeliveryZone","localDeliveryHasRadiusZones","shortfall","localDeliveryMinimumShortfall","itemsCents","minimumCents","window","findLocalDeliveryWindow","feeCents","localDeliveryFeeCents","label","formatLocalDeliveryWindow","option","rateId","name","amountCents","deliveryZoneId","deliveryWindowStartMs","startMs","deliveryWindowEndMs","endMs","deliveryPostalCode","deliveryFeeCents","deliveryLocationId","appendLocalFulfillmentMetadata","params","key","Object","entries","set","localFulfillmentLocationId","plan","localStockRefusalMessage","productName","what","orderLocalFulfillmentFromSession","customerPhone","exists","formatPostalAddressLine","postalAddress","pickup","locationName","instructions","hours","updatedAtMs","createdAtMs","order","fulfillmentMethod","list","orderLocalFulfillmentListFields","ready","pickupReadyWithinLabel","readyWithinMinutes","fields","summary","toLowerCase","filter","Boolean","join","outsideZone","zoneId","zones","windowEndMs","declared","shippingAddress","entered","windowLabel","localDelivery","zoneName","addressOutsideZone","PLACE_LIMIT","windowMs","localFulfillmentOptionsHandler","req","res","body","json","JSON","parse","query","nothing","cart","firestore","app","ownerOrg","checkEntitlement","buyNowProductId","productId","lines","variantId","Array","isArray","modifiers","modifierSelectionFrom","quantity","min","cartSnapshot","cartId","cookies","productIds","Set","line","products","product","indexOf","liftLegacyProduct","variant","variants","item","chosen","resolveWebsiteLineModifiers","priceUsd","ok","extraCents","type","windows","upcomingLocalDeliveryWindows","radius","needsAddress","visitor","socket","remoteAddress","allowed","result","freeOverCents","shortfallCents","unavailable","toggles","texts","isConfigured","buyerNotificationEnabled","console"],"mappings":"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"}
|
|
@@ -657,7 +657,7 @@ async function syncOne(context, raw) {
|
|
|
657
657
|
});
|
|
658
658
|
}
|
|
659
659
|
const variant = (_product_variants_find = product.variants.find((item)=>item.id === line.variantId)) != null ? _product_variants_find : product.variants[0];
|
|
660
|
-
const chosen = CommerceModel.resolveLineModifiers(product, line.modifiers);
|
|
660
|
+
const chosen = CommerceModel.resolveLineModifiers(product, line.modifiers, variant == null ? void 0 : variant.id);
|
|
661
661
|
const currentCents = variant && CommerceModel.variantHasPrice(variant) && chosen.ok ? Math.round(Number(variant.priceUsd) * 100) + chosen.extraCents : null;
|
|
662
662
|
if (currentCents !== line.unitAmountCents) {
|
|
663
663
|
flags.add('price-changed');
|