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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/package.json +13 -13
  2. package/src/lib/components/cart.js +3 -2
  3. package/src/lib/components/cart.js.map +1 -1
  4. package/src/lib/components/console/commerce-analytics-card.component.js +3 -6
  5. package/src/lib/components/console/commerce-analytics-card.component.js.map +1 -1
  6. package/src/lib/components/console/commerce-glance-card.component.js +9 -3
  7. package/src/lib/components/console/commerce-glance-card.component.js.map +1 -1
  8. package/src/lib/components/console/customer-notifications-card.component.js +3 -1
  9. package/src/lib/components/console/customer-notifications-card.component.js.map +1 -1
  10. package/src/lib/components/console/discounts-card.component.js +2 -3
  11. package/src/lib/components/console/discounts-card.component.js.map +1 -1
  12. package/src/lib/components/console/host-coupons-card.component.js +3 -4
  13. package/src/lib/components/console/host-coupons-card.component.js.map +1 -1
  14. package/src/lib/components/console/local-delivery-settings-card.component.js +2 -2
  15. package/src/lib/components/console/local-delivery-settings-card.component.js.map +1 -1
  16. package/src/lib/components/console/locations-card.component.js +1 -1
  17. package/src/lib/components/console/locations-card.component.js.map +1 -1
  18. package/src/lib/components/console/member-posts-card.component.js +2 -2
  19. package/src/lib/components/console/member-posts-card.component.js.map +1 -1
  20. package/src/lib/components/console/order-detail-dialog.component.js +35 -30
  21. package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
  22. package/src/lib/components/console/order-fulfillment-panel.component.d.ts +11 -1
  23. package/src/lib/components/console/order-fulfillment-panel.component.js +75 -4
  24. package/src/lib/components/console/order-fulfillment-panel.component.js.map +1 -1
  25. package/src/lib/components/console/order-zones.d.ts +45 -1
  26. package/src/lib/components/console/order-zones.js +8 -0
  27. package/src/lib/components/console/order-zones.js.map +1 -1
  28. package/src/lib/components/console/pickup-delivery-queue-card.component.js +15 -1
  29. package/src/lib/components/console/pickup-delivery-queue-card.component.js.map +1 -1
  30. package/src/lib/components/console/pos-page.component.js +3 -2
  31. package/src/lib/components/console/pos-page.component.js.map +1 -1
  32. package/src/lib/components/console/product-editor-dialog.component.d.ts +2 -0
  33. package/src/lib/components/console/product-editor-dialog.component.js +30 -12
  34. package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
  35. package/src/lib/components/console/products-hub-card.component.js +13 -4
  36. package/src/lib/components/console/products-hub-card.component.js.map +1 -1
  37. package/src/lib/components/console/products-hub-zone.component.d.ts +2 -1
  38. package/src/lib/components/console/products-hub-zone.component.js +1 -1
  39. package/src/lib/components/console/products-hub-zone.component.js.map +1 -1
  40. package/src/lib/components/console/reviews-moderation-card.component.js +1 -2
  41. package/src/lib/components/console/reviews-moderation-card.component.js.map +1 -1
  42. package/src/lib/components/console/shipping-settings-card.component.js +2 -2
  43. package/src/lib/components/console/shipping-settings-card.component.js.map +1 -1
  44. package/src/lib/components/console/shippingeasy-card.component.js +1 -1
  45. package/src/lib/components/console/shippingeasy-card.component.js.map +1 -1
  46. package/src/lib/components/console/shipstation-card.component.js +1 -2
  47. package/src/lib/components/console/shipstation-card.component.js.map +1 -1
  48. package/src/lib/components/console/store-settings-card.component.js +3 -1
  49. package/src/lib/components/console/store-settings-card.component.js.map +1 -1
  50. package/src/lib/components/console/tax-settings-card.component.js +4 -1
  51. package/src/lib/components/console/tax-settings-card.component.js.map +1 -1
  52. package/src/lib/components/order-status.js +24 -1
  53. package/src/lib/components/order-status.js.map +1 -1
  54. package/src/lib/components/product-detail.js +30 -16
  55. package/src/lib/components/product-detail.js.map +1 -1
  56. package/src/lib/components/product-grid.d.ts +7 -0
  57. package/src/lib/components/product-grid.js +50 -7
  58. package/src/lib/components/product-grid.js.map +1 -1
  59. package/src/lib/components/related-products.js +1 -1
  60. package/src/lib/components/related-products.js.map +1 -1
  61. package/src/lib/components/wishlist.js +1 -1
  62. package/src/lib/components/wishlist.js.map +1 -1
  63. package/src/lib/declarations.server.js +11 -0
  64. package/src/lib/declarations.server.js.map +1 -1
  65. package/src/lib/model/commerce-orders.d.ts +8 -0
  66. package/src/lib/model/commerce-orders.js.map +1 -1
  67. package/src/lib/model/commerce.d.ts +21 -0
  68. package/src/lib/model/commerce.js +39 -1
  69. package/src/lib/model/commerce.js.map +1 -1
  70. package/src/lib/model/index.d.ts +1 -0
  71. package/src/lib/model/index.js +1 -0
  72. package/src/lib/model/index.js.map +1 -1
  73. package/src/lib/model/order-annotations.d.ts +54 -0
  74. package/src/lib/model/order-annotations.js +76 -0
  75. package/src/lib/model/order-annotations.js.map +1 -0
  76. package/src/lib/model/order-events.d.ts +2 -0
  77. package/src/lib/model/order-events.js.map +1 -1
  78. package/src/lib/model/order-fulfillment.d.ts +29 -0
  79. package/src/lib/model/order-fulfillment.js +63 -0
  80. package/src/lib/model/order-fulfillment.js.map +1 -1
  81. package/src/lib/model/order-local-fulfillment.d.ts +36 -0
  82. package/src/lib/model/order-local-fulfillment.js +19 -1
  83. package/src/lib/model/order-local-fulfillment.js.map +1 -1
  84. package/src/lib/model/order-status-view.d.ts +11 -0
  85. package/src/lib/model/order-status-view.js +20 -3
  86. package/src/lib/model/order-status-view.js.map +1 -1
  87. package/src/lib/model/product-write.js +8 -4
  88. package/src/lib/model/product-write.js.map +1 -1
  89. package/src/lib/server/api-v1/openapi.js +1 -0
  90. package/src/lib/server/api-v1/openapi.js.map +1 -1
  91. package/src/lib/server/api-v1/order-view.d.ts +1 -0
  92. package/src/lib/server/api-v1/order-view.js +10 -0
  93. package/src/lib/server/api-v1/order-view.js.map +1 -1
  94. package/src/lib/server/cart-checkout.js +9 -1
  95. package/src/lib/server/cart-checkout.js.map +1 -1
  96. package/src/lib/server/catalog.d.ts +6 -0
  97. package/src/lib/server/catalog.js +3 -1
  98. package/src/lib/server/catalog.js.map +1 -1
  99. package/src/lib/server/checkout.js +11 -1
  100. package/src/lib/server/checkout.js.map +1 -1
  101. package/src/lib/server/fulfill-order.d.ts +12 -0
  102. package/src/lib/server/fulfill-order.js +13 -0
  103. package/src/lib/server/fulfill-order.js.map +1 -1
  104. package/src/lib/server/local-delivery-records.d.ts +30 -0
  105. package/src/lib/server/local-delivery-records.js +284 -0
  106. package/src/lib/server/local-delivery-records.js.map +1 -0
  107. package/src/lib/server/order-annotations.d.ts +28 -0
  108. package/src/lib/server/order-annotations.js +143 -0
  109. package/src/lib/server/order-annotations.js.map +1 -0
  110. package/src/lib/server/order-events.js +2 -1
  111. package/src/lib/server/order-events.js.map +1 -1
  112. package/src/lib/server/order-notifications.js +39 -8
  113. package/src/lib/server/order-notifications.js.map +1 -1
  114. package/src/lib/server/pos-payment.js +9 -3
  115. package/src/lib/server/pos-payment.js.map +1 -1
  116. package/src/lib/server/product-catalog.js +6 -2
  117. package/src/lib/server/product-catalog.js.map +1 -1
  118. package/src/lib/server/product-drafts.d.ts +36 -9
  119. package/src/lib/server/product-drafts.js +24 -5
  120. package/src/lib/server/product-drafts.js.map +1 -1
  121. package/src/lib/server/product.d.ts +6 -1
  122. package/src/lib/server/product.js +1 -1
  123. package/src/lib/server/product.js.map +1 -1
  124. package/src/lib/server/products-write.d.ts +29 -0
  125. package/src/lib/server/products-write.js +7 -7
  126. package/src/lib/server/products-write.js.map +1 -1
  127. package/src/lib/server/shipment-records.js +2 -0
  128. package/src/lib/server/shipment-records.js.map +1 -1
  129. package/src/lib/server/site-page-resolver.d.ts +13 -0
  130. package/src/lib/server/site-page-resolver.js +138 -1
  131. package/src/lib/server/site-page-resolver.js.map +1 -1
  132. package/src/lib/server.js +5 -0
  133. package/src/lib/server.js.map +1 -1
  134. package/src/lib/utils/use-storefront-purchase-event.js +8 -2
  135. package/src/lib/utils/use-storefront-purchase-event.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/order-notifications.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 * as Aglyn from '@aglyn/aglyn/server'\nimport { pluginSmsMessaging } from '@aglyn/aglyn/plugin-manager/plugin-sms-messaging'\nimport { resolveSiteTimeZone } from '@aglyn/aglyn/app-utils/collection-entry-date'\nimport {\n resolveHostToken,\n type HostTokenSource,\n} from '@aglyn/aglyn/app-utils/host-tokens'\nimport {\n firebaseAdmin,\n getOrgForHost,\n hostSendingIdentity,\n meterHostEmail,\n renderHostEmailWithTokens,\n} from '@aglyn/tenant-data-admin'\nimport { isEmailConfigured, sendEmail } from '@aglyn/shared-util-email'\nimport * as CommerceModel from '../model'\nimport { mintDownloadToken } from './download'\nimport { orderStatusUrl } from './order-status-token'\n\n/**\n * Every message a buyer gets about their order, through ONE door (AGL-3610).\n *\n * Before this, the buyer heard about their order once — the online receipt —\n * and never again: a register sale sent nothing, a paid payment link sent\n * nothing, and shipping, delivery, refunds and cancellations were silent\n * (`fulfill-order.ts` said so on purpose). Every path that moves an order now\n * makes one call here, and this decides what that moment owes the buyer:\n *\n * - whether the store turned the moment off (`buyerNotifications` on the\n * store settings; ON unless explicitly off — see the model),\n * - which channels can carry it (email when mail is configured and the\n * order has an address; a text when the platform's SMS provider is\n * configured and the order carries the buyer's phone),\n * - and whether it was already sent.\n *\n * ## ONCE PER MOMENT, PER OCCURRENCE, PER CHANNEL\n *\n * Stripe redelivers webhooks, admins click twice, a lost response is retried.\n * Each message is CLAIMED before it is sent: a transaction reads the order's\n * `buyerNotifications` map and writes a `sending` marker for\n * `event__occurrence__channel` only if none is there. Exactly one of two\n * racing callers wins the claim; the other returns `already`. The occurrence\n * is the shipment id for `shipped` and the refund id for `refunded`, so a\n * second parcel or a second partial refund is a new message, while a\n * redelivered one is not.\n *\n * A send that fails RELEASES its claim so the next attempt can try again. A\n * process that dies between claim and send leaves the marker in `sending` and\n * the message unsent — at most once, never twice, which is the right side to\n * fail on for mail a buyer reads as \"did they charge me again?\".\n *\n * ## NEVER THROWS\n *\n * Every caller is past its point of no return — money moved, an order\n * shipped — so a mail outage must not turn into a 500 that invites the\n * merchant to do it again. Failures are logged and returned as outcomes.\n *\n * Shared with the order-processing and POS lanes (AGL-3611, AGL-3607): call\n * {@link notifyOrderBuyer} from any new path that moves an order, and\n * {@link sendOrderReceipt} for an explicit, merchant-asked send.\n */\n\nexport type OrderBuyerEvent = CommerceModel.BuyerNotificationEvent\n\nexport interface OrderRef {\n hostId: string\n orderId: string\n}\n\nexport interface NotifyOrderBuyerOptions {\n /** `shipped`: which shipment. Absent means the order's latest one. */\n fulfillmentId?: string\n /** `refunded`: the refund's own id (Stripe's), the occurrence key. */\n refundId?: string\n /** `refunded`: what THIS refund moved, in cents. */\n refundCents?: number\n /** `refunded`: the line indexes refunded by name, when any were. */\n refundLineIndexes?: readonly number[]\n /** `refunded`: whether this refund closed the order. */\n fullyRefunded?: boolean\n /**\n * The buyer's address when the order document does not carry it — a POS\n * folio sale attributed to the reservation's guest, a payment link whose\n * payer Stripe named. The order's own `customerEmail` wins when present.\n */\n email?: string | null\n /** Injected in specs. */\n firestore?: FirebaseFirestore.Firestore\n}\n\nexport type OrderBuyerChannelOutcome =\n | { channel: CommerceModel.BuyerNotificationChannel; outcome: 'sent' }\n | { channel: CommerceModel.BuyerNotificationChannel; outcome: 'already' }\n | {\n channel: CommerceModel.BuyerNotificationChannel\n outcome: 'failed'\n error: string\n }\n\nexport type NotifyOrderBuyerOutcome =\n | { outcome: 'disabled' }\n | { outcome: 'no_such_order' }\n /** The moment does not apply to this order (a counter sale \"shipping\"). */\n | { outcome: 'not_applicable' }\n /** No channel could carry it: no address, no mail or text configured. */\n | { outcome: 'no_recipient' }\n | { outcome: 'handled'; channels: OrderBuyerChannelOutcome[] }\n\ninterface StoreContext {\n firestore: FirebaseFirestore.Firestore\n hostRef: FirebaseFirestore.DocumentReference\n host: Record<string, unknown>\n settings: Record<string, unknown>\n currency: string\n}\n\nasync function readStore(\n firestore: FirebaseFirestore.Firestore,\n hostId: string,\n): Promise<StoreContext> {\n const hostRef = firestore.collection('hosts').doc(hostId)\n const [hostSnapshot, settingsSnapshot] = await Promise.all([\n hostRef.get().catch(() => null),\n hostRef.collection('settings').doc('store').get().catch(() => null),\n ])\n const settings = (settingsSnapshot?.data?.() ?? {}) as Record<string, unknown>\n return {\n firestore,\n hostRef,\n host: (hostSnapshot?.data?.() ?? {}) as Record<string, unknown>,\n settings,\n currency: String(settings['currency'] ?? 'USD') || 'USD',\n }\n}\n\nfunction lineLabel(line: CommerceModel.OrderLineItem): string {\n return `${line.quantity}× ${line.name}${\n line.variantLabel ? ` (${line.variantLabel})` : ''\n }`\n}\n\n/** The status link, or the store's home page when no link can be minted. */\nfunction statusLink(store: StoreContext, ref: OrderRef): string {\n return (\n orderStatusUrl(store.host as never, ref.hostId, ref.orderId) ??\n Aglyn.hostPublicOrigin({\n cname: typeof store.host['cname'] === 'string' ? store.host['cname'] : null,\n subdomain:\n typeof store.host['subdomain'] === 'string'\n ? store.host['subdomain']\n : null,\n }) ??\n ''\n )\n}\n\ninterface ComposedMessage {\n emailKey: string\n /** Tokens for the designed email. */\n tokens: Record<string, string>\n /** Subject when the template renders none. */\n subject: string\n /** Plain-text body when the template renders none. */\n text: string\n /** The text message, kept short: one or two segments. */\n sms: string\n /** The order timeline line recording the send, channel appended. */\n timelineDetail: string\n}\n\n/** What the moment says. `null` when it does not apply to this order. */\nexport function composeOrderBuyerMessage(input: {\n event: OrderBuyerEvent\n order: CommerceModel.HostOrder\n orderId: string\n businessName: string\n currency: string\n statusUrl: string\n receiptFooter?: string\n /** Absolute digital-delivery links for a receipt, by product id. */\n downloadLinks?: Record<string, string>\n options?: NotifyOrderBuyerOptions\n}): (ComposedMessage & { occurrence: string }) | null {\n const {\n event,\n order,\n orderId,\n businessName,\n currency,\n statusUrl,\n options = {},\n } = input\n const lines = order.lineItems ?? []\n const number = CommerceModel.formatOrderNumber(order, orderId)\n const money = (cents: number) =>\n CommerceModel.formatOrderMoney(cents, currency)\n const store = businessName || 'The store'\n const base = {\n 'order.number': number,\n 'order.statusUrl': statusUrl,\n 'order.ref': orderId,\n }\n const link = statusUrl ? `\\n\\nView your order: ${statusUrl}` : ''\n const smsLink = statusUrl ? ` ${statusUrl}` : ''\n\n if (event === 'receipt') {\n const totalCents =\n order.totals?.totalCents ?? Number(order.amountCents ?? 0)\n const licenseText = Object.entries(\n ((order as { licenseKeys?: Record<string, string[]> }).licenseKeys ??\n {}) as Record<string, string[]>,\n )\n .flatMap(([productId, keys]) => {\n const line = lines.find((item) => item.productId === productId)\n return (keys ?? []).map(\n (key) => `License key (${line?.name ?? 'product'}): ${key}`,\n )\n })\n .join('\\n')\n const downloads = Object.entries(input.downloadLinks ?? {})\n .map(([productId, url]) => {\n const line = lines.find((item) => item.productId === productId)\n return `Download ${line?.name ?? 'your file'}: ${url}`\n })\n .join('\\n')\n const linesText = lines\n .map(\n (line) =>\n `${lineLabel(line)} — ${money(line.unitAmountCents * line.quantity)}`,\n )\n .join('\\n')\n const summary = [linesText, licenseText, downloads]\n .filter(Boolean)\n .join('\\n\\n')\n const footer = String(input.receiptFooter ?? '')\n return {\n occurrence: 'order',\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.receipt,\n tokens: {\n ...base,\n 'order.summary': summary,\n 'order.total': money(totalCents),\n 'store.receiptFooter': footer,\n },\n subject: 'Receipt for your order',\n text:\n `Thanks for your purchase!\\n\\n${summary}\\n\\nTotal: ${money(totalCents)}\\n` +\n `Order ${number}` +\n (footer ? `\\n\\n${footer}` : '') +\n link,\n sms: `${store}: receipt for order ${number}, ${money(totalCents)}.${smsLink}`,\n timelineDetail: 'Receipt sent',\n }\n }\n\n if (event === 'shipped') {\n const fulfillments = order.fulfillments ?? []\n const fulfillment = options.fulfillmentId\n ? fulfillments.find((entry) => entry.id === options.fulfillmentId)\n : [...fulfillments].sort((a, b) => (b.atMs ?? 0) - (a.atMs ?? 0))[0]\n if (!fulfillment) return null\n // Collected at the counter or handed over by the store's driver\n // (AGL-3624): \"picked up\" and \"delivered\" say so, never \"shipped\".\n if (fulfillment.handover) return null\n // The units THIS shipment carried (AGL-3611): a partial shipment of a\n // line names its own count, not the line's.\n const shipped = CommerceModel.fulfillmentLineQuantities(order, fulfillment)\n .map((entry) =>\n lines[entry.lineItemId] ? { ...lines[entry.lineItemId], quantity: entry.quantity } : null,\n )\n .filter(Boolean) as CommerceModel.OrderLineItem[]\n const tracking = CommerceModel.fulfillmentTrackingUrl(fulfillment)\n const hasTracking = Boolean(fulfillment.trackingNumber)\n // Nothing physical left the building: a register sale handed over the\n // counter, or a digital-only order the merchant marked fulfilled. A\n // \"your order is on its way\" email for either is noise at best.\n const physical = shipped.some(\n (line) =>\n line.productType !== 'digital' && line.productType !== 'service',\n )\n if (!hasTracking && (order.channel === 'pos' || !physical)) return null\n // Shippable units still out, over every active shipment (AGL-3611).\n const remainingUnits = CommerceModel.remainingFulfillmentLines(order).reduce(\n (sum, entry) => sum + entry.quantity,\n 0,\n )\n const carrier = CommerceModel.carrierLabelFor(fulfillment.carrier)\n const trackingSentence = hasTracking\n ? `Tracking: ${[carrier, fulfillment.trackingNumber]\n .filter(Boolean)\n .join(' ')}${tracking ? ` — ${tracking}` : ''}`\n : ''\n const remainingSentence =\n remainingUnits > 0\n ? `${remainingUnits} more item${remainingUnits === 1 ? '' : 's'} will ship separately.`\n : ''\n const summary = shipped.map(lineLabel).join('\\n')\n return {\n occurrence: fulfillment.id,\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.shipped,\n tokens: {\n ...base,\n 'shipment.summary': summary,\n 'shipment.carrier': carrier,\n 'shipment.trackingNumber': String(fulfillment.trackingNumber ?? ''),\n 'shipment.tracking': trackingSentence,\n 'shipment.remaining': remainingSentence,\n },\n subject: `Your order ${number} has shipped`,\n text:\n `${store} shipped items from order ${number}.\\n\\n${summary}` +\n (trackingSentence ? `\\n\\n${trackingSentence}` : '') +\n (remainingSentence ? `\\n\\n${remainingSentence}` : '') +\n link,\n sms:\n `${store}: order ${number} has shipped.` +\n (tracking ? ` Track it: ${tracking}` : smsLink),\n timelineDetail: 'Shipping confirmation sent',\n }\n }\n\n if (event === 'delivered') {\n // A counter sale is \"delivered\" the moment it is handed over.\n if (order.channel === 'pos') return null\n const summary = lines.map(lineLabel).join('\\n')\n return {\n occurrence: 'order',\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.delivered,\n tokens: { ...base, 'order.summary': summary },\n subject: `Your order ${number} was delivered`,\n text:\n `${store} marked order ${number} as delivered.\\n\\n${summary}` + link,\n sms: `${store}: order ${number} was delivered.${smsLink}`,\n timelineDetail: 'Delivery confirmation sent',\n }\n }\n\n if (event === 'ready_for_pickup' || event === 'picked_up') {\n const pickup = order.pickup\n if (order.fulfillmentMethod !== 'pickup' || !pickup) return null\n const summary = lines.map(lineLabel).join('\\n')\n const where = `${pickup.locationName}${pickup.address ? `, ${pickup.address}` : ''}`\n const hours = pickup.hours ? `Pickup hours:\\n${pickup.hours}` : ''\n const instructions = pickup.instructions ?? ''\n if (event === 'picked_up') {\n return {\n occurrence: 'order',\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.picked_up,\n tokens: { ...base, 'order.summary': summary, 'pickup.location': where },\n subject: `You picked up order ${number}`,\n text: `Order ${number} was picked up from ${where}. Thanks for shopping with ${store}.\\n\\n${summary}` + link,\n sms: `${store}: order ${number} was picked up. Thank you!${smsLink}`,\n timelineDetail: 'Pickup confirmation sent',\n }\n }\n return {\n occurrence: 'order',\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.ready_for_pickup,\n tokens: {\n ...base,\n 'order.summary': summary,\n 'pickup.location': where,\n 'pickup.hours': hours,\n 'pickup.instructions': instructions,\n },\n subject: `Your order ${number} is ready for pickup`,\n text:\n `Your order ${number} is ready for pickup at ${where}.\\n\\n${summary}` +\n (hours ? `\\n\\n${hours}` : '') +\n (instructions ? `\\n\\n${instructions}` : '') +\n `\\n\\nBring your order number, ${number}.` +\n link,\n sms: `${store}: order ${number} is ready for pickup at ${pickup.locationName}.${smsLink}`,\n timelineDetail: 'Ready-for-pickup notice sent',\n }\n }\n\n if (event === 'out_for_delivery') {\n const delivery = order.localDelivery\n if (order.fulfillmentMethod !== 'local_delivery' || !delivery) return null\n const summary = lines.map(lineLabel).join('\\n')\n const window = delivery.windowLabel ?? ''\n return {\n // Each run out the door is its own message: a failed drop sent out\n // again tells the buyer again.\n occurrence: String(delivery.outForDeliveryAtMs ?? 'order'),\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.out_for_delivery,\n tokens: { ...base, 'order.summary': summary, 'delivery.window': window },\n subject: `Your order ${number} is out for delivery`,\n text:\n `${store} is on the way with order ${number}` +\n (window ? `, in your window ${window}` : '') +\n `.\\n\\n${summary}` +\n link,\n sms: `${store}: order ${number} is out for delivery${window ? ` (${window})` : ''}.${smsLink}`,\n timelineDetail: 'Out-for-delivery notice sent',\n }\n }\n\n if (event === 'refunded') {\n const refundCents = Math.max(0, Math.round(Number(options.refundCents ?? 0)))\n if (!(refundCents > 0)) return null\n const refundedLines = (options.refundLineIndexes ?? [])\n .map((index) => lines[index])\n .filter(Boolean) as CommerceModel.OrderLineItem[]\n const summary = refundedLines.map(lineLabel).join('\\n')\n const note = options.fullyRefunded\n ? 'Your order has been refunded in full.'\n : 'The rest of your order is unchanged.'\n return {\n // The refund's own id is the occurrence; without one, the running\n // total after this refund is (each settled refund raises it).\n occurrence:\n options.refundId ||\n `total-${Number(order.refundedCents ?? 0) || refundCents}`,\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.refunded,\n tokens: {\n ...base,\n 'refund.amount': money(refundCents),\n 'refund.summary': summary,\n 'refund.note': note,\n },\n subject: `Refund for your order ${number}`,\n text:\n `${store} refunded ${money(refundCents)} on order ${number}.` +\n (summary ? `\\n\\n${summary}` : '') +\n `\\n\\n${note}\\n\\nRefunds go back to the original payment method and ` +\n 'usually appear within 5 to 10 business days, depending on your bank.' +\n link,\n sms: `${store}: ${money(refundCents)} refunded on order ${number}.${smsLink}`,\n timelineDetail: `Refund confirmation sent (${money(refundCents)})`,\n }\n }\n\n // cancelled\n const summary = lines.map(lineLabel).join('\\n')\n const wasPaid = Boolean(\n order.paymentIntentId ||\n (order.channel === 'pos' && order.status !== 'pending') ||\n (order.timeline ?? []).some((entry) => entry?.event === 'paid'),\n )\n const note = wasPaid\n ? `Questions about a payment? Reply to this email or contact ${store}.`\n : ''\n return {\n occurrence: 'order',\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.cancelled,\n tokens: { ...base, 'order.summary': summary, 'cancel.note': note },\n subject: `Your order ${number} was canceled`,\n text:\n `${store} canceled order ${number}.\\n\\n${summary}` +\n (note ? `\\n\\n${note}` : '') +\n link,\n sms: `${store}: order ${number} was canceled.${smsLink}`,\n timelineDetail: 'Cancellation notice sent',\n }\n}\n\n/**\n * Claims one message in a transaction. `true` when this caller owns the send;\n * `false` when a marker is already there (sent, or being sent).\n */\nasync function claimMessage(\n firestore: FirebaseFirestore.Firestore,\n orderRef: FirebaseFirestore.DocumentReference,\n marker: string,\n channel: CommerceModel.BuyerNotificationChannel,\n): Promise<boolean> {\n return firestore.runTransaction(async (transaction) => {\n const snapshot = await transaction.get(orderRef)\n if (!snapshot.exists) return false\n const sent = (snapshot.get('buyerNotifications') ?? {}) as Record<\n string,\n unknown\n >\n if (sent[marker]) return false\n transaction.update(orderRef, {\n [`buyerNotifications.${marker}`]: {\n state: 'sending',\n channel,\n atMs: Date.now(),\n },\n })\n return true\n })\n}\n\n/** Marks a claimed message sent and records it on the order timeline. */\nasync function settleMessage(\n firestore: FirebaseFirestore.Firestore,\n orderRef: FirebaseFirestore.DocumentReference,\n marker: string | null,\n channel: CommerceModel.BuyerNotificationChannel,\n detail: string,\n): Promise<void> {\n await firestore\n .runTransaction(async (transaction) => {\n const snapshot = await transaction.get(orderRef)\n if (!snapshot.exists) return\n const order = CommerceModel.liftLegacyOrder(\n (snapshot.data() ?? {}) as never,\n )\n const atMs = Date.now()\n transaction.update(orderRef, {\n ...(marker\n ? {\n [`buyerNotifications.${marker}`]: { state: 'sent', channel, atMs },\n }\n : {}),\n timeline: CommerceModel.appendOrderEvent(\n order,\n 'buyer-notified',\n `${detail} by ${channel === 'sms' ? 'text' : 'email'}`,\n atMs,\n ),\n })\n })\n .catch((error) => {\n console.error('[order-notifications] settle failed', marker, error)\n })\n}\n\nasync function releaseMessage(\n orderRef: FirebaseFirestore.DocumentReference,\n marker: string,\n): Promise<void> {\n await orderRef\n .update({\n [`buyerNotifications.${marker}`]:\n firebaseAdmin.firestore.FieldValue.delete(),\n })\n .catch(() => undefined)\n}\n\nasync function deliverEmail(\n store: StoreContext,\n ref: OrderRef,\n to: string,\n message: ComposedMessage,\n context: string,\n): Promise<void> {\n const designed = await renderHostEmailWithTokens(\n store.firestore,\n ref.hostId,\n message.emailKey,\n message.tokens,\n )\n const org = await getOrgForHost(ref.hostId).catch(() => null)\n await sendEmail({\n to,\n subject: designed?.subject ?? message.subject,\n text: designed?.text || message.text,\n ...(designed?.html ? { html: designed.html } : {}),\n fromName: Aglyn.resolveBrandingProfile(org?.org as never).fromName,\n sendingIdentity: await hostSendingIdentity(ref.hostId),\n audience: 'tenant',\n context,\n // Owed to the recipient by their own order: the phishing screen's soft\n // rules never hold it (AGL-3356).\n owedFor: 'order',\n })\n // Cost meter (AGL-1438), as every transactional store email.\n await meterHostEmail(ref.hostId)\n}\n\n/**\n * What a text attempt came to: `refused` names why nothing went (null when it\n * went), and `scheduledForMs` says it was held for the buyer's morning.\n */\ninterface SmsDelivery {\n refused: string | null\n scheduledForMs?: number\n}\n\n/**\n * Sends one order text. A receipt goes at once — the buyer is at the counter\n * or the checkout waiting for it. Every later moment (shipped, delivered,\n * refunded, canceled) can fire at any hour, from a carrier scan or a late\n * packing shift, so it asks the provider to keep quiet hours in the STORE's\n * zone: the best stand-in the platform has for a buyer whose own zone it was\n * never told, and right for the local and regional stores most texts come\n * from. Held texts arrive at 8 AM there.\n */\nasync function deliverSms(\n ref: OrderRef,\n to: string,\n message: ComposedMessage,\n context: string,\n quiet: { host: Record<string, unknown> } | null,\n): Promise<SmsDelivery> {\n const sms = pluginSmsMessaging()\n if (!sms?.isConfigured()) return { refused: 'Text messages are not configured' }\n let timeZone: string | null = null\n if (quiet) {\n const owner = await getOrgForHost(ref.hostId).catch(() => null)\n timeZone = resolveSiteTimeZone(\n (owner?.org ?? null) as { timeZone?: string } | null,\n quiet.host as { timeZone?: string },\n )\n }\n const outcome = await sms.send({\n to,\n body: message.sms,\n hostId: ref.hostId,\n purpose: 'transactional',\n context,\n ...(timeZone ? { quietHours: { timeZone } } : {}),\n })\n if (outcome.status !== 'sent') return { refused: outcome.status }\n return {\n refused: null,\n ...(outcome.scheduledForMs ? { scheduledForMs: outcome.scheduledForMs } : {}),\n }\n}\n\n/** Absolute download links for a receipt's digital lines. */\nfunction downloadLinksFor(\n store: StoreContext,\n ref: OrderRef,\n order: CommerceModel.HostOrder,\n): Record<string, string> {\n const origin = Aglyn.hostPublicOrigin({\n cname: typeof store.host['cname'] === 'string' ? store.host['cname'] : null,\n subdomain:\n typeof store.host['subdomain'] === 'string' ? store.host['subdomain'] : null,\n })\n const digital = (order.lineItems ?? []).filter(\n (line) => line.productType === 'digital',\n )\n if (!origin || !digital.length) return {}\n let token: string\n try {\n token = mintDownloadToken(ref.hostId, ref.orderId)\n } catch {\n return {}\n }\n return Object.fromEntries(\n digital.map((line) => [\n line.productId,\n `${origin}/api/commerce/download?hostId=${encodeURIComponent(ref.hostId)}` +\n `&orderId=${encodeURIComponent(ref.orderId)}` +\n `&productId=${encodeURIComponent(line.productId)}&token=${token}`,\n ]),\n )\n}\n\nasync function readOrder(\n firestore: FirebaseFirestore.Firestore,\n ref: OrderRef,\n): Promise<{\n orderRef: FirebaseFirestore.DocumentReference\n order: CommerceModel.HostOrder | null\n}> {\n const orderRef = firestore\n .collection('hosts')\n .doc(ref.hostId)\n .collection('orders')\n .doc(ref.orderId)\n const snapshot = await orderRef.get()\n return {\n orderRef,\n order: snapshot.exists\n ? CommerceModel.liftLegacyOrder((snapshot.data() ?? {}) as never)\n : null,\n }\n}\n\nfunction emailOf(value: unknown): string {\n const email = String(value ?? '').trim()\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email) ? email : ''\n}\n\n/**\n * Tells the buyer about one moment in their order's life, once. See the\n * module header. Never throws.\n */\nexport async function notifyOrderBuyer(\n ref: OrderRef,\n event: OrderBuyerEvent,\n options: NotifyOrderBuyerOptions = {},\n): Promise<NotifyOrderBuyerOutcome> {\n try {\n if (!ref.hostId || !ref.orderId) return { outcome: 'no_such_order' }\n const firestore = options.firestore ?? firebaseAdmin.app().firestore()\n const store = await readStore(firestore, ref.hostId)\n const toggles = store.settings['buyerNotifications']\n if (!CommerceModel.buyerNotificationEnabled(toggles, event)) {\n return { outcome: 'disabled' }\n }\n const { orderRef, order } = await readOrder(firestore, ref)\n if (!order) return { outcome: 'no_such_order' }\n const message = composeOrderBuyerMessage({\n event,\n order,\n orderId: ref.orderId,\n businessName: String(\n resolveHostToken('businessName', store.host as HostTokenSource) ?? '',\n ),\n currency: store.currency,\n statusUrl: statusLink(store, ref),\n receiptFooter: String(store.settings['receiptFooter'] ?? ''),\n downloadLinks:\n event === 'receipt' ? downloadLinksFor(store, ref, order) : undefined,\n options,\n })\n if (!message) return { outcome: 'not_applicable' }\n\n const email = emailOf(order.customerEmail) || emailOf(options.email)\n const phone = String(order.customerPhone ?? '').trim()\n const targets: {\n channel: CommerceModel.BuyerNotificationChannel\n to: string\n }[] = []\n if (email && isEmailConfigured()) targets.push({ channel: 'email', to: email })\n if (\n phone &&\n CommerceModel.buyerNotificationEnabled(toggles, 'texts') &&\n pluginSmsMessaging()?.isConfigured()\n ) {\n targets.push({ channel: 'sms', to: phone })\n }\n if (!targets.length) return { outcome: 'no_recipient' }\n\n const channels: OrderBuyerChannelOutcome[] = []\n for (const target of targets) {\n const marker = CommerceModel.buyerNotificationMarker(\n event,\n message.occurrence,\n target.channel,\n )\n const claimed = await claimMessage(\n firestore,\n orderRef,\n marker,\n target.channel,\n ).catch((error) => {\n console.error('[order-notifications] claim failed', marker, error)\n return false\n })\n if (!claimed) {\n channels.push({ channel: target.channel, outcome: 'already' })\n continue\n }\n const context = message.emailKey\n let held: number | null = null\n try {\n if (target.channel === 'email') {\n await deliverEmail(store, ref, target.to, message, context)\n } else {\n const sent = await deliverSms(\n ref,\n target.to,\n message,\n context,\n event === 'receipt' ? null : { host: store.host },\n )\n if (sent.refused) throw new Error(sent.refused)\n held = sent.scheduledForMs ?? null\n }\n await settleMessage(\n firestore,\n orderRef,\n marker,\n target.channel,\n held\n ? `${message.timelineDetail} (held for the morning)`\n : message.timelineDetail,\n )\n channels.push({ channel: target.channel, outcome: 'sent' })\n } catch (error) {\n await releaseMessage(orderRef, marker)\n console.error('[order-notifications] send failed', {\n hostId: ref.hostId,\n orderId: ref.orderId,\n event,\n channel: target.channel,\n error,\n })\n channels.push({\n channel: target.channel,\n outcome: 'failed',\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n return { outcome: 'handled', channels }\n } catch (error) {\n console.error('[order-notifications] notify failed', ref, event, error)\n return { outcome: 'handled', channels: [] }\n }\n}\n\nexport type SendOrderReceiptOutcome =\n | { outcome: 'sent'; channel: CommerceModel.BuyerNotificationChannel }\n | { outcome: 'no_such_order' }\n | { outcome: 'not_configured' }\n | { outcome: 'invalid_recipient' }\n | { outcome: 'failed'; error: string }\n\n/**\n * Sends the receipt again, on the merchant's say-so, to the address or number\n * they gave (AGL-3610) — the order dialog's \"Resend receipt\".\n *\n * NOT idempotent, on purpose: a resend IS the second send. The route in front\n * of it is rate-limited instead, and the send is recorded on the timeline so\n * the merchant can see it went. It does not consult the store's moment\n * switches either — a merchant who turned automatic receipts off can still\n * hand one to a customer who asks.\n */\nexport async function sendOrderReceipt(\n ref: OrderRef,\n input: {\n channel: CommerceModel.BuyerNotificationChannel\n to: string\n firestore?: FirebaseFirestore.Firestore\n },\n): Promise<SendOrderReceiptOutcome> {\n try {\n const firestore = input.firestore ?? firebaseAdmin.app().firestore()\n const { orderRef, order } = await readOrder(firestore, ref)\n if (!order) return { outcome: 'no_such_order' }\n if (input.channel === 'email') {\n if (!isEmailConfigured()) return { outcome: 'not_configured' }\n if (!emailOf(input.to)) return { outcome: 'invalid_recipient' }\n } else {\n if (!pluginSmsMessaging()?.isConfigured()) {\n return { outcome: 'not_configured' }\n }\n if (!Aglyn.normalizePhone(input.to)) {\n return { outcome: 'invalid_recipient' }\n }\n }\n const store = await readStore(firestore, ref.hostId)\n const message = composeOrderBuyerMessage({\n event: 'receipt',\n order,\n orderId: ref.orderId,\n businessName: String(\n resolveHostToken('businessName', store.host as HostTokenSource) ?? '',\n ),\n currency: store.currency,\n statusUrl: statusLink(store, ref),\n receiptFooter: String(store.settings['receiptFooter'] ?? ''),\n downloadLinks: downloadLinksFor(store, ref, order),\n })\n if (!message) return { outcome: 'failed', error: 'Nothing to send' }\n if (input.channel === 'email') {\n await deliverEmail(store, ref, emailOf(input.to), message, 'order-receipt')\n } else {\n const sent = await deliverSms(ref, input.to, message, 'order-receipt', null)\n if (sent.refused) return { outcome: 'failed', error: sent.refused }\n }\n await settleMessage(firestore, orderRef, null, input.channel, 'Receipt re-sent')\n return { outcome: 'sent', channel: input.channel }\n } catch (error) {\n console.error('[order-notifications] receipt send failed', ref, error)\n return {\n outcome: 'failed',\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n/**\n * What the online receipt sends needs from this module (AGL-3610): whether\n * the store left receipts on, and the `order.number` / `order.statusUrl`\n * tokens. The cart and buy-now receipts compose their own summary (license\n * keys and download links built from the Stripe session) inside the\n * webhook's redelivery guard, so they ask here rather than route through\n * {@link notifyOrderBuyer}.\n */\nexport async function onlineReceiptExtras(\n hostId: string,\n orderId: string,\n firestore: FirebaseFirestore.Firestore = firebaseAdmin.app().firestore(),\n): Promise<{ enabled: boolean; tokens: Record<string, string> }> {\n try {\n const store = await readStore(firestore, hostId)\n const { order } = await readOrder(firestore, { hostId, orderId })\n return {\n enabled: CommerceModel.buyerNotificationEnabled(\n store.settings['buyerNotifications'],\n 'receipt',\n ),\n tokens: {\n 'order.number': CommerceModel.formatOrderNumber(order ?? {}, orderId),\n 'order.statusUrl': statusLink(store, { hostId, orderId }),\n },\n }\n } catch (error) {\n console.error('[order-notifications] receipt extras failed', error)\n return { enabled: true, tokens: { 'order.number': '', 'order.statusUrl': '' } }\n }\n}\n"],"names":["Aglyn","pluginSmsMessaging","resolveSiteTimeZone","resolveHostToken","firebaseAdmin","getOrgForHost","hostSendingIdentity","meterHostEmail","renderHostEmailWithTokens","isEmailConfigured","sendEmail","CommerceModel","mintDownloadToken","orderStatusUrl","readStore","firestore","hostId","settings","settingsSnapshot","hostSnapshot","hostRef","collection","doc","Promise","all","get","catch","data","host","currency","String","lineLabel","line","quantity","name","variantLabel","statusLink","store","ref","orderId","hostPublicOrigin","cname","subdomain","composeOrderBuyerMessage","input","order","event","businessName","statusUrl","options","lines","lineItems","number","formatOrderNumber","money","cents","formatOrderMoney","base","link","smsLink","totalCents","totals","Number","amountCents","licenseText","Object","entries","licenseKeys","flatMap","productId","keys","find","item","map","key","join","downloads","downloadLinks","url","linesText","unitAmountCents","summary","filter","Boolean","footer","receiptFooter","occurrence","emailKey","BUYER_NOTIFICATION_EMAIL_KEYS","receipt","tokens","subject","text","sms","timelineDetail","fulfillment","fulfillments","fulfillmentId","entry","id","sort","a","b","atMs","handover","shipped","fulfillmentLineQuantities","lineItemId","tracking","fulfillmentTrackingUrl","hasTracking","trackingNumber","physical","some","productType","channel","remainingUnits","remainingFulfillmentLines","reduce","sum","carrier","carrierLabelFor","trackingSentence","remainingSentence","delivered","pickup","fulfillmentMethod","where","locationName","address","hours","instructions","picked_up","ready_for_pickup","delivery","localDelivery","window","windowLabel","outForDeliveryAtMs","out_for_delivery","refundCents","Math","max","round","refundedLines","refundLineIndexes","index","note","fullyRefunded","refundId","refundedCents","refunded","wasPaid","paymentIntentId","status","timeline","cancelled","claimMessage","orderRef","marker","runTransaction","transaction","snapshot","exists","sent","update","state","Date","now","settleMessage","detail","liftLegacyOrder","appendOrderEvent","error","console","releaseMessage","FieldValue","delete","undefined","deliverEmail","to","message","context","designed","org","html","fromName","resolveBrandingProfile","sendingIdentity","audience","owedFor","deliverSms","quiet","isConfigured","refused","timeZone","owner","outcome","send","body","purpose","quietHours","scheduledForMs","downloadLinksFor","origin","digital","length","token","fromEntries","encodeURIComponent","readOrder","emailOf","value","email","trim","test","notifyOrderBuyer","app","toggles","buyerNotificationEnabled","customerEmail","phone","customerPhone","targets","push","channels","target","buyerNotificationMarker","claimed","held","Error","sendOrderReceipt","normalizePhone","onlineReceiptExtras","enabled"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/order-notifications.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 * as Aglyn from '@aglyn/aglyn/server'\nimport { pluginSmsMessaging } from '@aglyn/aglyn/plugin-manager/plugin-sms-messaging'\nimport { resolveSiteTimeZone } from '@aglyn/aglyn/app-utils/collection-entry-date'\nimport {\n resolveHostToken,\n type HostTokenSource,\n} from '@aglyn/aglyn/app-utils/host-tokens'\nimport {\n firebaseAdmin,\n getOrgForHost,\n hostSendingIdentity,\n meterHostEmail,\n renderHostEmailWithTokens,\n} from '@aglyn/tenant-data-admin'\nimport {\n isEmailConfigured,\n renderTextEmailHtml,\n sendEmail,\n} from '@aglyn/shared-util-email'\nimport {\n resolvePluginOrderEmailCopies,\n withOrderEmailDataBlocks,\n} from '@aglyn/aglyn/plugin-manager/plugin-order-email-copies'\nimport * as CommerceModel from '../model'\nimport { orderViewFromData } from './api-v1/order-view'\nimport { mintDownloadToken } from './download'\nimport { orderStatusUrl } from './order-status-token'\n\n/**\n * Every message a buyer gets about their order, through ONE door (AGL-3610).\n *\n * Before this, the buyer heard about their order once — the online receipt —\n * and never again: a register sale sent nothing, a paid payment link sent\n * nothing, and shipping, delivery, refunds and cancellations were silent\n * (`fulfill-order.ts` said so on purpose). Every path that moves an order now\n * makes one call here, and this decides what that moment owes the buyer:\n *\n * - whether the store turned the moment off (`buyerNotifications` on the\n * store settings; ON unless explicitly off — see the model),\n * - which channels can carry it (email when mail is configured and the\n * order has an address; a text when the platform's SMS provider is\n * configured and the order carries the buyer's phone),\n * - and whether it was already sent.\n *\n * ## ONCE PER MOMENT, PER OCCURRENCE, PER CHANNEL\n *\n * Stripe redelivers webhooks, admins click twice, a lost response is retried.\n * Each message is CLAIMED before it is sent: a transaction reads the order's\n * `buyerNotifications` map and writes a `sending` marker for\n * `event__occurrence__channel` only if none is there. Exactly one of two\n * racing callers wins the claim; the other returns `already`. The occurrence\n * is the shipment id for `shipped` and the refund id for `refunded`, so a\n * second parcel or a second partial refund is a new message, while a\n * redelivered one is not.\n *\n * A send that fails RELEASES its claim so the next attempt can try again. A\n * process that dies between claim and send leaves the marker in `sending` and\n * the message unsent — at most once, never twice, which is the right side to\n * fail on for mail a buyer reads as \"did they charge me again?\".\n *\n * ## NEVER THROWS\n *\n * Every caller is past its point of no return — money moved, an order\n * shipped — so a mail outage must not turn into a 500 that invites the\n * merchant to do it again. Failures are logged and returned as outcomes.\n *\n * Shared with the order-processing and POS lanes (AGL-3611, AGL-3607): call\n * {@link notifyOrderBuyer} from any new path that moves an order, and\n * {@link sendOrderReceipt} for an explicit, merchant-asked send.\n */\n\nexport type OrderBuyerEvent = CommerceModel.BuyerNotificationEvent\n\nexport interface OrderRef {\n hostId: string\n orderId: string\n}\n\nexport interface NotifyOrderBuyerOptions {\n /** `shipped`: which shipment. Absent means the order's latest one. */\n fulfillmentId?: string\n /** `refunded`: the refund's own id (Stripe's), the occurrence key. */\n refundId?: string\n /** `refunded`: what THIS refund moved, in cents. */\n refundCents?: number\n /** `refunded`: the line indexes refunded by name, when any were. */\n refundLineIndexes?: readonly number[]\n /** `refunded`: whether this refund closed the order. */\n fullyRefunded?: boolean\n /**\n * The buyer's address when the order document does not carry it — a POS\n * folio sale attributed to the reservation's guest, a payment link whose\n * payer Stripe named. The order's own `customerEmail` wins when present.\n */\n email?: string | null\n /** Injected in specs. */\n firestore?: FirebaseFirestore.Firestore\n}\n\nexport type OrderBuyerChannelOutcome =\n | { channel: CommerceModel.BuyerNotificationChannel; outcome: 'sent' }\n | { channel: CommerceModel.BuyerNotificationChannel; outcome: 'already' }\n | {\n channel: CommerceModel.BuyerNotificationChannel\n outcome: 'failed'\n error: string\n }\n\nexport type NotifyOrderBuyerOutcome =\n | { outcome: 'disabled' }\n | { outcome: 'no_such_order' }\n /** The moment does not apply to this order (a counter sale \"shipping\"). */\n | { outcome: 'not_applicable' }\n /** No channel could carry it: no address, no mail or text configured. */\n | { outcome: 'no_recipient' }\n | { outcome: 'handled'; channels: OrderBuyerChannelOutcome[] }\n\ninterface StoreContext {\n firestore: FirebaseFirestore.Firestore\n hostRef: FirebaseFirestore.DocumentReference\n host: Record<string, unknown>\n settings: Record<string, unknown>\n currency: string\n}\n\nasync function readStore(\n firestore: FirebaseFirestore.Firestore,\n hostId: string,\n): Promise<StoreContext> {\n const hostRef = firestore.collection('hosts').doc(hostId)\n const [hostSnapshot, settingsSnapshot] = await Promise.all([\n hostRef.get().catch(() => null),\n hostRef.collection('settings').doc('store').get().catch(() => null),\n ])\n const settings = (settingsSnapshot?.data?.() ?? {}) as Record<string, unknown>\n return {\n firestore,\n hostRef,\n host: (hostSnapshot?.data?.() ?? {}) as Record<string, unknown>,\n settings,\n currency: String(settings['currency'] ?? 'USD') || 'USD',\n }\n}\n\nfunction lineLabel(line: CommerceModel.OrderLineItem): string {\n return `${line.quantity}× ${line.name}${\n line.variantLabel ? ` (${line.variantLabel})` : ''\n }`\n}\n\n/** The status link, or the store's home page when no link can be minted. */\nfunction statusLink(store: StoreContext, ref: OrderRef): string {\n return (\n orderStatusUrl(store.host as never, ref.hostId, ref.orderId) ??\n Aglyn.hostPublicOrigin({\n cname: typeof store.host['cname'] === 'string' ? store.host['cname'] : null,\n subdomain:\n typeof store.host['subdomain'] === 'string'\n ? store.host['subdomain']\n : null,\n }) ??\n ''\n )\n}\n\ninterface ComposedMessage {\n emailKey: string\n /** Tokens for the designed email. */\n tokens: Record<string, string>\n /** Subject when the template renders none. */\n subject: string\n /** Plain-text body when the template renders none. */\n text: string\n /** The text message, kept short: one or two segments. */\n sms: string\n /** The order timeline line recording the send, channel appended. */\n timelineDetail: string\n}\n\n/** What the moment says. `null` when it does not apply to this order. */\nexport function composeOrderBuyerMessage(input: {\n event: OrderBuyerEvent\n order: CommerceModel.HostOrder\n orderId: string\n businessName: string\n currency: string\n statusUrl: string\n receiptFooter?: string\n /** Absolute digital-delivery links for a receipt, by product id. */\n downloadLinks?: Record<string, string>\n options?: NotifyOrderBuyerOptions\n}): (ComposedMessage & { occurrence: string }) | null {\n const {\n event,\n order,\n orderId,\n businessName,\n currency,\n statusUrl,\n options = {},\n } = input\n const lines = order.lineItems ?? []\n const number = CommerceModel.formatOrderNumber(order, orderId)\n const money = (cents: number) =>\n CommerceModel.formatOrderMoney(cents, currency)\n const store = businessName || 'The store'\n const base = {\n 'order.number': number,\n 'order.statusUrl': statusUrl,\n 'order.ref': orderId,\n }\n const link = statusUrl ? `\\n\\nView your order: ${statusUrl}` : ''\n const smsLink = statusUrl ? ` ${statusUrl}` : ''\n\n if (event === 'receipt') {\n const totalCents =\n order.totals?.totalCents ?? Number(order.amountCents ?? 0)\n const licenseText = Object.entries(\n ((order as { licenseKeys?: Record<string, string[]> }).licenseKeys ??\n {}) as Record<string, string[]>,\n )\n .flatMap(([productId, keys]) => {\n const line = lines.find((item) => item.productId === productId)\n return (keys ?? []).map(\n (key) => `License key (${line?.name ?? 'product'}): ${key}`,\n )\n })\n .join('\\n')\n const downloads = Object.entries(input.downloadLinks ?? {})\n .map(([productId, url]) => {\n const line = lines.find((item) => item.productId === productId)\n return `Download ${line?.name ?? 'your file'}: ${url}`\n })\n .join('\\n')\n const linesText = lines\n .map(\n (line) =>\n `${lineLabel(line)} — ${money(line.unitAmountCents * line.quantity)}`,\n )\n .join('\\n')\n const summary = [linesText, licenseText, downloads]\n .filter(Boolean)\n .join('\\n\\n')\n const footer = String(input.receiptFooter ?? '')\n return {\n occurrence: 'order',\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.receipt,\n tokens: {\n ...base,\n 'order.summary': summary,\n 'order.total': money(totalCents),\n 'store.receiptFooter': footer,\n },\n subject: 'Receipt for your order',\n text:\n `Thanks for your purchase!\\n\\n${summary}\\n\\nTotal: ${money(totalCents)}\\n` +\n `Order ${number}` +\n (footer ? `\\n\\n${footer}` : '') +\n link,\n sms: `${store}: receipt for order ${number}, ${money(totalCents)}.${smsLink}`,\n timelineDetail: 'Receipt sent',\n }\n }\n\n if (event === 'shipped') {\n const fulfillments = order.fulfillments ?? []\n const fulfillment = options.fulfillmentId\n ? fulfillments.find((entry) => entry.id === options.fulfillmentId)\n : [...fulfillments].sort((a, b) => (b.atMs ?? 0) - (a.atMs ?? 0))[0]\n if (!fulfillment) return null\n // Collected at the counter or handed over by the store's driver\n // (AGL-3624): \"picked up\" and \"delivered\" say so, never \"shipped\".\n if (fulfillment.handover) return null\n // The units THIS shipment carried (AGL-3611): a partial shipment of a\n // line names its own count, not the line's.\n const shipped = CommerceModel.fulfillmentLineQuantities(order, fulfillment)\n .map((entry) =>\n lines[entry.lineItemId] ? { ...lines[entry.lineItemId], quantity: entry.quantity } : null,\n )\n .filter(Boolean) as CommerceModel.OrderLineItem[]\n const tracking = CommerceModel.fulfillmentTrackingUrl(fulfillment)\n const hasTracking = Boolean(fulfillment.trackingNumber)\n // Nothing physical left the building: a register sale handed over the\n // counter, or a digital-only order the merchant marked fulfilled. A\n // \"your order is on its way\" email for either is noise at best.\n const physical = shipped.some(\n (line) =>\n line.productType !== 'digital' && line.productType !== 'service',\n )\n if (!hasTracking && (order.channel === 'pos' || !physical)) return null\n // Shippable units still out, over every active shipment (AGL-3611).\n const remainingUnits = CommerceModel.remainingFulfillmentLines(order).reduce(\n (sum, entry) => sum + entry.quantity,\n 0,\n )\n const carrier = CommerceModel.carrierLabelFor(fulfillment.carrier)\n const trackingSentence = hasTracking\n ? `Tracking: ${[carrier, fulfillment.trackingNumber]\n .filter(Boolean)\n .join(' ')}${tracking ? ` — ${tracking}` : ''}`\n : ''\n const remainingSentence =\n remainingUnits > 0\n ? `${remainingUnits} more item${remainingUnits === 1 ? '' : 's'} will ship separately.`\n : ''\n const summary = shipped.map(lineLabel).join('\\n')\n return {\n occurrence: fulfillment.id,\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.shipped,\n tokens: {\n ...base,\n 'shipment.summary': summary,\n 'shipment.carrier': carrier,\n 'shipment.trackingNumber': String(fulfillment.trackingNumber ?? ''),\n 'shipment.tracking': trackingSentence,\n 'shipment.remaining': remainingSentence,\n },\n subject: `Your order ${number} has shipped`,\n text:\n `${store} shipped items from order ${number}.\\n\\n${summary}` +\n (trackingSentence ? `\\n\\n${trackingSentence}` : '') +\n (remainingSentence ? `\\n\\n${remainingSentence}` : '') +\n link,\n sms:\n `${store}: order ${number} has shipped.` +\n (tracking ? ` Track it: ${tracking}` : smsLink),\n timelineDetail: 'Shipping confirmation sent',\n }\n }\n\n if (event === 'delivered') {\n // A counter sale is \"delivered\" the moment it is handed over.\n if (order.channel === 'pos') return null\n const summary = lines.map(lineLabel).join('\\n')\n return {\n occurrence: 'order',\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.delivered,\n tokens: { ...base, 'order.summary': summary },\n subject: `Your order ${number} was delivered`,\n text:\n `${store} marked order ${number} as delivered.\\n\\n${summary}` + link,\n sms: `${store}: order ${number} was delivered.${smsLink}`,\n timelineDetail: 'Delivery confirmation sent',\n }\n }\n\n if (event === 'ready_for_pickup' || event === 'picked_up') {\n const pickup = order.pickup\n if (order.fulfillmentMethod !== 'pickup' || !pickup) return null\n const summary = lines.map(lineLabel).join('\\n')\n const where = `${pickup.locationName}${pickup.address ? `, ${pickup.address}` : ''}`\n const hours = pickup.hours ? `Pickup hours:\\n${pickup.hours}` : ''\n const instructions = pickup.instructions ?? ''\n if (event === 'picked_up') {\n return {\n occurrence: 'order',\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.picked_up,\n tokens: { ...base, 'order.summary': summary, 'pickup.location': where },\n subject: `You picked up order ${number}`,\n text: `Order ${number} was picked up from ${where}. Thanks for shopping with ${store}.\\n\\n${summary}` + link,\n sms: `${store}: order ${number} was picked up. Thank you!${smsLink}`,\n timelineDetail: 'Pickup confirmation sent',\n }\n }\n return {\n occurrence: 'order',\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.ready_for_pickup,\n tokens: {\n ...base,\n 'order.summary': summary,\n 'pickup.location': where,\n 'pickup.hours': hours,\n 'pickup.instructions': instructions,\n },\n subject: `Your order ${number} is ready for pickup`,\n text:\n `Your order ${number} is ready for pickup at ${where}.\\n\\n${summary}` +\n (hours ? `\\n\\n${hours}` : '') +\n (instructions ? `\\n\\n${instructions}` : '') +\n `\\n\\nBring your order number, ${number}.` +\n link,\n sms: `${store}: order ${number} is ready for pickup at ${pickup.locationName}.${smsLink}`,\n timelineDetail: 'Ready-for-pickup notice sent',\n }\n }\n\n if (event === 'out_for_delivery') {\n const delivery = order.localDelivery\n if (order.fulfillmentMethod !== 'local_delivery' || !delivery) return null\n const summary = lines.map(lineLabel).join('\\n')\n const window = delivery.windowLabel ?? ''\n return {\n // Each run out the door is its own message: a failed drop sent out\n // again tells the buyer again.\n occurrence: String(delivery.outForDeliveryAtMs ?? 'order'),\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.out_for_delivery,\n tokens: { ...base, 'order.summary': summary, 'delivery.window': window },\n subject: `Your order ${number} is out for delivery`,\n text:\n `${store} is on the way with order ${number}` +\n (window ? `, in your window ${window}` : '') +\n `.\\n\\n${summary}` +\n link,\n sms: `${store}: order ${number} is out for delivery${window ? ` (${window})` : ''}.${smsLink}`,\n timelineDetail: 'Out-for-delivery notice sent',\n }\n }\n\n if (event === 'refunded') {\n const refundCents = Math.max(0, Math.round(Number(options.refundCents ?? 0)))\n if (!(refundCents > 0)) return null\n const refundedLines = (options.refundLineIndexes ?? [])\n .map((index) => lines[index])\n .filter(Boolean) as CommerceModel.OrderLineItem[]\n const summary = refundedLines.map(lineLabel).join('\\n')\n const note = options.fullyRefunded\n ? 'Your order has been refunded in full.'\n : 'The rest of your order is unchanged.'\n return {\n // The refund's own id is the occurrence; without one, the running\n // total after this refund is (each settled refund raises it).\n occurrence:\n options.refundId ||\n `total-${Number(order.refundedCents ?? 0) || refundCents}`,\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.refunded,\n tokens: {\n ...base,\n 'refund.amount': money(refundCents),\n 'refund.summary': summary,\n 'refund.note': note,\n },\n subject: `Refund for your order ${number}`,\n text:\n `${store} refunded ${money(refundCents)} on order ${number}.` +\n (summary ? `\\n\\n${summary}` : '') +\n `\\n\\n${note}\\n\\nRefunds go back to the original payment method and ` +\n 'usually appear within 5 to 10 business days, depending on your bank.' +\n link,\n sms: `${store}: ${money(refundCents)} refunded on order ${number}.${smsLink}`,\n timelineDetail: `Refund confirmation sent (${money(refundCents)})`,\n }\n }\n\n // cancelled\n const summary = lines.map(lineLabel).join('\\n')\n const wasPaid = Boolean(\n order.paymentIntentId ||\n (order.channel === 'pos' && order.status !== 'pending') ||\n (order.timeline ?? []).some((entry) => entry?.event === 'paid'),\n )\n const note = wasPaid\n ? `Questions about a payment? Reply to this email or contact ${store}.`\n : ''\n return {\n occurrence: 'order',\n emailKey: CommerceModel.BUYER_NOTIFICATION_EMAIL_KEYS.cancelled,\n tokens: { ...base, 'order.summary': summary, 'cancel.note': note },\n subject: `Your order ${number} was canceled`,\n text:\n `${store} canceled order ${number}.\\n\\n${summary}` +\n (note ? `\\n\\n${note}` : '') +\n link,\n sms: `${store}: order ${number} was canceled.${smsLink}`,\n timelineDetail: 'Cancellation notice sent',\n }\n}\n\n/**\n * Claims one message in a transaction. `true` when this caller owns the send;\n * `false` when a marker is already there (sent, or being sent).\n */\nasync function claimMessage(\n firestore: FirebaseFirestore.Firestore,\n orderRef: FirebaseFirestore.DocumentReference,\n marker: string,\n channel: CommerceModel.BuyerNotificationChannel,\n): Promise<boolean> {\n return firestore.runTransaction(async (transaction) => {\n const snapshot = await transaction.get(orderRef)\n if (!snapshot.exists) return false\n const sent = (snapshot.get('buyerNotifications') ?? {}) as Record<\n string,\n unknown\n >\n if (sent[marker]) return false\n transaction.update(orderRef, {\n [`buyerNotifications.${marker}`]: {\n state: 'sending',\n channel,\n atMs: Date.now(),\n },\n })\n return true\n })\n}\n\n/** Marks a claimed message sent and records it on the order timeline. */\nasync function settleMessage(\n firestore: FirebaseFirestore.Firestore,\n orderRef: FirebaseFirestore.DocumentReference,\n marker: string | null,\n channel: CommerceModel.BuyerNotificationChannel,\n detail: string,\n): Promise<void> {\n await firestore\n .runTransaction(async (transaction) => {\n const snapshot = await transaction.get(orderRef)\n if (!snapshot.exists) return\n const order = CommerceModel.liftLegacyOrder(\n (snapshot.data() ?? {}) as never,\n )\n const atMs = Date.now()\n transaction.update(orderRef, {\n ...(marker\n ? {\n [`buyerNotifications.${marker}`]: { state: 'sent', channel, atMs },\n }\n : {}),\n timeline: CommerceModel.appendOrderEvent(\n order,\n 'buyer-notified',\n `${detail} by ${channel === 'sms' ? 'text' : 'email'}`,\n atMs,\n ),\n })\n })\n .catch((error) => {\n console.error('[order-notifications] settle failed', marker, error)\n })\n}\n\nasync function releaseMessage(\n orderRef: FirebaseFirestore.DocumentReference,\n marker: string,\n): Promise<void> {\n await orderRef\n .update({\n [`buyerNotifications.${marker}`]:\n firebaseAdmin.firestore.FieldValue.delete(),\n })\n .catch(() => undefined)\n}\n\n/** How long a buyer email waits on plugins asking to be copied on it. */\nconst EMAIL_COPIES_TIMEOUT_MS = 3_000\n\nasync function deliverEmail(\n store: StoreContext,\n ref: OrderRef,\n to: string,\n message: ComposedMessage,\n context: string,\n /** The moment and order, for plugins copied on it; absent for a merchant-asked resend. */\n about?: { event: OrderBuyerEvent; order: CommerceModel.HostOrder },\n): Promise<void> {\n const designed = await renderHostEmailWithTokens(\n store.firestore,\n ref.hostId,\n message.emailKey,\n message.tokens,\n )\n const org = await getOrgForHost(ref.hostId).catch(() => null)\n // Plugins copied on this email (AGL-3699): a review platform's invitation\n // address, with the data block it reads. Asked through core's seam, by\n // the email's key and moment; nothing answers unless a site connected one.\n const copies = about\n ? await resolvePluginOrderEmailCopies(\n {\n hostId: ref.hostId,\n recordId: ref.orderId,\n emailKey: message.emailKey,\n moment: about.event,\n recipient: to,\n order: orderViewFromData(ref.orderId, about.order as Record<string, any>),\n },\n { timeoutMs: EMAIL_COPIES_TIMEOUT_MS },\n )\n : { bcc: [] as string[], html: '', settle: async () => undefined }\n const subject = designed?.subject ?? message.subject\n const text = designed?.text || message.text\n // A data block lives in the HTML part, so a text-only email gets the same\n // HTML part `sendEmail` would have synthesized, carrying it.\n const html = copies.html\n ? withOrderEmailDataBlocks(designed?.html || renderTextEmailHtml(text, subject), copies.html)\n : designed?.html\n const result = await sendEmail({\n to,\n subject,\n text,\n ...(html ? { html } : {}),\n ...(copies.bcc.length ? { bcc: copies.bcc } : {}),\n fromName: Aglyn.resolveBrandingProfile(org?.org as never).fromName,\n sendingIdentity: await hostSendingIdentity(ref.hostId),\n audience: 'tenant',\n context,\n // Owed to the recipient by their own order: the phishing screen's soft\n // rules never hold it (AGL-3356).\n owedFor: 'order',\n })\n await copies.settle(Boolean(result?.sent))\n // Cost meter (AGL-1438), as every transactional store email.\n await meterHostEmail(ref.hostId)\n}\n\n/**\n * What a text attempt came to: `refused` names why nothing went (null when it\n * went), and `scheduledForMs` says it was held for the buyer's morning.\n */\ninterface SmsDelivery {\n refused: string | null\n scheduledForMs?: number\n}\n\n/**\n * Sends one order text. A receipt goes at once — the buyer is at the counter\n * or the checkout waiting for it. Every later moment (shipped, delivered,\n * refunded, canceled) can fire at any hour, from a carrier scan or a late\n * packing shift, so it asks the provider to keep quiet hours in the STORE's\n * zone: the best stand-in the platform has for a buyer whose own zone it was\n * never told, and right for the local and regional stores most texts come\n * from. Held texts arrive at 8 AM there.\n */\nasync function deliverSms(\n ref: OrderRef,\n to: string,\n message: ComposedMessage,\n context: string,\n quiet: { host: Record<string, unknown> } | null,\n): Promise<SmsDelivery> {\n const sms = pluginSmsMessaging()\n if (!sms?.isConfigured()) return { refused: 'Text messages are not configured' }\n let timeZone: string | null = null\n if (quiet) {\n const owner = await getOrgForHost(ref.hostId).catch(() => null)\n timeZone = resolveSiteTimeZone(\n (owner?.org ?? null) as { timeZone?: string } | null,\n quiet.host as { timeZone?: string },\n )\n }\n const outcome = await sms.send({\n to,\n body: message.sms,\n hostId: ref.hostId,\n purpose: 'transactional',\n context,\n ...(timeZone ? { quietHours: { timeZone } } : {}),\n })\n if (outcome.status !== 'sent') return { refused: outcome.status }\n return {\n refused: null,\n ...(outcome.scheduledForMs ? { scheduledForMs: outcome.scheduledForMs } : {}),\n }\n}\n\n/** Absolute download links for a receipt's digital lines. */\nfunction downloadLinksFor(\n store: StoreContext,\n ref: OrderRef,\n order: CommerceModel.HostOrder,\n): Record<string, string> {\n const origin = Aglyn.hostPublicOrigin({\n cname: typeof store.host['cname'] === 'string' ? store.host['cname'] : null,\n subdomain:\n typeof store.host['subdomain'] === 'string' ? store.host['subdomain'] : null,\n })\n const digital = (order.lineItems ?? []).filter(\n (line) => line.productType === 'digital',\n )\n if (!origin || !digital.length) return {}\n let token: string\n try {\n token = mintDownloadToken(ref.hostId, ref.orderId)\n } catch {\n return {}\n }\n return Object.fromEntries(\n digital.map((line) => [\n line.productId,\n `${origin}/api/commerce/download?hostId=${encodeURIComponent(ref.hostId)}` +\n `&orderId=${encodeURIComponent(ref.orderId)}` +\n `&productId=${encodeURIComponent(line.productId)}&token=${token}`,\n ]),\n )\n}\n\nasync function readOrder(\n firestore: FirebaseFirestore.Firestore,\n ref: OrderRef,\n): Promise<{\n orderRef: FirebaseFirestore.DocumentReference\n order: CommerceModel.HostOrder | null\n}> {\n const orderRef = firestore\n .collection('hosts')\n .doc(ref.hostId)\n .collection('orders')\n .doc(ref.orderId)\n const snapshot = await orderRef.get()\n return {\n orderRef,\n order: snapshot.exists\n ? CommerceModel.liftLegacyOrder((snapshot.data() ?? {}) as never)\n : null,\n }\n}\n\nfunction emailOf(value: unknown): string {\n const email = String(value ?? '').trim()\n return /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email) ? email : ''\n}\n\n/**\n * Tells the buyer about one moment in their order's life, once. See the\n * module header. Never throws.\n */\nexport async function notifyOrderBuyer(\n ref: OrderRef,\n event: OrderBuyerEvent,\n options: NotifyOrderBuyerOptions = {},\n): Promise<NotifyOrderBuyerOutcome> {\n try {\n if (!ref.hostId || !ref.orderId) return { outcome: 'no_such_order' }\n const firestore = options.firestore ?? firebaseAdmin.app().firestore()\n const store = await readStore(firestore, ref.hostId)\n const toggles = store.settings['buyerNotifications']\n if (!CommerceModel.buyerNotificationEnabled(toggles, event)) {\n return { outcome: 'disabled' }\n }\n const { orderRef, order } = await readOrder(firestore, ref)\n if (!order) return { outcome: 'no_such_order' }\n const message = composeOrderBuyerMessage({\n event,\n order,\n orderId: ref.orderId,\n businessName: String(\n resolveHostToken('businessName', store.host as HostTokenSource) ?? '',\n ),\n currency: store.currency,\n statusUrl: statusLink(store, ref),\n receiptFooter: String(store.settings['receiptFooter'] ?? ''),\n downloadLinks:\n event === 'receipt' ? downloadLinksFor(store, ref, order) : undefined,\n options,\n })\n if (!message) return { outcome: 'not_applicable' }\n\n const email = emailOf(order.customerEmail) || emailOf(options.email)\n const phone = String(order.customerPhone ?? '').trim()\n const targets: {\n channel: CommerceModel.BuyerNotificationChannel\n to: string\n }[] = []\n if (email && isEmailConfigured()) targets.push({ channel: 'email', to: email })\n if (\n phone &&\n CommerceModel.buyerNotificationEnabled(toggles, 'texts') &&\n pluginSmsMessaging()?.isConfigured()\n ) {\n targets.push({ channel: 'sms', to: phone })\n }\n if (!targets.length) return { outcome: 'no_recipient' }\n\n const channels: OrderBuyerChannelOutcome[] = []\n for (const target of targets) {\n const marker = CommerceModel.buyerNotificationMarker(\n event,\n message.occurrence,\n target.channel,\n )\n const claimed = await claimMessage(\n firestore,\n orderRef,\n marker,\n target.channel,\n ).catch((error) => {\n console.error('[order-notifications] claim failed', marker, error)\n return false\n })\n if (!claimed) {\n channels.push({ channel: target.channel, outcome: 'already' })\n continue\n }\n const context = message.emailKey\n let held: number | null = null\n try {\n if (target.channel === 'email') {\n await deliverEmail(store, ref, target.to, message, context, { event, order })\n } else {\n const sent = await deliverSms(\n ref,\n target.to,\n message,\n context,\n event === 'receipt' ? null : { host: store.host },\n )\n if (sent.refused) throw new Error(sent.refused)\n held = sent.scheduledForMs ?? null\n }\n await settleMessage(\n firestore,\n orderRef,\n marker,\n target.channel,\n held\n ? `${message.timelineDetail} (held for the morning)`\n : message.timelineDetail,\n )\n channels.push({ channel: target.channel, outcome: 'sent' })\n } catch (error) {\n await releaseMessage(orderRef, marker)\n console.error('[order-notifications] send failed', {\n hostId: ref.hostId,\n orderId: ref.orderId,\n event,\n channel: target.channel,\n error,\n })\n channels.push({\n channel: target.channel,\n outcome: 'failed',\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n return { outcome: 'handled', channels }\n } catch (error) {\n console.error('[order-notifications] notify failed', ref, event, error)\n return { outcome: 'handled', channels: [] }\n }\n}\n\nexport type SendOrderReceiptOutcome =\n | { outcome: 'sent'; channel: CommerceModel.BuyerNotificationChannel }\n | { outcome: 'no_such_order' }\n | { outcome: 'not_configured' }\n | { outcome: 'invalid_recipient' }\n | { outcome: 'failed'; error: string }\n\n/**\n * Sends the receipt again, on the merchant's say-so, to the address or number\n * they gave (AGL-3610) — the order dialog's \"Resend receipt\".\n *\n * NOT idempotent, on purpose: a resend IS the second send. The route in front\n * of it is rate-limited instead, and the send is recorded on the timeline so\n * the merchant can see it went. It does not consult the store's moment\n * switches either — a merchant who turned automatic receipts off can still\n * hand one to a customer who asks.\n */\nexport async function sendOrderReceipt(\n ref: OrderRef,\n input: {\n channel: CommerceModel.BuyerNotificationChannel\n to: string\n firestore?: FirebaseFirestore.Firestore\n },\n): Promise<SendOrderReceiptOutcome> {\n try {\n const firestore = input.firestore ?? firebaseAdmin.app().firestore()\n const { orderRef, order } = await readOrder(firestore, ref)\n if (!order) return { outcome: 'no_such_order' }\n if (input.channel === 'email') {\n if (!isEmailConfigured()) return { outcome: 'not_configured' }\n if (!emailOf(input.to)) return { outcome: 'invalid_recipient' }\n } else {\n if (!pluginSmsMessaging()?.isConfigured()) {\n return { outcome: 'not_configured' }\n }\n if (!Aglyn.normalizePhone(input.to)) {\n return { outcome: 'invalid_recipient' }\n }\n }\n const store = await readStore(firestore, ref.hostId)\n const message = composeOrderBuyerMessage({\n event: 'receipt',\n order,\n orderId: ref.orderId,\n businessName: String(\n resolveHostToken('businessName', store.host as HostTokenSource) ?? '',\n ),\n currency: store.currency,\n statusUrl: statusLink(store, ref),\n receiptFooter: String(store.settings['receiptFooter'] ?? ''),\n downloadLinks: downloadLinksFor(store, ref, order),\n })\n if (!message) return { outcome: 'failed', error: 'Nothing to send' }\n if (input.channel === 'email') {\n await deliverEmail(store, ref, emailOf(input.to), message, 'order-receipt')\n } else {\n const sent = await deliverSms(ref, input.to, message, 'order-receipt', null)\n if (sent.refused) return { outcome: 'failed', error: sent.refused }\n }\n await settleMessage(firestore, orderRef, null, input.channel, 'Receipt re-sent')\n return { outcome: 'sent', channel: input.channel }\n } catch (error) {\n console.error('[order-notifications] receipt send failed', ref, error)\n return {\n outcome: 'failed',\n error: error instanceof Error ? error.message : String(error),\n }\n }\n}\n\n/**\n * What the online receipt sends needs from this module (AGL-3610): whether\n * the store left receipts on, and the `order.number` / `order.statusUrl`\n * tokens. The cart and buy-now receipts compose their own summary (license\n * keys and download links built from the Stripe session) inside the\n * webhook's redelivery guard, so they ask here rather than route through\n * {@link notifyOrderBuyer}.\n */\nexport async function onlineReceiptExtras(\n hostId: string,\n orderId: string,\n firestore: FirebaseFirestore.Firestore = firebaseAdmin.app().firestore(),\n): Promise<{ enabled: boolean; tokens: Record<string, string> }> {\n try {\n const store = await readStore(firestore, hostId)\n const { order } = await readOrder(firestore, { hostId, orderId })\n return {\n enabled: CommerceModel.buyerNotificationEnabled(\n store.settings['buyerNotifications'],\n 'receipt',\n ),\n tokens: {\n 'order.number': CommerceModel.formatOrderNumber(order ?? {}, orderId),\n 'order.statusUrl': statusLink(store, { hostId, orderId }),\n },\n }\n } catch (error) {\n console.error('[order-notifications] receipt extras failed', error)\n return { enabled: true, tokens: { 'order.number': '', 'order.statusUrl': '' } }\n }\n}\n"],"names":["Aglyn","pluginSmsMessaging","resolveSiteTimeZone","resolveHostToken","firebaseAdmin","getOrgForHost","hostSendingIdentity","meterHostEmail","renderHostEmailWithTokens","isEmailConfigured","renderTextEmailHtml","sendEmail","resolvePluginOrderEmailCopies","withOrderEmailDataBlocks","CommerceModel","orderViewFromData","mintDownloadToken","orderStatusUrl","readStore","firestore","hostId","settings","settingsSnapshot","hostSnapshot","hostRef","collection","doc","Promise","all","get","catch","data","host","currency","String","lineLabel","line","quantity","name","variantLabel","statusLink","store","ref","orderId","hostPublicOrigin","cname","subdomain","composeOrderBuyerMessage","input","order","event","businessName","statusUrl","options","lines","lineItems","number","formatOrderNumber","money","cents","formatOrderMoney","base","link","smsLink","totalCents","totals","Number","amountCents","licenseText","Object","entries","licenseKeys","flatMap","productId","keys","find","item","map","key","join","downloads","downloadLinks","url","linesText","unitAmountCents","summary","filter","Boolean","footer","receiptFooter","occurrence","emailKey","BUYER_NOTIFICATION_EMAIL_KEYS","receipt","tokens","subject","text","sms","timelineDetail","fulfillment","fulfillments","fulfillmentId","entry","id","sort","a","b","atMs","handover","shipped","fulfillmentLineQuantities","lineItemId","tracking","fulfillmentTrackingUrl","hasTracking","trackingNumber","physical","some","productType","channel","remainingUnits","remainingFulfillmentLines","reduce","sum","carrier","carrierLabelFor","trackingSentence","remainingSentence","delivered","pickup","fulfillmentMethod","where","locationName","address","hours","instructions","picked_up","ready_for_pickup","delivery","localDelivery","window","windowLabel","outForDeliveryAtMs","out_for_delivery","refundCents","Math","max","round","refundedLines","refundLineIndexes","index","note","fullyRefunded","refundId","refundedCents","refunded","wasPaid","paymentIntentId","status","timeline","cancelled","claimMessage","orderRef","marker","runTransaction","transaction","snapshot","exists","sent","update","state","Date","now","settleMessage","detail","liftLegacyOrder","appendOrderEvent","error","console","releaseMessage","FieldValue","delete","undefined","EMAIL_COPIES_TIMEOUT_MS","deliverEmail","to","message","context","about","designed","org","copies","recordId","moment","recipient","timeoutMs","bcc","html","settle","result","length","fromName","resolveBrandingProfile","sendingIdentity","audience","owedFor","deliverSms","quiet","isConfigured","refused","timeZone","owner","outcome","send","body","purpose","quietHours","scheduledForMs","downloadLinksFor","origin","digital","token","fromEntries","encodeURIComponent","readOrder","emailOf","value","email","trim","test","notifyOrderBuyer","app","toggles","buyerNotificationEnabled","customerEmail","phone","customerPhone","targets","push","channels","target","buyerNotificationMarker","claimed","held","Error","sendOrderReceipt","normalizePhone","onlineReceiptExtras","enabled"],"mappings":"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"}
@@ -22,6 +22,7 @@ import { checkoutCreditProvider, checkoutCreditProviderForCode, normalizeCheckou
22
22
  import { finishPosDisplayReceipt } from "./pos-display.js";
23
23
  import * as CommerceModel from "../model/index.js";
24
24
  import { posRegisterSettings } from "../plugin-config.js";
25
+ import { posOpsSettings } from "../pos-ops-config.js";
25
26
  import { authorizePosStaff, posIdempotencyKey, posQueryBody, posRequestBody } from "./pos-auth.js";
26
27
  import { applyPosPayment, deliverChosenPosReceipt, posPaymentId, posSaleSummary, readPosSale, releasePosSaleDiscount } from "./pos-sale.js";
27
28
  import { posPaymentCashierId } from "./pos-sale-stamp.js";
@@ -78,10 +79,15 @@ import { cancelPosCardPayment, createCardPresentIntent, refreshPosCardPayment, r
78
79
  livemode: Boolean(doc.get('livemode'))
79
80
  };
80
81
  }) : [];
82
+ const config = await getPluginConfig(staff.orgId || undefined, 'commerce', {
83
+ hostId
84
+ });
81
85
  return res.status(200).json({
82
- settings: posRegisterSettings(await getPluginConfig(staff.orgId || undefined, 'commerce', {
83
- hostId
84
- })),
86
+ settings: posRegisterSettings(config),
87
+ // The register's own rules (AGL-3609): the shift it requires, the
88
+ // idle lock and the refund limit. The console reads the same config
89
+ // client-side; the native apps read it here.
90
+ ops: posOpsSettings(config),
85
91
  terminal: {
86
92
  available: posTerminalAvailable(),
87
93
  testMode: posStripeTestMode()