@aglyn/plugins-commerce 1.0.0-beta.233 → 1.0.0-beta.235

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
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type PluginApiHandler } from '@aglyn/aglyn/server';
18
+ import { type ProductsWriteDeps } from './products-write';
19
+ /** Adds a note to an order's timeline. */
20
+ export declare function createOrderNoteHandler(options?: ProductsWriteDeps): PluginApiHandler;
21
+ /**
22
+ * Answers an order's open restock question. `flaggedAtMs` names the question
23
+ * the person saw; an answer never lands on a newer one. It moves no stock, as
24
+ * in the console: it only clears the question.
25
+ */
26
+ export declare function createOrderRestockAnswerHandler(options?: ProductsWriteDeps): PluginApiHandler;
27
+ export declare const orderNoteHandler: PluginApiHandler;
28
+ export declare const orderRestockAnswerHandler: PluginApiHandler;
@@ -0,0 +1,143 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { liftLegacyOrder, orderNoteUpdate, restockAnswer } from "../model/index.js";
17
+ import { bodyOf, gate, resolved, validId } from "./products-write.js";
18
+ function missing(res) {
19
+ return res.status(400).json({
20
+ error: 'Missing site or order'
21
+ });
22
+ }
23
+ /** Adds a note to an order's timeline. */ export function createOrderNoteHandler(options = {}) {
24
+ return async (req, res)=>{
25
+ if (req.method !== 'POST') return res.status(405).json({
26
+ error: 'Method not allowed'
27
+ });
28
+ const deps = resolved(options);
29
+ const body = bodyOf(req);
30
+ const { hostId, orderId } = body;
31
+ if (!validId(hostId) || !validId(orderId)) return missing(res);
32
+ if (typeof body['note'] !== 'string' || !body['note'].trim()) return res.status(400).json({
33
+ error: 'Write a note first'
34
+ });
35
+ try {
36
+ const door = await gate(req, res, hostId, deps);
37
+ if (!door) return;
38
+ const db = deps.firestore();
39
+ const orderRef = db.collection('hosts').doc(hostId).collection('orders').doc(orderId);
40
+ const answer = await db.runTransaction(async (tx)=>{
41
+ var _stored_data;
42
+ const stored = await tx.get(orderRef);
43
+ if (!stored.exists) return {
44
+ status: 404,
45
+ body: {
46
+ error: 'This order no longer exists'
47
+ }
48
+ };
49
+ const order = liftLegacyOrder((_stored_data = stored.data()) != null ? _stored_data : {});
50
+ const update = orderNoteUpdate(order, body['note'], deps.now());
51
+ if (!update) return {
52
+ status: 400,
53
+ body: {
54
+ error: 'Write a note first'
55
+ }
56
+ };
57
+ tx.update(orderRef, update);
58
+ return {
59
+ status: 200,
60
+ body: {
61
+ ok: true,
62
+ timeline: update.timeline
63
+ }
64
+ };
65
+ });
66
+ return res.status(answer.status).json(answer.body);
67
+ } catch (error) {
68
+ console.error('[commerce/order-note]', error);
69
+ return res.status(500).json({
70
+ error: 'The note could not be added'
71
+ });
72
+ }
73
+ };
74
+ }
75
+ const RESOLUTIONS = [
76
+ 'restocked',
77
+ 'dismissed'
78
+ ];
79
+ /**
80
+ * Answers an order's open restock question. `flaggedAtMs` names the question
81
+ * the person saw; an answer never lands on a newer one. It moves no stock, as
82
+ * in the console: it only clears the question.
83
+ */ export function createOrderRestockAnswerHandler(options = {}) {
84
+ return async (req, res)=>{
85
+ if (req.method !== 'POST') return res.status(405).json({
86
+ error: 'Method not allowed'
87
+ });
88
+ const deps = resolved(options);
89
+ const body = bodyOf(req);
90
+ const { hostId, orderId } = body;
91
+ if (!validId(hostId) || !validId(orderId)) return missing(res);
92
+ const resolution = body['resolution'];
93
+ const flaggedAtMs = Number(body['flaggedAtMs']);
94
+ if (!RESOLUTIONS.includes(resolution) || !Number.isFinite(flaggedAtMs)) {
95
+ return res.status(400).json({
96
+ error: 'Choose restocked or no restock'
97
+ });
98
+ }
99
+ try {
100
+ const door = await gate(req, res, hostId, deps);
101
+ if (!door) return;
102
+ const db = deps.firestore();
103
+ const orderRef = db.collection('hosts').doc(hostId).collection('orders').doc(orderId);
104
+ const answer = await db.runTransaction(async (tx)=>{
105
+ var _stored_data;
106
+ const stored = await tx.get(orderRef);
107
+ if (!stored.exists) return {
108
+ status: 404,
109
+ body: {
110
+ error: 'This order no longer exists'
111
+ }
112
+ };
113
+ const order = liftLegacyOrder((_stored_data = stored.data()) != null ? _stored_data : {});
114
+ const verdict = restockAnswer(order, flaggedAtMs, resolution, door.uid, deps.now());
115
+ if (verdict.verdict !== 'recorded') return {
116
+ status: 200,
117
+ body: {
118
+ ok: true,
119
+ verdict: verdict.verdict
120
+ }
121
+ };
122
+ tx.update(orderRef, verdict.update);
123
+ return {
124
+ status: 200,
125
+ body: {
126
+ ok: true,
127
+ verdict: 'recorded'
128
+ }
129
+ };
130
+ });
131
+ return res.status(answer.status).json(answer.body);
132
+ } catch (error) {
133
+ console.error('[commerce/order-restock-answer]', error);
134
+ return res.status(500).json({
135
+ error: 'The answer could not be recorded'
136
+ });
137
+ }
138
+ };
139
+ }
140
+ export const orderNoteHandler = createOrderNoteHandler();
141
+ export const orderRestockAnswerHandler = createOrderRestockAnswerHandler();
142
+
143
+ //# sourceMappingURL=order-annotations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/order-annotations.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { type PluginApiHandler } from '@aglyn/aglyn/server'\nimport { liftLegacyOrder, orderNoteUpdate, restockAnswer, type RestockResolution } from '../model'\nimport { bodyOf, gate, resolved, validId, type ProductsWriteDeps } from './products-write'\n\n/*\n * `POST /api/commerce/order-note` and `POST /api/commerce/order-restock-answer`\n * (AGL-3651, AGL-3652): the order dialog's \"Add note\" and its restock\n * question, for the native apps.\n *\n * The console makes both writes from the browser, as an `updateDoc` of the\n * order's `timeline` (and `restockCheck`), the only two fields the Firestore\n * rules let a member write on an order. Both are read-then-append, so a\n * stale seed would drop an event another tab wrote. These routes run the\n * console's own computation (`model/order-annotations.ts`, which the console\n * imports too) inside a transaction on the stored order, under the gate the\n * rules put on that write: a verified member with a write role on the site,\n * or staff, while the site, its org and the platform are not frozen.\n */\n\ntype Reply = Parameters<PluginApiHandler>[1]\n\nfunction missing(res: Reply) {\n return res.status(400).json({ error: 'Missing site or order' })\n}\n\n/** Adds a note to an order's timeline. */\nexport function createOrderNoteHandler(options: ProductsWriteDeps = {}): PluginApiHandler {\n return async (req, res) => {\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })\n const deps = resolved(options)\n const body = bodyOf(req)\n const { hostId, orderId } = body\n if (!validId(hostId) || !validId(orderId)) return missing(res)\n if (typeof body['note'] !== 'string' || !body['note'].trim()) return res.status(400).json({ error: 'Write a note first' })\n try {\n const door = await gate(req, res, hostId, deps)\n if (!door) return\n const db = deps.firestore()\n const orderRef = db.collection('hosts').doc(hostId).collection('orders').doc(orderId)\n const answer = await db.runTransaction(async (tx) => {\n const stored = await tx.get(orderRef)\n if (!stored.exists) return { status: 404, body: { error: 'This order no longer exists' } }\n const order = liftLegacyOrder((stored.data() ?? {}) as never)\n const update = orderNoteUpdate(order, body['note'] as string, deps.now())\n if (!update) return { status: 400, body: { error: 'Write a note first' } }\n tx.update(orderRef, update)\n return { status: 200, body: { ok: true, timeline: update.timeline } }\n })\n return res.status(answer.status).json(answer.body)\n } catch (error) {\n console.error('[commerce/order-note]', error)\n return res.status(500).json({ error: 'The note could not be added' })\n }\n }\n}\n\nconst RESOLUTIONS: readonly RestockResolution[] = ['restocked', 'dismissed']\n\n/**\n * Answers an order's open restock question. `flaggedAtMs` names the question\n * the person saw; an answer never lands on a newer one. It moves no stock, as\n * in the console: it only clears the question.\n */\nexport function createOrderRestockAnswerHandler(options: ProductsWriteDeps = {}): PluginApiHandler {\n return async (req, res) => {\n if (req.method !== 'POST') return res.status(405).json({ error: 'Method not allowed' })\n const deps = resolved(options)\n const body = bodyOf(req)\n const { hostId, orderId } = body\n if (!validId(hostId) || !validId(orderId)) return missing(res)\n const resolution = body['resolution'] as RestockResolution\n const flaggedAtMs = Number(body['flaggedAtMs'])\n if (!RESOLUTIONS.includes(resolution) || !Number.isFinite(flaggedAtMs)) {\n return res.status(400).json({ error: 'Choose restocked or no restock' })\n }\n try {\n const door = await gate(req, res, hostId, deps)\n if (!door) return\n const db = deps.firestore()\n const orderRef = db.collection('hosts').doc(hostId).collection('orders').doc(orderId)\n const answer = await db.runTransaction(async (tx) => {\n const stored = await tx.get(orderRef)\n if (!stored.exists) return { status: 404, body: { error: 'This order no longer exists' } }\n const order = liftLegacyOrder((stored.data() ?? {}) as never)\n const verdict = restockAnswer(order, flaggedAtMs, resolution, door.uid, deps.now())\n if (verdict.verdict !== 'recorded') return { status: 200, body: { ok: true, verdict: verdict.verdict } }\n tx.update(orderRef, verdict.update)\n return { status: 200, body: { ok: true, verdict: 'recorded' } }\n })\n return res.status(answer.status).json(answer.body)\n } catch (error) {\n console.error('[commerce/order-restock-answer]', error)\n return res.status(500).json({ error: 'The answer could not be recorded' })\n }\n }\n}\n\nexport const orderNoteHandler = createOrderNoteHandler()\nexport const orderRestockAnswerHandler = createOrderRestockAnswerHandler()\n"],"names":["liftLegacyOrder","orderNoteUpdate","restockAnswer","bodyOf","gate","resolved","validId","missing","res","status","json","error","createOrderNoteHandler","options","req","method","deps","body","hostId","orderId","trim","door","db","firestore","orderRef","collection","doc","answer","runTransaction","tx","stored","get","exists","order","data","update","now","ok","timeline","console","RESOLUTIONS","createOrderRestockAnswerHandler","resolution","flaggedAtMs","Number","includes","isFinite","verdict","uid","orderNoteHandler","orderRestockAnswerHandler"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SAASA,eAAe,EAAEC,eAAe,EAAEC,aAAa,QAAgC,oBAAU;AAClG,SAASC,MAAM,EAAEC,IAAI,EAAEC,QAAQ,EAAEC,OAAO,QAAgC,sBAAkB;AAmB1F,SAASC,QAAQC,GAAU;IACzB,OAAOA,IAAIC,MAAM,CAAC,KAAKC,IAAI,CAAC;QAAEC,OAAO;IAAwB;AAC/D;AAEA,wCAAwC,GACxC,OAAO,SAASC,uBAAuBC,UAA6B,CAAC,CAAC;IACpE,OAAO,OAAOC,KAAKN;QACjB,IAAIM,IAAIC,MAAM,KAAK,QAAQ,OAAOP,IAAIC,MAAM,CAAC,KAAKC,IAAI,CAAC;YAAEC,OAAO;QAAqB;QACrF,MAAMK,OAAOX,SAASQ;QACtB,MAAMI,OAAOd,OAAOW;QACpB,MAAM,EAAEI,MAAM,EAAEC,OAAO,EAAE,GAAGF;QAC5B,IAAI,CAACX,QAAQY,WAAW,CAACZ,QAAQa,UAAU,OAAOZ,QAAQC;QAC1D,IAAI,OAAOS,IAAI,CAAC,OAAO,KAAK,YAAY,CAACA,IAAI,CAAC,OAAO,CAACG,IAAI,IAAI,OAAOZ,IAAIC,MAAM,CAAC,KAAKC,IAAI,CAAC;YAAEC,OAAO;QAAqB;QACxH,IAAI;YACF,MAAMU,OAAO,MAAMjB,KAAKU,KAAKN,KAAKU,QAAQF;YAC1C,IAAI,CAACK,MAAM;YACX,MAAMC,KAAKN,KAAKO,SAAS;YACzB,MAAMC,WAAWF,GAAGG,UAAU,CAAC,SAASC,GAAG,CAACR,QAAQO,UAAU,CAAC,UAAUC,GAAG,CAACP;YAC7E,MAAMQ,SAAS,MAAML,GAAGM,cAAc,CAAC,OAAOC;oBAGbC;gBAF/B,MAAMA,SAAS,MAAMD,GAAGE,GAAG,CAACP;gBAC5B,IAAI,CAACM,OAAOE,MAAM,EAAE,OAAO;oBAAEvB,QAAQ;oBAAKQ,MAAM;wBAAEN,OAAO;oBAA8B;gBAAE;gBACzF,MAAMsB,QAAQjC,iBAAiB8B,eAAAA,OAAOI,IAAI,cAAXJ,eAAiB,CAAC;gBACjD,MAAMK,SAASlC,gBAAgBgC,OAAOhB,IAAI,CAAC,OAAO,EAAYD,KAAKoB,GAAG;gBACtE,IAAI,CAACD,QAAQ,OAAO;oBAAE1B,QAAQ;oBAAKQ,MAAM;wBAAEN,OAAO;oBAAqB;gBAAE;gBACzEkB,GAAGM,MAAM,CAACX,UAAUW;gBACpB,OAAO;oBAAE1B,QAAQ;oBAAKQ,MAAM;wBAAEoB,IAAI;wBAAMC,UAAUH,OAAOG,QAAQ;oBAAC;gBAAE;YACtE;YACA,OAAO9B,IAAIC,MAAM,CAACkB,OAAOlB,MAAM,EAAEC,IAAI,CAACiB,OAAOV,IAAI;QACnD,EAAE,OAAON,OAAO;YACd4B,QAAQ5B,KAAK,CAAC,yBAAyBA;YACvC,OAAOH,IAAIC,MAAM,CAAC,KAAKC,IAAI,CAAC;gBAAEC,OAAO;YAA8B;QACrE;IACF;AACF;AAEA,MAAM6B,cAA4C;IAAC;IAAa;CAAY;AAE5E;;;;CAIC,GACD,OAAO,SAASC,gCAAgC5B,UAA6B,CAAC,CAAC;IAC7E,OAAO,OAAOC,KAAKN;QACjB,IAAIM,IAAIC,MAAM,KAAK,QAAQ,OAAOP,IAAIC,MAAM,CAAC,KAAKC,IAAI,CAAC;YAAEC,OAAO;QAAqB;QACrF,MAAMK,OAAOX,SAASQ;QACtB,MAAMI,OAAOd,OAAOW;QACpB,MAAM,EAAEI,MAAM,EAAEC,OAAO,EAAE,GAAGF;QAC5B,IAAI,CAACX,QAAQY,WAAW,CAACZ,QAAQa,UAAU,OAAOZ,QAAQC;QAC1D,MAAMkC,aAAazB,IAAI,CAAC,aAAa;QACrC,MAAM0B,cAAcC,OAAO3B,IAAI,CAAC,cAAc;QAC9C,IAAI,CAACuB,YAAYK,QAAQ,CAACH,eAAe,CAACE,OAAOE,QAAQ,CAACH,cAAc;YACtE,OAAOnC,IAAIC,MAAM,CAAC,KAAKC,IAAI,CAAC;gBAAEC,OAAO;YAAiC;QACxE;QACA,IAAI;YACF,MAAMU,OAAO,MAAMjB,KAAKU,KAAKN,KAAKU,QAAQF;YAC1C,IAAI,CAACK,MAAM;YACX,MAAMC,KAAKN,KAAKO,SAAS;YACzB,MAAMC,WAAWF,GAAGG,UAAU,CAAC,SAASC,GAAG,CAACR,QAAQO,UAAU,CAAC,UAAUC,GAAG,CAACP;YAC7E,MAAMQ,SAAS,MAAML,GAAGM,cAAc,CAAC,OAAOC;oBAGbC;gBAF/B,MAAMA,SAAS,MAAMD,GAAGE,GAAG,CAACP;gBAC5B,IAAI,CAACM,OAAOE,MAAM,EAAE,OAAO;oBAAEvB,QAAQ;oBAAKQ,MAAM;wBAAEN,OAAO;oBAA8B;gBAAE;gBACzF,MAAMsB,QAAQjC,iBAAiB8B,eAAAA,OAAOI,IAAI,cAAXJ,eAAiB,CAAC;gBACjD,MAAMiB,UAAU7C,cAAc+B,OAAOU,aAAaD,YAAYrB,KAAK2B,GAAG,EAAEhC,KAAKoB,GAAG;gBAChF,IAAIW,QAAQA,OAAO,KAAK,YAAY,OAAO;oBAAEtC,QAAQ;oBAAKQ,MAAM;wBAAEoB,IAAI;wBAAMU,SAASA,QAAQA,OAAO;oBAAC;gBAAE;gBACvGlB,GAAGM,MAAM,CAACX,UAAUuB,QAAQZ,MAAM;gBAClC,OAAO;oBAAE1B,QAAQ;oBAAKQ,MAAM;wBAAEoB,IAAI;wBAAMU,SAAS;oBAAW;gBAAE;YAChE;YACA,OAAOvC,IAAIC,MAAM,CAACkB,OAAOlB,MAAM,EAAEC,IAAI,CAACiB,OAAOV,IAAI;QACnD,EAAE,OAAON,OAAO;YACd4B,QAAQ5B,KAAK,CAAC,mCAAmCA;YACjD,OAAOH,IAAIC,MAAM,CAAC,KAAKC,IAAI,CAAC;gBAAEC,OAAO;YAAmC;QAC1E;IACF;AACF;AAEA,OAAO,MAAMsC,mBAAmBrC,yBAAwB;AACxD,OAAO,MAAMsC,4BAA4BT,kCAAiC"}
@@ -35,7 +35,7 @@ import { orderViewFromData } from "./api-v1/order-view.js";
35
35
  * Neither ever throws into the write it reports: a fact that happened is not
36
36
  * undone because its announcement could not be queued.
37
37
  */ /** A shipment in the event's words. */ export function fulfillmentEventView(order, fulfillment) {
38
- var _fulfillment_carrier, _fulfillment_trackingNumber, _fulfillment_labelUrl;
38
+ var _fulfillment_carrier, _fulfillment_trackingNumber, _fulfillment_labelUrl, _fulfillment_labelCostCents;
39
39
  return {
40
40
  id: fulfillment.id,
41
41
  lines: fulfillmentLineQuantities(order, fulfillment),
@@ -43,6 +43,7 @@ import { orderViewFromData } from "./api-v1/order-view.js";
43
43
  trackingNumber: (_fulfillment_trackingNumber = fulfillment.trackingNumber) != null ? _fulfillment_trackingNumber : null,
44
44
  trackingUrl: fulfillmentTrackingUrl(fulfillment),
45
45
  labelUrl: (_fulfillment_labelUrl = fulfillment.labelUrl) != null ? _fulfillment_labelUrl : null,
46
+ labelCostCents: (_fulfillment_labelCostCents = fulfillment.labelCostCents) != null ? _fulfillment_labelCostCents : null,
46
47
  at: new Date(fulfillment.atMs).toISOString()
47
48
  };
48
49
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/order-events.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { PluginDomainEvent } from '@aglyn/aglyn/plugin-manager/plugin-domain-events'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport {\n type PluginEventWriter,\n raisePluginEvent,\n stagePluginEvent,\n} from '@aglyn/tenant-data-admin/server/plugin-event-outbox'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport type { OrderFulfillment } from '../model/commerce-orders'\nimport {\n CHECKOUT_STARTED_EVENT,\n type CheckoutStartedEventPayload,\n} from '../model/order-events'\nimport type {\n OrderEventFulfillment,\n OrderEventOrder,\n OrderEventPayload,\n} from '../model/order-events'\nimport { fulfillmentLineQuantities } from '../model/order-fulfillment'\nimport { fulfillmentTrackingUrl } from '../model/tracking-url'\nimport { orderViewFromData } from './api-v1/order-view'\n\n/**\n * Raising commerce's order events (AGL-3611). Two doors, one per kind of\n * caller, so every status-change site raises its event in one line:\n *\n * - `stageOrderEvent` rides the caller's transaction, so the event exists\n * exactly when the status does. For a write that already holds the order\n * it is about to store.\n * - `raiseOrderEvent` reads the order once the write has landed and queues\n * the event, keyed so a redelivered webhook or a retried request raises it\n * once. For a write with no transaction to ride.\n *\n * Neither ever throws into the write it reports: a fact that happened is not\n * undone because its announcement could not be queued.\n */\n\n/** A shipment in the event's words. */\nexport function fulfillmentEventView(\n order: { lineItems?: unknown },\n fulfillment: OrderFulfillment,\n): OrderEventFulfillment {\n return {\n id: fulfillment.id,\n lines: fulfillmentLineQuantities(order as never, fulfillment),\n carrier: fulfillment.carrier ?? null,\n trackingNumber: fulfillment.trackingNumber ?? null,\n trackingUrl: fulfillmentTrackingUrl(fulfillment),\n labelUrl: fulfillment.labelUrl ?? null,\n at: new Date(fulfillment.atMs).toISOString(),\n }\n}\n\nexport interface OrderEventRequest<Payload extends OrderEventPayload> {\n hostId: string\n orderId: string\n /** The occurrence: the same key raises the event once. */\n key: string\n /** Everything the payload carries beside `order`. */\n extra?: Omit<Payload, 'order'>\n}\n\n/**\n * Stages an event inside the caller's transaction, from the order data the\n * transaction is about to store (the read merged with its patch).\n */\nexport function stageOrderEvent<Payload extends OrderEventPayload>(\n writer: PluginEventWriter,\n event: PluginDomainEvent<Payload>,\n request: OrderEventRequest<Payload> & { order: Record<string, unknown> },\n): void {\n try {\n const firestore = firebaseAdmin.app().firestore()\n stagePluginEvent(writer, firestore, {\n event,\n pluginId: BUNDLE_ID,\n hostId: request.hostId,\n key: `${request.orderId}:${request.key}`,\n payload: {\n order: orderViewFromData(request.orderId, request.order) as OrderEventOrder,\n ...(request.extra ?? {}),\n },\n })\n } catch (error) {\n console.error(`[commerce] ${event.id} not staged for ${request.orderId}`, error)\n }\n}\n\n/** Queues an event after the write, reading the order as it now stands. */\nexport async function raiseOrderEvent<Payload extends OrderEventPayload>(\n event: PluginDomainEvent<Payload>,\n request: OrderEventRequest<Payload>,\n): Promise<void> {\n try {\n const firestore = firebaseAdmin.app().firestore()\n const snapshot = await firestore\n .collection('hosts')\n .doc(request.hostId)\n .collection('orders')\n .doc(request.orderId)\n .get()\n if (!snapshot.exists) return\n await raisePluginEvent(firestore, {\n event,\n pluginId: BUNDLE_ID,\n hostId: request.hostId,\n key: `${request.orderId}:${request.key}`,\n payload: {\n order: orderViewFromData(request.orderId, snapshot.data() ?? {}) as OrderEventOrder,\n ...(request.extra ?? {}),\n },\n })\n } catch (error) {\n console.error(`[commerce] ${event.id} not raised for ${request.orderId}`, error)\n }\n}\n\n/**\n * Raises `checkout.started` (AGL-3639) for a checkout a shopper reached with\n * an address, keyed by the session so a retried request raises it once.\n * Never throws into the checkout it reports.\n */\nexport async function raiseCheckoutStarted(\n hostId: string,\n checkout: CheckoutStartedEventPayload['checkout'],\n): Promise<void> {\n if (!checkout.email) return\n try {\n await raisePluginEvent(firebaseAdmin.app().firestore(), {\n event: CHECKOUT_STARTED_EVENT,\n pluginId: BUNDLE_ID,\n hostId,\n key: `checkout:${checkout.id}`,\n payload: { checkout },\n })\n } catch (error) {\n console.error(`[commerce] checkout.started not raised for ${checkout.id}`, error)\n }\n}\n"],"names":["firebaseAdmin","raisePluginEvent","stagePluginEvent","BUNDLE_ID","CHECKOUT_STARTED_EVENT","fulfillmentLineQuantities","fulfillmentTrackingUrl","orderViewFromData","fulfillmentEventView","order","fulfillment","id","lines","carrier","trackingNumber","trackingUrl","labelUrl","at","Date","atMs","toISOString","stageOrderEvent","writer","event","request","firestore","app","pluginId","hostId","key","orderId","payload","extra","error","console","raiseOrderEvent","snapshot","collection","doc","get","exists","data","raiseCheckoutStarted","checkout","email"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/order-events.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { PluginDomainEvent } from '@aglyn/aglyn/plugin-manager/plugin-domain-events'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport {\n type PluginEventWriter,\n raisePluginEvent,\n stagePluginEvent,\n} from '@aglyn/tenant-data-admin/server/plugin-event-outbox'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport type { OrderFulfillment } from '../model/commerce-orders'\nimport {\n CHECKOUT_STARTED_EVENT,\n type CheckoutStartedEventPayload,\n} from '../model/order-events'\nimport type {\n OrderEventFulfillment,\n OrderEventOrder,\n OrderEventPayload,\n} from '../model/order-events'\nimport { fulfillmentLineQuantities } from '../model/order-fulfillment'\nimport { fulfillmentTrackingUrl } from '../model/tracking-url'\nimport { orderViewFromData } from './api-v1/order-view'\n\n/**\n * Raising commerce's order events (AGL-3611). Two doors, one per kind of\n * caller, so every status-change site raises its event in one line:\n *\n * - `stageOrderEvent` rides the caller's transaction, so the event exists\n * exactly when the status does. For a write that already holds the order\n * it is about to store.\n * - `raiseOrderEvent` reads the order once the write has landed and queues\n * the event, keyed so a redelivered webhook or a retried request raises it\n * once. For a write with no transaction to ride.\n *\n * Neither ever throws into the write it reports: a fact that happened is not\n * undone because its announcement could not be queued.\n */\n\n/** A shipment in the event's words. */\nexport function fulfillmentEventView(\n order: { lineItems?: unknown },\n fulfillment: OrderFulfillment,\n): OrderEventFulfillment {\n return {\n id: fulfillment.id,\n lines: fulfillmentLineQuantities(order as never, fulfillment),\n carrier: fulfillment.carrier ?? null,\n trackingNumber: fulfillment.trackingNumber ?? null,\n trackingUrl: fulfillmentTrackingUrl(fulfillment),\n labelUrl: fulfillment.labelUrl ?? null,\n labelCostCents: fulfillment.labelCostCents ?? null,\n at: new Date(fulfillment.atMs).toISOString(),\n }\n}\n\nexport interface OrderEventRequest<Payload extends OrderEventPayload> {\n hostId: string\n orderId: string\n /** The occurrence: the same key raises the event once. */\n key: string\n /** Everything the payload carries beside `order`. */\n extra?: Omit<Payload, 'order'>\n}\n\n/**\n * Stages an event inside the caller's transaction, from the order data the\n * transaction is about to store (the read merged with its patch).\n */\nexport function stageOrderEvent<Payload extends OrderEventPayload>(\n writer: PluginEventWriter,\n event: PluginDomainEvent<Payload>,\n request: OrderEventRequest<Payload> & { order: Record<string, unknown> },\n): void {\n try {\n const firestore = firebaseAdmin.app().firestore()\n stagePluginEvent(writer, firestore, {\n event,\n pluginId: BUNDLE_ID,\n hostId: request.hostId,\n key: `${request.orderId}:${request.key}`,\n payload: {\n order: orderViewFromData(request.orderId, request.order) as OrderEventOrder,\n ...(request.extra ?? {}),\n },\n })\n } catch (error) {\n console.error(`[commerce] ${event.id} not staged for ${request.orderId}`, error)\n }\n}\n\n/** Queues an event after the write, reading the order as it now stands. */\nexport async function raiseOrderEvent<Payload extends OrderEventPayload>(\n event: PluginDomainEvent<Payload>,\n request: OrderEventRequest<Payload>,\n): Promise<void> {\n try {\n const firestore = firebaseAdmin.app().firestore()\n const snapshot = await firestore\n .collection('hosts')\n .doc(request.hostId)\n .collection('orders')\n .doc(request.orderId)\n .get()\n if (!snapshot.exists) return\n await raisePluginEvent(firestore, {\n event,\n pluginId: BUNDLE_ID,\n hostId: request.hostId,\n key: `${request.orderId}:${request.key}`,\n payload: {\n order: orderViewFromData(request.orderId, snapshot.data() ?? {}) as OrderEventOrder,\n ...(request.extra ?? {}),\n },\n })\n } catch (error) {\n console.error(`[commerce] ${event.id} not raised for ${request.orderId}`, error)\n }\n}\n\n/**\n * Raises `checkout.started` (AGL-3639) for a checkout a shopper reached with\n * an address, keyed by the session so a retried request raises it once.\n * Never throws into the checkout it reports.\n */\nexport async function raiseCheckoutStarted(\n hostId: string,\n checkout: CheckoutStartedEventPayload['checkout'],\n): Promise<void> {\n if (!checkout.email) return\n try {\n await raisePluginEvent(firebaseAdmin.app().firestore(), {\n event: CHECKOUT_STARTED_EVENT,\n pluginId: BUNDLE_ID,\n hostId,\n key: `checkout:${checkout.id}`,\n payload: { checkout },\n })\n } catch (error) {\n console.error(`[commerce] checkout.started not raised for ${checkout.id}`, error)\n }\n}\n"],"names":["firebaseAdmin","raisePluginEvent","stagePluginEvent","BUNDLE_ID","CHECKOUT_STARTED_EVENT","fulfillmentLineQuantities","fulfillmentTrackingUrl","orderViewFromData","fulfillmentEventView","order","fulfillment","id","lines","carrier","trackingNumber","trackingUrl","labelUrl","labelCostCents","at","Date","atMs","toISOString","stageOrderEvent","writer","event","request","firestore","app","pluginId","hostId","key","orderId","payload","extra","error","console","raiseOrderEvent","snapshot","collection","doc","get","exists","data","raiseCheckoutStarted","checkout","email"],"mappings":"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"}
@@ -19,8 +19,10 @@ import { pluginSmsMessaging } from "@aglyn/aglyn/plugin-manager/plugin-sms-messa
19
19
  import { resolveSiteTimeZone } from "@aglyn/aglyn/app-utils/collection-entry-date";
20
20
  import { resolveHostToken } from "@aglyn/aglyn/app-utils/host-tokens";
21
21
  import { firebaseAdmin, getOrgForHost, hostSendingIdentity, meterHostEmail, renderHostEmailWithTokens } from "@aglyn/tenant-data-admin";
22
- import { isEmailConfigured, sendEmail } from "@aglyn/shared-util-email";
22
+ import { isEmailConfigured, renderTextEmailHtml, sendEmail } from "@aglyn/shared-util-email";
23
+ import { resolvePluginOrderEmailCopies, withOrderEmailDataBlocks } from "@aglyn/aglyn/plugin-manager/plugin-order-email-copies";
23
24
  import * as CommerceModel from "../model/index.js";
25
+ import { orderViewFromData } from "./api-v1/order-view.js";
24
26
  import { mintDownloadToken } from "./download.js";
25
27
  import { orderStatusUrl } from "./order-status-token.js";
26
28
  async function readStore(firestore, hostId) {
@@ -310,16 +312,41 @@ async function releaseMessage(orderRef, marker) {
310
312
  [`buyerNotifications.${marker}`]: firebaseAdmin.firestore.FieldValue.delete()
311
313
  }).catch(()=>undefined);
312
314
  }
313
- async function deliverEmail(store, ref, to, message, context) {
315
+ /** How long a buyer email waits on plugins asking to be copied on it. */ const EMAIL_COPIES_TIMEOUT_MS = 3000;
316
+ async function deliverEmail(store, ref, to, message, context, /** The moment and order, for plugins copied on it; absent for a merchant-asked resend. */ about) {
314
317
  var _ref;
315
318
  const designed = await renderHostEmailWithTokens(store.firestore, ref.hostId, message.emailKey, message.tokens);
316
319
  const org = await getOrgForHost(ref.hostId).catch(()=>null);
317
- await sendEmail(_extends({
320
+ // Plugins copied on this email (AGL-3699): a review platform's invitation
321
+ // address, with the data block it reads. Asked through core's seam, by
322
+ // the email's key and moment; nothing answers unless a site connected one.
323
+ const copies = about ? await resolvePluginOrderEmailCopies({
324
+ hostId: ref.hostId,
325
+ recordId: ref.orderId,
326
+ emailKey: message.emailKey,
327
+ moment: about.event,
328
+ recipient: to,
329
+ order: orderViewFromData(ref.orderId, about.order)
330
+ }, {
331
+ timeoutMs: EMAIL_COPIES_TIMEOUT_MS
332
+ }) : {
333
+ bcc: [],
334
+ html: '',
335
+ settle: async ()=>undefined
336
+ };
337
+ const subject = (_ref = designed == null ? void 0 : designed.subject) != null ? _ref : message.subject;
338
+ const text = (designed == null ? void 0 : designed.text) || message.text;
339
+ // A data block lives in the HTML part, so a text-only email gets the same
340
+ // HTML part `sendEmail` would have synthesized, carrying it.
341
+ const html = copies.html ? withOrderEmailDataBlocks((designed == null ? void 0 : designed.html) || renderTextEmailHtml(text, subject), copies.html) : designed == null ? void 0 : designed.html;
342
+ const result = await sendEmail(_extends({
318
343
  to,
319
- subject: (_ref = designed == null ? void 0 : designed.subject) != null ? _ref : message.subject,
320
- text: (designed == null ? void 0 : designed.text) || message.text
321
- }, (designed == null ? void 0 : designed.html) ? {
322
- html: designed.html
344
+ subject,
345
+ text
346
+ }, html ? {
347
+ html
348
+ } : {}, copies.bcc.length ? {
349
+ bcc: copies.bcc
323
350
  } : {}, {
324
351
  fromName: Aglyn.resolveBrandingProfile(org == null ? void 0 : org.org).fromName,
325
352
  sendingIdentity: await hostSendingIdentity(ref.hostId),
@@ -329,6 +356,7 @@ async function deliverEmail(store, ref, to, message, context) {
329
356
  // rules never hold it (AGL-3356).
330
357
  owedFor: 'order'
331
358
  }));
359
+ await copies.settle(Boolean(result == null ? void 0 : result.sent));
332
360
  // Cost meter (AGL-1438), as every transactional store email.
333
361
  await meterHostEmail(ref.hostId);
334
362
  }
@@ -473,7 +501,10 @@ function emailOf(value) {
473
501
  let held = null;
474
502
  try {
475
503
  if (target.channel === 'email') {
476
- await deliverEmail(store, ref, target.to, message, context);
504
+ await deliverEmail(store, ref, target.to, message, context, {
505
+ event,
506
+ order
507
+ });
477
508
  } else {
478
509
  var _sent_scheduledForMs;
479
510
  const sent = await deliverSms(ref, target.to, message, context, event === 'receipt' ? null : {