@infuro/cms-core 1.0.46 → 1.0.47

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 (58) hide show
  1. package/dist/admin.cjs +3420 -2702
  2. package/dist/admin.d.cts +18 -4
  3. package/dist/admin.d.ts +18 -4
  4. package/dist/admin.js +2328 -1612
  5. package/dist/api.cjs +37 -38
  6. package/dist/api.d.cts +9 -3
  7. package/dist/api.d.ts +9 -3
  8. package/dist/api.js +5 -6
  9. package/dist/auth.cjs +55 -51
  10. package/dist/auth.d.cts +5 -3
  11. package/dist/auth.d.ts +5 -3
  12. package/dist/auth.js +3 -3
  13. package/dist/{chunk-KFFB6EMZ.js → chunk-2HU5R2JE.js} +2 -0
  14. package/dist/chunk-2IAXTKQN.cjs +19 -0
  15. package/dist/{chunk-SPQFNIYJ.cjs → chunk-4PMK3RNA.cjs} +2 -0
  16. package/dist/{chunk-X6UQFV4X.cjs → chunk-77NUXO6A.cjs} +14 -12
  17. package/dist/chunk-EIZKW2PF.js +13 -0
  18. package/dist/{chunk-X47PBV4I.js → chunk-JE22VP6S.js} +1 -0
  19. package/dist/{chunk-DN65KDIA.js → chunk-JIWUVQ6B.js} +14 -13
  20. package/dist/{chunk-HT5DI3XW.cjs → chunk-KOTXDSQB.cjs} +1 -0
  21. package/dist/{chunk-TCSGFAWB.cjs → chunk-LMJ7RKPF.cjs} +1188 -3
  22. package/dist/{chunk-FBKDMRQL.cjs → chunk-Q3HQEM4R.cjs} +6 -6
  23. package/dist/{chunk-NBY4NVTY.cjs → chunk-VUQFARRT.cjs} +7 -7
  24. package/dist/{chunk-UKC3HYXI.js → chunk-W42UZLQO.js} +1377 -559
  25. package/dist/{chunk-UDVLFVEC.cjs → chunk-XUCKZPML.cjs} +1448 -630
  26. package/dist/{chunk-57O3HZPG.js → chunk-YC4NUZCS.js} +1 -1
  27. package/dist/{chunk-CRYKVJTO.js → chunk-YXH2UUEZ.js} +1179 -3
  28. package/dist/{chunk-SF2XKMCI.js → chunk-ZF2RQWXB.js} +1 -1
  29. package/dist/{emit-order-notification-trigger-IVLWKKJL.js → emit-order-notification-trigger-6XX7LSC4.js} +1 -1
  30. package/dist/{emit-order-notification-trigger-D2N3KFHL.cjs → emit-order-notification-trigger-NASG7ZEO.cjs} +3 -3
  31. package/dist/{event-order-defaults-EMWBIGL3.cjs → event-order-defaults-7Z3UZOEH.cjs} +9 -9
  32. package/dist/{event-order-defaults-SDRNAZHC.js → event-order-defaults-XQ3IDPD7.js} +1 -1
  33. package/dist/hooks.cjs +43 -0
  34. package/dist/hooks.d.cts +10 -1
  35. package/dist/hooks.d.ts +10 -1
  36. package/dist/hooks.js +39 -1
  37. package/dist/index.cjs +414 -310
  38. package/dist/index.d.cts +51 -17
  39. package/dist/index.d.ts +51 -17
  40. package/dist/index.js +114 -12
  41. package/dist/migrations/1782200000000-VendorTeamProfiles.ts +233 -0
  42. package/dist/migrations/1782300000000-AddTypeToOrderNotificationBindings.ts +75 -0
  43. package/dist/{order-notification-dispatcher-ZJZB2HUN.js → order-notification-dispatcher-6WNG24NY.js} +1 -2
  44. package/dist/{order-notification-dispatcher-SBQAMM5V.cjs → order-notification-dispatcher-6XSU3AR7.cjs} +3 -4
  45. package/dist/{rbac-debug-db-2SGNLANF.js → rbac-debug-db-6EMIVMGF.js} +1 -1
  46. package/dist/{rbac-debug-db-4Z6A425H.cjs → rbac-debug-db-6PWTLBEL.cjs} +2 -2
  47. package/dist/theme.cjs +6 -1
  48. package/dist/theme.d.cts +4 -1
  49. package/dist/theme.d.ts +4 -1
  50. package/dist/theme.js +3 -1
  51. package/package.json +1 -1
  52. package/src/admin/admin.css +7 -0
  53. package/dist/chunk-7PMHZRF3.cjs +0 -872
  54. package/dist/chunk-CQHXW4TR.js +0 -107
  55. package/dist/chunk-GUSHM5HN.js +0 -862
  56. package/dist/chunk-XMRMZ6NQ.cjs +0 -109
  57. package/dist/send-order-placed-emails-B5ZVJT7T.cjs +0 -15
  58. package/dist/send-order-placed-emails-WPI37KRZ.js +0 -6
@@ -1,7 +1,9 @@
1
1
  'use strict';
2
2
 
3
+ var chunkUCKN4BBY_cjs = require('./chunk-UCKN4BBY.cjs');
3
4
  var chunkV25RDUOU_cjs = require('./chunk-V25RDUOU.cjs');
4
5
  var chunkBXYZDMTZ_cjs = require('./chunk-BXYZDMTZ.cjs');
6
+ var chunkOOTLNH63_cjs = require('./chunk-OOTLNH63.cjs');
5
7
  var chunkUSNT2KNT_cjs = require('./chunk-USNT2KNT.cjs');
6
8
  var clientSes = require('@aws-sdk/client-ses');
7
9
  var module$1 = require('module');
@@ -12,6 +14,633 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
14
 
13
15
  var nodemailer__default = /*#__PURE__*/_interopDefault(nodemailer);
14
16
 
17
+ // src/lib/event-order-template-context.ts
18
+ function resolvePublicSiteUrl() {
19
+ if (typeof process !== "undefined") {
20
+ const u = (process.env.CMS_PUBLIC_URL || process.env.NEXT_PUBLIC_SITE_URL || "").trim();
21
+ if (u) return u.replace(/\/+$/, "");
22
+ const v = (process.env.VERCEL_URL || "").trim();
23
+ if (v) return v.startsWith("http") ? v.replace(/\/+$/, "") : `https://${v.replace(/\/+$/, "")}`;
24
+ }
25
+ return "";
26
+ }
27
+ chunkUSNT2KNT_cjs.__name(resolvePublicSiteUrl, "resolvePublicSiteUrl");
28
+ function buildOrderTrackUrl(qrToken, siteUrl, orderNumber) {
29
+ const token = qrToken?.trim() || orderNumber?.trim();
30
+ if (!token) return "";
31
+ const base = (siteUrl ?? resolvePublicSiteUrl()).replace(/\/+$/, "");
32
+ if (!base) return `/track/${token}`;
33
+ return `${base}/track/${token}`;
34
+ }
35
+ chunkUSNT2KNT_cjs.__name(buildOrderTrackUrl, "buildOrderTrackUrl");
36
+ function buildOrderQrImageUrl(trackUrl, size = 200, fallbackData) {
37
+ const data = trackUrl.trim() || fallbackData?.trim() || "";
38
+ if (!data) return "";
39
+ return `https://api.qrserver.com/v1/create-qr-code/?size=${size}x${size}&data=${encodeURIComponent(data)}`;
40
+ }
41
+ chunkUSNT2KNT_cjs.__name(buildOrderQrImageUrl, "buildOrderQrImageUrl");
42
+ function formatOrderDetailsText(input) {
43
+ const { orderNumber, total, currency, lines } = input;
44
+ const header = `Order: ${orderNumber}
45
+ Total: ${total} ${currency}`;
46
+ if (!lines.length) return header;
47
+ const items = lines.map((line) => `- ${line.name} \xD7 ${line.quantity} (${line.total} ${line.currency})`).join("\n");
48
+ return `${header}
49
+
50
+ Items:
51
+ ${items}`;
52
+ }
53
+ chunkUSNT2KNT_cjs.__name(formatOrderDetailsText, "formatOrderDetailsText");
54
+ function formatOrderDetailsHtml(input) {
55
+ const text = formatOrderDetailsText(input);
56
+ const escaped = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
57
+ return `<pre style="font-family:inherit;white-space:pre-wrap;margin:0">${escaped}</pre>`;
58
+ }
59
+ chunkUSNT2KNT_cjs.__name(formatOrderDetailsHtml, "formatOrderDetailsHtml");
60
+ function buildEventOrderTemplateVariables(input) {
61
+ const { event, order, contact, productNames, vendorName = "", orderLines = [], siteUrl } = input;
62
+ const trackUrl = buildOrderTrackUrl(order.qrToken, siteUrl, order.orderNumber);
63
+ const qrImageUrl = buildOrderQrImageUrl(trackUrl, 200, order.orderNumber);
64
+ const ticketQr = qrImageUrl ? `<img src="${qrImageUrl}" alt="Ticket QR Code" width="200" height="200" style="display:block;margin:12px 0;border:1px solid #e5e7eb;border-radius:8px;" />` : "";
65
+ const baseSite = (siteUrl ?? resolvePublicSiteUrl()).replace(/\/+$/, "");
66
+ const invoiceNumber = `INV-${order.orderNumber ?? ""}`;
67
+ const invoiceUrl = baseSite ? `${baseSite}/api/orders/${order.orderNumber ?? ""}/invoice` : `/api/orders/${order.orderNumber ?? ""}/invoice`;
68
+ const orderDetailsPayload = {
69
+ orderNumber: String(order.orderNumber ?? ""),
70
+ total: String(order.total ?? ""),
71
+ currency: String(order.currency ?? "INR"),
72
+ lines: orderLines
73
+ };
74
+ return {
75
+ eventName: event.name ?? productNames ?? "",
76
+ vendorName: vendorName.trim(),
77
+ eventSlug: event.slug ?? "",
78
+ venue: event.venue ?? "",
79
+ startDate: formatEventDate(event.startDate),
80
+ orderNumber: String(order.orderNumber ?? ""),
81
+ orderTotal: String(order.total ?? ""),
82
+ currency: String(order.currency ?? "INR"),
83
+ customerName: contact.name?.trim() || contact.email?.split("@")[0] || "Guest",
84
+ customerEmail: contact.email?.trim() || "",
85
+ customerPhone: contact.phone?.trim() || "",
86
+ productNames,
87
+ trackUrl,
88
+ qrImageUrl,
89
+ ticketQr,
90
+ invoiceNumber,
91
+ invoiceUrl,
92
+ orderDetails: formatOrderDetailsText(orderDetailsPayload),
93
+ orderDetailsHtml: formatOrderDetailsHtml(orderDetailsPayload)
94
+ };
95
+ }
96
+ chunkUSNT2KNT_cjs.__name(buildEventOrderTemplateVariables, "buildEventOrderTemplateVariables");
97
+ function formatEventDate(d) {
98
+ if (!d) return "";
99
+ try {
100
+ return new Date(d).toLocaleString(void 0, {
101
+ dateStyle: "medium",
102
+ timeStyle: "short"
103
+ });
104
+ } catch {
105
+ return String(d);
106
+ }
107
+ }
108
+ chunkUSNT2KNT_cjs.__name(formatEventDate, "formatEventDate");
109
+
110
+ // src/lib/order-notification-context.ts
111
+ async function loadOrderLines(dataSource, entityMap, orderId, currency) {
112
+ if (!entityMap.order_items) return [];
113
+ const itemRows = await dataSource.getRepository(entityMap.order_items).find({
114
+ where: {
115
+ orderId
116
+ },
117
+ relations: [
118
+ "product"
119
+ ],
120
+ order: {
121
+ id: "ASC"
122
+ }
123
+ });
124
+ return itemRows.map((item) => {
125
+ const row = item;
126
+ const name = row.product?.name || row.product?.title || `Product #${row.productId}`;
127
+ return {
128
+ name: String(name),
129
+ quantity: Number(row.quantity) || 1,
130
+ total: row.total,
131
+ currency
132
+ };
133
+ });
134
+ }
135
+ chunkUSNT2KNT_cjs.__name(loadOrderLines, "loadOrderLines");
136
+ async function loadPrimaryEvent(dataSource, entityMap, itemRows) {
137
+ if (!entityMap.event_products || !entityMap.events) {
138
+ return {
139
+ productNames: []
140
+ };
141
+ }
142
+ const epRepo = dataSource.getRepository(entityMap.event_products);
143
+ const eventRepo = dataSource.getRepository(entityMap.events);
144
+ const eventMap = /* @__PURE__ */ new Map();
145
+ for (const item of itemRows) {
146
+ const productId = item.productId;
147
+ const productName = item.product?.name || item.product?.title || `Product #${productId}`;
148
+ const eps = await epRepo.find({
149
+ where: {
150
+ productId,
151
+ isActive: true
152
+ }
153
+ });
154
+ for (const ep of eps) {
155
+ const eventId = ep.eventId;
156
+ const existing = eventMap.get(eventId);
157
+ if (existing) {
158
+ if (!existing.productNames.includes(String(productName))) {
159
+ existing.productNames.push(String(productName));
160
+ }
161
+ } else {
162
+ const event = await eventRepo.findOne({
163
+ where: {
164
+ id: eventId,
165
+ deleted: false
166
+ }
167
+ });
168
+ if (event) {
169
+ eventMap.set(eventId, {
170
+ event,
171
+ productNames: [
172
+ String(productName)
173
+ ]
174
+ });
175
+ }
176
+ }
177
+ }
178
+ }
179
+ const first = eventMap.values().next().value;
180
+ if (!first) return {
181
+ productNames: []
182
+ };
183
+ return {
184
+ event: first.event,
185
+ productNames: first.productNames
186
+ };
187
+ }
188
+ chunkUSNT2KNT_cjs.__name(loadPrimaryEvent, "loadPrimaryEvent");
189
+ async function isFeatureGroupEnabled(dataSource, entityMap, group) {
190
+ if (!entityMap.configs) return true;
191
+ try {
192
+ const rows = await dataSource.getRepository(entityMap.configs).find({
193
+ where: {
194
+ settings: group,
195
+ deleted: false
196
+ }
197
+ });
198
+ const settings = Object.fromEntries(rows.map((r) => [
199
+ r.key,
200
+ r.value
201
+ ]));
202
+ return settings.enabled !== "false" && settings.enabled !== "0";
203
+ } catch {
204
+ return true;
205
+ }
206
+ }
207
+ chunkUSNT2KNT_cjs.__name(isFeatureGroupEnabled, "isFeatureGroupEnabled");
208
+ async function loadOrderNotificationContexts(orderId, deps) {
209
+ if (!deps.entityMap.orders) return null;
210
+ const order = await deps.dataSource.getRepository(deps.entityMap.orders).findOne({
211
+ where: {
212
+ id: orderId,
213
+ deleted: false
214
+ },
215
+ relations: [
216
+ "contact"
217
+ ]
218
+ });
219
+ if (!order) return null;
220
+ const o = order;
221
+ const meta = o.metadata && typeof o.metadata === "object" && !Array.isArray(o.metadata) ? o.metadata : {};
222
+ const metaBuyerEmail = typeof meta.buyerEmail === "string" && meta.buyerEmail.includes("@") ? meta.buyerEmail.trim() : null;
223
+ const metaCheckoutEmail = typeof meta.checkoutEmail === "string" && meta.checkoutEmail.includes("@") ? meta.checkoutEmail.trim() : null;
224
+ const metaBuyerName = typeof meta.buyerName === "string" && meta.buyerName.trim() ? meta.buyerName.trim() : null;
225
+ const rawContact = o.contact ?? {};
226
+ const contact = {
227
+ name: metaBuyerName || rawContact.name,
228
+ email: metaBuyerEmail || metaCheckoutEmail || rawContact.email,
229
+ phone: rawContact.phone
230
+ };
231
+ const currency = String(o.currency ?? "INR");
232
+ const siteUrl = resolvePublicSiteUrl();
233
+ const itemRows = await deps.dataSource.getRepository(deps.entityMap.order_items).find({
234
+ where: {
235
+ orderId
236
+ },
237
+ relations: [
238
+ "product"
239
+ ],
240
+ order: {
241
+ id: "ASC"
242
+ }
243
+ });
244
+ const orderLines = await loadOrderLines(deps.dataSource, deps.entityMap, orderId, currency);
245
+ const { event, productNames: eventProductNames } = await loadPrimaryEvent(deps.dataSource, deps.entityMap, itemRows);
246
+ const allProductNames = orderLines.map((line) => line.name);
247
+ const productNames = eventProductNames.length > 0 ? eventProductNames.join(", ") : allProductNames.join(", ");
248
+ const [multiVendorEnabled, eventsEnabled] = await Promise.all([
249
+ isFeatureGroupEnabled(deps.dataSource, deps.entityMap, "multi_vendor"),
250
+ isFeatureGroupEnabled(deps.dataSource, deps.entityMap, "events")
251
+ ]);
252
+ let vendorName = "";
253
+ if (multiVendorEnabled) {
254
+ const vendorId = o.vendorId ?? event?.vendorId;
255
+ if (vendorId && deps.entityMap.vendors) {
256
+ try {
257
+ const vRow = await deps.dataSource.getRepository(deps.entityMap.vendors).findOne({
258
+ where: {
259
+ id: vendorId
260
+ }
261
+ });
262
+ if (vRow) {
263
+ const v = vRow;
264
+ vendorName = String(v.storeName?.trim() || v.name?.trim() || v.companyName?.trim() || "");
265
+ }
266
+ } catch {
267
+ }
268
+ }
269
+ }
270
+ const effectiveEvent = eventsEnabled ? event : void 0;
271
+ const variables = effectiveEvent ? buildEventOrderTemplateVariables({
272
+ event: effectiveEvent,
273
+ order: {
274
+ orderNumber: o.orderNumber,
275
+ total: o.total,
276
+ currency,
277
+ qrToken: o.qrToken
278
+ },
279
+ contact,
280
+ productNames,
281
+ vendorName,
282
+ orderLines,
283
+ siteUrl
284
+ }) : buildEventOrderTemplateVariables({
285
+ event: {
286
+ name: eventsEnabled ? productNames || "Your order" : "",
287
+ slug: "",
288
+ venue: "",
289
+ startDate: null
290
+ },
291
+ order: {
292
+ orderNumber: o.orderNumber,
293
+ total: o.total,
294
+ currency,
295
+ qrToken: o.qrToken
296
+ },
297
+ contact,
298
+ productNames,
299
+ vendorName,
300
+ orderLines,
301
+ siteUrl
302
+ });
303
+ return {
304
+ order: {
305
+ id: o.id,
306
+ orderNumber: String(o.orderNumber ?? ""),
307
+ total: o.total,
308
+ currency,
309
+ qrToken: o.qrToken
310
+ },
311
+ contact,
312
+ orderLines,
313
+ productNames,
314
+ event,
315
+ variables
316
+ };
317
+ }
318
+ chunkUSNT2KNT_cjs.__name(loadOrderNotificationContexts, "loadOrderNotificationContexts");
319
+
320
+ // src/plugins/whatsapp/whatsapp-queue.ts
321
+ var WHATSAPP_QUEUE_NAME = "whatsapp";
322
+ function maskPhone(raw) {
323
+ const digits = raw.replace(/\D/g, "");
324
+ if (digits.length <= 4) return "****";
325
+ return `${"*".repeat(Math.max(0, digits.length - 4))}${digits.slice(-4)}`;
326
+ }
327
+ chunkUSNT2KNT_cjs.__name(maskPhone, "maskPhone");
328
+ function registerWhatsAppQueueProcessor(cms) {
329
+ const queue = cms.getPlugin("queue");
330
+ const whatsapp = cms.getPlugin("whatsapp");
331
+ if (!queue) {
332
+ console.warn("[whatsapp] queue processor not registered: queue plugin missing");
333
+ return;
334
+ }
335
+ if (!whatsapp || typeof whatsapp.send !== "function") {
336
+ console.warn("[whatsapp] queue processor not registered: whatsapp plugin missing or not configured");
337
+ return;
338
+ }
339
+ queue.registerProcessor(WHATSAPP_QUEUE_NAME, async (data) => {
340
+ const payload = data;
341
+ if (!payload.to) {
342
+ console.warn("[whatsapp] queue job skipped: missing recipient");
343
+ return;
344
+ }
345
+ console.log("[whatsapp] queue job processing", {
346
+ to: maskPhone(payload.to),
347
+ templateKey: payload.templateKey ?? null,
348
+ externalTemplateRef: payload.externalTemplateRef ?? null
349
+ });
350
+ try {
351
+ const ok = await whatsapp.send(payload);
352
+ if (ok) {
353
+ console.log("[whatsapp] queue job completed", {
354
+ to: maskPhone(payload.to),
355
+ sent: true
356
+ });
357
+ } else {
358
+ console.warn("[whatsapp] queue job completed without delivery", {
359
+ to: maskPhone(payload.to),
360
+ sent: false
361
+ });
362
+ }
363
+ } catch (err) {
364
+ console.error("[whatsapp] queue job failed", {
365
+ to: maskPhone(payload.to),
366
+ error: err instanceof Error ? err.message : String(err)
367
+ });
368
+ }
369
+ });
370
+ console.log("[whatsapp] queue processor registered", {
371
+ queue: WHATSAPP_QUEUE_NAME
372
+ });
373
+ }
374
+ chunkUSNT2KNT_cjs.__name(registerWhatsAppQueueProcessor, "registerWhatsAppQueueProcessor");
375
+ async function queueWhatsApp(cms, payload) {
376
+ const queue = cms.getPlugin("queue");
377
+ if (queue) {
378
+ console.log("[whatsapp] enqueue", {
379
+ to: maskPhone(payload.to),
380
+ templateKey: payload.templateKey ?? null,
381
+ externalTemplateRef: payload.externalTemplateRef ?? null
382
+ });
383
+ await queue.add(WHATSAPP_QUEUE_NAME, payload);
384
+ return true;
385
+ }
386
+ const whatsapp = cms.getPlugin("whatsapp");
387
+ if (whatsapp && typeof whatsapp.send === "function") {
388
+ console.log("[whatsapp] no queue plugin \u2014 sending immediately", {
389
+ to: maskPhone(payload.to)
390
+ });
391
+ return whatsapp.send(payload);
392
+ }
393
+ console.warn("[whatsapp] send skipped: neither queue nor whatsapp plugin available", {
394
+ to: maskPhone(payload.to)
395
+ });
396
+ return false;
397
+ }
398
+ chunkUSNT2KNT_cjs.__name(queueWhatsApp, "queueWhatsApp");
399
+
400
+ // src/plugins/whatsapp/whatsapp-service.ts
401
+ function norm(v) {
402
+ const t = v?.trim();
403
+ return t || void 0;
404
+ }
405
+ chunkUSNT2KNT_cjs.__name(norm, "norm");
406
+ function pick(plugin, db, env, ...keys) {
407
+ for (const key of keys) {
408
+ const pk = key;
409
+ if (pk in plugin && plugin[pk] !== void 0 && String(plugin[pk]).trim() !== "") {
410
+ const v = norm(String(plugin[pk]));
411
+ if (v) return v;
412
+ }
413
+ if (norm(db[key])) return norm(db[key]);
414
+ if (norm(env[key])) return norm(env[key]);
415
+ }
416
+ return void 0;
417
+ }
418
+ chunkUSNT2KNT_cjs.__name(pick, "pick");
419
+ function mergeWhatsAppConfigLayers(env, db, plugin) {
420
+ return {
421
+ accessToken: pick(plugin, db, env, "accessToken", "WHATSAPP_ACCESS_TOKEN"),
422
+ phoneNumberId: pick(plugin, db, env, "phoneNumberId", "WHATSAPP_PHONE_NUMBER_ID"),
423
+ apiVersion: pick(plugin, db, env, "apiVersion", "WHATSAPP_API_VERSION") ?? "v21.0"
424
+ };
425
+ }
426
+ chunkUSNT2KNT_cjs.__name(mergeWhatsAppConfigLayers, "mergeWhatsAppConfigLayers");
427
+ function isWhatsAppPluginEnabled(db) {
428
+ const v = (db.enabled ?? "true").toLowerCase();
429
+ return v !== "false" && v !== "0";
430
+ }
431
+ chunkUSNT2KNT_cjs.__name(isWhatsAppPluginEnabled, "isWhatsAppPluginEnabled");
432
+ function whatsAppConfigured(config) {
433
+ return Boolean(config.accessToken?.trim() && config.phoneNumberId?.trim());
434
+ }
435
+ chunkUSNT2KNT_cjs.__name(whatsAppConfigured, "whatsAppConfigured");
436
+ function normalizeWhatsAppRecipient(to) {
437
+ const digits = to.replace(/\D/g, "");
438
+ return digits;
439
+ }
440
+ chunkUSNT2KNT_cjs.__name(normalizeWhatsAppRecipient, "normalizeWhatsAppRecipient");
441
+ function resolveMetaWhatsAppTemplateName(opts) {
442
+ const ref = opts.externalTemplateRef?.trim();
443
+ const rowName = opts.name?.trim();
444
+ if (ref && !/^\d+$/.test(ref)) return ref;
445
+ if (rowName) return rowName;
446
+ return ref || void 0;
447
+ }
448
+ chunkUSNT2KNT_cjs.__name(resolveMetaWhatsAppTemplateName, "resolveMetaWhatsAppTemplateName");
449
+ function maskPhone2(digits) {
450
+ if (digits.length <= 4) return "****";
451
+ return `${"*".repeat(Math.max(0, digits.length - 4))}${digits.slice(-4)}`;
452
+ }
453
+ chunkUSNT2KNT_cjs.__name(maskPhone2, "maskPhone");
454
+ function logWhatsApp(level, message, extra) {
455
+ const line = extra ? `${message} ${JSON.stringify(extra)}` : message;
456
+ if (level === "info") console.log(`[whatsapp] ${line}`);
457
+ else if (level === "warn") console.warn(`[whatsapp] ${line}`);
458
+ else console.error(`[whatsapp] ${line}`);
459
+ }
460
+ chunkUSNT2KNT_cjs.__name(logWhatsApp, "logWhatsApp");
461
+ var WhatsAppService = class {
462
+ static {
463
+ chunkUSNT2KNT_cjs.__name(this, "WhatsAppService");
464
+ }
465
+ getWhatsAppSettings;
466
+ config;
467
+ getMessageTemplateRow;
468
+ constructor(env, plugin = {}, getWhatsAppSettings, getMessageTemplateRow) {
469
+ this.getWhatsAppSettings = getWhatsAppSettings;
470
+ this.getMessageTemplateRow = getMessageTemplateRow;
471
+ this.config = mergeWhatsAppConfigLayers(env, {}, plugin);
472
+ }
473
+ async mergedConfig() {
474
+ let db = {};
475
+ try {
476
+ db = await this.getWhatsAppSettings?.() ?? {};
477
+ } catch {
478
+ }
479
+ return mergeWhatsAppConfigLayers((typeof process !== "undefined" ? process.env : {}) ?? {}, db, this.config);
480
+ }
481
+ async send(opts) {
482
+ const cfg = await this.mergedConfig();
483
+ if (!whatsAppConfigured(cfg)) {
484
+ logWhatsApp("warn", "send skipped: not configured (Plugins \u2192 WhatsApp or WHATSAPP_ACCESS_TOKEN + WHATSAPP_PHONE_NUMBER_ID)");
485
+ return false;
486
+ }
487
+ const to = normalizeWhatsAppRecipient(opts.to);
488
+ if (!to) {
489
+ logWhatsApp("warn", "send skipped: invalid recipient", {
490
+ rawTo: opts.to
491
+ });
492
+ return false;
493
+ }
494
+ logWhatsApp("info", "send started", {
495
+ to: maskPhone2(to),
496
+ templateKey: opts.templateKey ?? null,
497
+ externalTemplateRef: opts.externalTemplateRef ?? null,
498
+ phoneNumberId: cfg.phoneNumberId
499
+ });
500
+ let templateName = resolveMetaWhatsAppTemplateName({
501
+ externalTemplateRef: opts.externalTemplateRef
502
+ });
503
+ let templateLanguage = opts.templateLanguage?.trim();
504
+ let body = opts.body?.trim() ?? "";
505
+ const variables = opts.variables ?? {};
506
+ if (opts.templateKey?.trim() && this.getMessageTemplateRow) {
507
+ const { resolveEventOrderTemplate, EVENT_ORDER_TEMPLATE_KEY } = await import('./event-order-defaults-7Z3UZOEH.cjs');
508
+ if (opts.templateKey.trim() === EVENT_ORDER_TEMPLATE_KEY) {
509
+ const resolved = await resolveEventOrderTemplate("whatsapp", async (ch, key) => {
510
+ const row = await this.getMessageTemplateRow(ch, key);
511
+ if (!row) return null;
512
+ return {
513
+ subject: null,
514
+ body: row.body,
515
+ externalTemplateRef: row.externalTemplateRef,
516
+ enabled: row.enabled
517
+ };
518
+ });
519
+ if (resolved && !resolved.enabled) {
520
+ logWhatsApp("warn", "send skipped: template disabled in message_templates", {
521
+ templateKey: opts.templateKey
522
+ });
523
+ return false;
524
+ }
525
+ if (resolved) {
526
+ body = resolved.body;
527
+ if (resolved.externalTemplateRef) templateName = resolved.externalTemplateRef;
528
+ }
529
+ }
530
+ }
531
+ const { applyTemplateVars: applyTemplateVars2 } = await import('./interpolate-WAKPN56R.cjs');
532
+ if (body) body = applyTemplateVars2(body, variables);
533
+ if (templateName) {
534
+ const paramValues = opts.templateParamOrder?.length ? opts.templateParamOrder.map((key) => String(variables[key] ?? "").slice(0, 1024)) : Object.keys(variables).length ? Object.values(variables) : body ? [
535
+ body
536
+ ] : [];
537
+ logWhatsApp("info", "sending template message", {
538
+ to: maskPhone2(to),
539
+ templateName,
540
+ paramCount: paramValues.length,
541
+ paramOrder: opts.templateParamOrder ?? null
542
+ });
543
+ return this.sendTemplateMessage(cfg, to, templateName, paramValues, templateLanguage);
544
+ }
545
+ if (!body) {
546
+ logWhatsApp("warn", "send skipped: no template name and empty body", {
547
+ to: maskPhone2(to)
548
+ });
549
+ return false;
550
+ }
551
+ logWhatsApp("info", "sending text message", {
552
+ to: maskPhone2(to),
553
+ bodyLength: body.length
554
+ });
555
+ return this.sendTextMessage(cfg, to, body);
556
+ }
557
+ async sendTextMessage(cfg, to, body) {
558
+ const url = `https://graph.facebook.com/${cfg.apiVersion}/${cfg.phoneNumberId}/messages`;
559
+ const res = await fetch(url, {
560
+ method: "POST",
561
+ headers: {
562
+ Authorization: `Bearer ${cfg.accessToken}`,
563
+ "Content-Type": "application/json"
564
+ },
565
+ body: JSON.stringify({
566
+ messaging_product: "whatsapp",
567
+ to,
568
+ type: "text",
569
+ text: {
570
+ body
571
+ }
572
+ })
573
+ });
574
+ if (!res.ok) {
575
+ const errText = await res.text().catch(() => "");
576
+ logWhatsApp("error", "text send failed", {
577
+ status: res.status,
578
+ to: maskPhone2(to),
579
+ error: errText
580
+ });
581
+ return false;
582
+ }
583
+ const data = await res.json().catch(() => ({}));
584
+ logWhatsApp("info", "text send succeeded", {
585
+ to: maskPhone2(to),
586
+ messageId: data.messages?.[0]?.id ?? null
587
+ });
588
+ return true;
589
+ }
590
+ async sendTemplateMessage(cfg, to, templateName, paramValues, templateLanguage) {
591
+ const components = paramValues.length > 0 ? [
592
+ {
593
+ type: "body",
594
+ parameters: paramValues.map((text) => ({
595
+ type: "text",
596
+ text: String(text).slice(0, 1024)
597
+ }))
598
+ }
599
+ ] : void 0;
600
+ const url = `https://graph.facebook.com/${cfg.apiVersion}/${cfg.phoneNumberId}/messages`;
601
+ const payload = {
602
+ messaging_product: "whatsapp",
603
+ to,
604
+ type: "template",
605
+ template: {
606
+ name: templateName,
607
+ language: {
608
+ code: templateLanguage?.trim() || "en"
609
+ },
610
+ ...components ? {
611
+ components
612
+ } : {}
613
+ }
614
+ };
615
+ const res = await fetch(url, {
616
+ method: "POST",
617
+ headers: {
618
+ Authorization: `Bearer ${cfg.accessToken}`,
619
+ "Content-Type": "application/json"
620
+ },
621
+ body: JSON.stringify(payload)
622
+ });
623
+ if (!res.ok) {
624
+ const errText = await res.text().catch(() => "");
625
+ logWhatsApp("error", "template send failed", {
626
+ status: res.status,
627
+ to: maskPhone2(to),
628
+ templateName,
629
+ paramCount: paramValues.length,
630
+ error: errText
631
+ });
632
+ return false;
633
+ }
634
+ const data = await res.json().catch(() => ({}));
635
+ logWhatsApp("info", "template send succeeded", {
636
+ to: maskPhone2(to),
637
+ templateName,
638
+ messageId: data.messages?.[0]?.id ?? null
639
+ });
640
+ return true;
641
+ }
642
+ };
643
+
15
644
  // src/plugins/email/templates/layout.ts
16
645
  function escapeHtml(s) {
17
646
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -574,6 +1203,19 @@ ${slugNote}
574
1203
  `Owner / invitee: ${ownerName?.trim() || "\u2014"} (${ownerEmail})`,
575
1204
  `Activation: ${activation === "invite" ? "Invitation" : "Password set"}`
576
1205
  ].filter(Boolean).join("\n");
1206
+ } else if (kind === "team_invite" && activation === "membership" && inviteLink) {
1207
+ subject = `You're invited to ${storeLabel}`;
1208
+ bodyHtml = `<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">${greeting}</p>
1209
+ <p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">You have been invited to join the vendor team for <strong>${escapeHtml2(storeLabel)}</strong>. You already have an account \u2014 accept below to join this store. You will not need to set a new password.</p>
1210
+ ${primaryCtaButton(inviteLink, "Accept invitation")}
1211
+ <p style="margin:16px 0 0 0;font-size:12px;line-height:1.5;color:#888;">If the button does not work, copy and paste this URL into your browser:<br/><span style="word-break:break-all;">${escapeHtml2(inviteLink)}</span></p>`;
1212
+ text = [
1213
+ ownerName?.trim() ? `Hello ${ownerName.trim()},` : "Hello,",
1214
+ "",
1215
+ `You have been invited to join ${storeLabel}.`,
1216
+ "Open this link to accept (no new password required):",
1217
+ inviteLink
1218
+ ].join("\n");
577
1219
  } else if (kind === "team_invite" && activation === "invite" && inviteLink) {
578
1220
  subject = `You're invited to ${storeLabel}`;
579
1221
  bodyHtml = `<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">${greeting}</p>
@@ -672,7 +1314,9 @@ function renderEmail(templateName, ctx, options) {
672
1314
  return render12(ctx);
673
1315
  }
674
1316
  chunkUSNT2KNT_cjs.__name(renderEmail, "renderEmail");
675
- var require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('chunk-TCSGFAWB.cjs', document.baseURI).href)));
1317
+
1318
+ // src/plugins/email/email-service.ts
1319
+ var require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('chunk-LMJ7RKPF.cjs', document.baseURI).href)));
676
1320
  var MailComposer = require2("nodemailer/lib/mail-composer");
677
1321
  var SES_REGIONS_WITHOUT_SMTP = /* @__PURE__ */ new Set([
678
1322
  "af-south-1",
@@ -1038,8 +1682,8 @@ function resolveChatEmailToolSettings(configMap, sources) {
1038
1682
  chunkUSNT2KNT_cjs.__name(resolveChatEmailToolSettings, "resolveChatEmailToolSettings");
1039
1683
  function intentByKey(intents, key) {
1040
1684
  if (!key) return null;
1041
- const norm = normalizeIntentKey(key);
1042
- return intents.find((i) => i.intent === norm) ?? null;
1685
+ const norm2 = normalizeIntentKey(key);
1686
+ return intents.find((i) => i.intent === norm2) ?? null;
1043
1687
  }
1044
1688
  chunkUSNT2KNT_cjs.__name(intentByKey, "intentByKey");
1045
1689
  function buildIntentTableForPrompt(intents) {
@@ -1425,18 +2069,559 @@ function emailPlugin(config) {
1425
2069
  }
1426
2070
  chunkUSNT2KNT_cjs.__name(emailPlugin, "emailPlugin");
1427
2071
 
2072
+ // src/lib/order-notification-dispatcher.ts
2073
+ var initialized = false;
2074
+ function maskPhone3(raw) {
2075
+ const digits = String(raw ?? "").replace(/\D/g, "");
2076
+ if (digits.length <= 4) return "(none)";
2077
+ return `***${digits.slice(-4)}`;
2078
+ }
2079
+ chunkUSNT2KNT_cjs.__name(maskPhone3, "maskPhone");
2080
+ async function getSettingsGroup(dataSource, entityMap, group) {
2081
+ if (!entityMap.configs) return {};
2082
+ const rows = await dataSource.getRepository(entityMap.configs).find({
2083
+ where: {
2084
+ settings: group,
2085
+ deleted: false
2086
+ }
2087
+ });
2088
+ return Object.fromEntries(rows.map((r) => [
2089
+ r.key,
2090
+ r.value
2091
+ ]));
2092
+ }
2093
+ chunkUSNT2KNT_cjs.__name(getSettingsGroup, "getSettingsGroup");
2094
+ async function isEmailNotificationsEnabled(dataSource, entityMap) {
2095
+ const settings = await getSettingsGroup(dataSource, entityMap, "event_notifications");
2096
+ if (settings.enabled === "false" || settings.enabled === "0") return false;
2097
+ return settings.email_enabled !== "false" && settings.email_enabled !== "0";
2098
+ }
2099
+ chunkUSNT2KNT_cjs.__name(isEmailNotificationsEnabled, "isEmailNotificationsEnabled");
2100
+ async function isWhatsappNotificationsEnabled(dataSource, entityMap) {
2101
+ const settings = await getSettingsGroup(dataSource, entityMap, "event_notifications");
2102
+ if (settings.enabled === "false" || settings.enabled === "0") return false;
2103
+ return settings.whatsapp_enabled !== "false" && settings.whatsapp_enabled !== "0";
2104
+ }
2105
+ chunkUSNT2KNT_cjs.__name(isWhatsappNotificationsEnabled, "isWhatsappNotificationsEnabled");
2106
+ async function isTriggerCatalogEnabled(triggerKey, dataSource, entityMap) {
2107
+ if (!entityMap.order_notification_triggers) {
2108
+ console.warn("[order-notifications] order_notification_triggers missing \u2014 run migration 1781230000000-AddOrderNotificationTriggers");
2109
+ return false;
2110
+ }
2111
+ try {
2112
+ const row = await dataSource.getRepository(entityMap.order_notification_triggers).findOne({
2113
+ where: {
2114
+ triggerKey,
2115
+ enabled: true
2116
+ }
2117
+ });
2118
+ if (!row) {
2119
+ console.warn("[order-notifications] trigger disabled or not in DB", {
2120
+ triggerKey
2121
+ });
2122
+ }
2123
+ return Boolean(row);
2124
+ } catch (err) {
2125
+ console.error("[order-notifications] failed to load trigger catalog", {
2126
+ triggerKey,
2127
+ err
2128
+ });
2129
+ return false;
2130
+ }
2131
+ }
2132
+ chunkUSNT2KNT_cjs.__name(isTriggerCatalogEnabled, "isTriggerCatalogEnabled");
2133
+ async function loadBoundTemplate(triggerKey, channel, audienceType = "customers", dataSource, entityMap) {
2134
+ if (!entityMap.order_notification_bindings || !entityMap.message_templates) {
2135
+ console.warn("[order-notifications] order_notification_bindings or message_templates missing from entityMap");
2136
+ return null;
2137
+ }
2138
+ const bindingRepo = dataSource.getRepository(entityMap.order_notification_bindings);
2139
+ let binding = await bindingRepo.findOne({
2140
+ where: {
2141
+ triggerKey,
2142
+ channel,
2143
+ type: audienceType,
2144
+ enabled: true
2145
+ }
2146
+ });
2147
+ if (!binding && audienceType === "customers") {
2148
+ binding = await bindingRepo.findOne({
2149
+ where: {
2150
+ triggerKey,
2151
+ channel,
2152
+ enabled: true
2153
+ }
2154
+ });
2155
+ }
2156
+ if (!binding) {
2157
+ console.warn("[order-notifications] no order_notification_bindings row", {
2158
+ triggerKey,
2159
+ channel,
2160
+ audienceType
2161
+ });
2162
+ return null;
2163
+ }
2164
+ const templateId = binding.messageTemplateId;
2165
+ if (templateId == null) {
2166
+ console.warn("[order-notifications] binding exists but no template selected", {
2167
+ triggerKey,
2168
+ channel,
2169
+ audienceType
2170
+ });
2171
+ return null;
2172
+ }
2173
+ const template = await dataSource.getRepository(entityMap.message_templates).findOne({
2174
+ where: {
2175
+ id: templateId,
2176
+ channel,
2177
+ deleted: false,
2178
+ enabled: true
2179
+ }
2180
+ });
2181
+ if (!template) {
2182
+ console.warn("[order-notifications] bound template not found or disabled", {
2183
+ triggerKey,
2184
+ channel,
2185
+ audienceType,
2186
+ templateId
2187
+ });
2188
+ return null;
2189
+ }
2190
+ return template;
2191
+ }
2192
+ chunkUSNT2KNT_cjs.__name(loadBoundTemplate, "loadBoundTemplate");
2193
+ function mergeTriggerVariables(payload, rich) {
2194
+ const basic = {
2195
+ orderId: String(payload.orderId ?? ""),
2196
+ orderNumber: payload.orderNumber,
2197
+ customerName: payload.customerName,
2198
+ customerPhone: payload.customerPhone ?? "",
2199
+ total: String(payload.total),
2200
+ orderTotal: String(payload.total),
2201
+ currency: payload.currency,
2202
+ vendorId: String(payload.vendorId ?? ""),
2203
+ ...payload.refundAmount != null ? {
2204
+ refundAmount: String(payload.refundAmount)
2205
+ } : {}
2206
+ };
2207
+ return {
2208
+ ...basic,
2209
+ ...rich ?? {}
2210
+ };
2211
+ }
2212
+ chunkUSNT2KNT_cjs.__name(mergeTriggerVariables, "mergeTriggerVariables");
2213
+ async function isAudienceEmailEnabled(dataSource, entityMap, audience) {
2214
+ const settings = await getSettingsGroup(dataSource, entityMap, "event_notifications");
2215
+ if (settings.enabled === "false" || settings.enabled === "0") return false;
2216
+ if (settings.email_enabled === "false" || settings.email_enabled === "0") return false;
2217
+ const key = `email_${audience}_enabled`;
2218
+ return settings[key] !== "false" && settings[key] !== "0";
2219
+ }
2220
+ chunkUSNT2KNT_cjs.__name(isAudienceEmailEnabled, "isAudienceEmailEnabled");
2221
+ async function getOrderVendorEmails(orderId, orderVendorId, dataSource, entityMap) {
2222
+ const emails = /* @__PURE__ */ new Set();
2223
+ if (!entityMap.vendors) return [];
2224
+ const vendorRepo = dataSource.getRepository(entityMap.vendors);
2225
+ if (orderVendorId) {
2226
+ try {
2227
+ const v = await vendorRepo.findOne({
2228
+ where: {
2229
+ id: orderVendorId
2230
+ }
2231
+ });
2232
+ if (v) {
2233
+ const row = v;
2234
+ if (row.email?.trim()) emails.add(row.email.trim());
2235
+ if (row.supportEmail?.trim()) emails.add(row.supportEmail.trim());
2236
+ }
2237
+ } catch {
2238
+ }
2239
+ }
2240
+ if (entityMap.order_items) {
2241
+ try {
2242
+ const items = await dataSource.getRepository(entityMap.order_items).find({
2243
+ where: {
2244
+ orderId
2245
+ },
2246
+ relations: [
2247
+ "product"
2248
+ ]
2249
+ });
2250
+ for (const item of items) {
2251
+ const row = item;
2252
+ const vId = row.product?.vendorId;
2253
+ if (vId && vId !== orderVendorId) {
2254
+ const v = await vendorRepo.findOne({
2255
+ where: {
2256
+ id: vId
2257
+ }
2258
+ });
2259
+ if (v) {
2260
+ const vRow = v;
2261
+ if (vRow.email?.trim()) emails.add(vRow.email.trim());
2262
+ if (vRow.supportEmail?.trim()) emails.add(vRow.supportEmail.trim());
2263
+ }
2264
+ }
2265
+ }
2266
+ } catch {
2267
+ }
2268
+ }
2269
+ return Array.from(emails);
2270
+ }
2271
+ chunkUSNT2KNT_cjs.__name(getOrderVendorEmails, "getOrderVendorEmails");
2272
+ async function getAdminRecipientEmails(dataSource, entityMap) {
2273
+ const emails = /* @__PURE__ */ new Set();
2274
+ const emailSettings = await getSettingsGroup(dataSource, entityMap, "email");
2275
+ const rawRecipients = [
2276
+ emailSettings.salesTeamEmails,
2277
+ emailSettings.salesTeamEmail,
2278
+ emailSettings.fulfilmentTeamEmails,
2279
+ emailSettings.fulfilmentTeamEmail,
2280
+ emailSettings.adminEmail
2281
+ ];
2282
+ for (const raw of rawRecipients) {
2283
+ if (!raw) continue;
2284
+ const parts = String(raw).split(/[,;]/);
2285
+ for (const p of parts) {
2286
+ const trimmed = p.trim();
2287
+ if (trimmed && trimmed.includes("@")) emails.add(trimmed.toLowerCase());
2288
+ }
2289
+ }
2290
+ if (entityMap.users) {
2291
+ try {
2292
+ const userRepo = dataSource.getRepository(entityMap.users);
2293
+ const adminUsers = await userRepo.find({
2294
+ where: [
2295
+ {
2296
+ groupId: 1,
2297
+ blocked: false
2298
+ },
2299
+ {
2300
+ adminAccess: true,
2301
+ blocked: false
2302
+ }
2303
+ ]
2304
+ });
2305
+ for (const u of adminUsers) {
2306
+ const em = u.email?.trim().toLowerCase();
2307
+ if (em && em.includes("@")) {
2308
+ emails.add(em);
2309
+ }
2310
+ }
2311
+ } catch {
2312
+ }
2313
+ }
2314
+ try {
2315
+ const rows = await dataSource.query(`SELECT DISTINCT LOWER(TRIM(email)) AS email
2316
+ FROM users
2317
+ WHERE ("groupId" = 1 OR "adminAccess" = true)
2318
+ AND COALESCE(blocked, false) = false
2319
+ AND COALESCE(deleted, false) = false
2320
+ AND "deletedAt" IS NULL`);
2321
+ for (const r of rows) {
2322
+ const em = r.email?.trim().toLowerCase();
2323
+ if (em && em.includes("@")) {
2324
+ emails.add(em);
2325
+ }
2326
+ }
2327
+ } catch {
2328
+ }
2329
+ return Array.from(emails);
2330
+ }
2331
+ chunkUSNT2KNT_cjs.__name(getAdminRecipientEmails, "getAdminRecipientEmails");
2332
+ function formatBodyForEmailHtml(body) {
2333
+ if (!body) return "";
2334
+ if (/^\s*<(html|body|table|section)[\s>]/i.test(body.trim())) {
2335
+ return body;
2336
+ }
2337
+ const withBreaks = body.replace(/\r\n/g, "\n").replace(/\n/g, "<br />");
2338
+ return `<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; font-size: 15px; line-height: 1.6; color: #1f2937;">${withBreaks}</div>`;
2339
+ }
2340
+ chunkUSNT2KNT_cjs.__name(formatBodyForEmailHtml, "formatBodyForEmailHtml");
2341
+ async function sendEmailForTrigger(triggerKey, orderId, audienceType, variables, contactEmail, dataSource, entityMap, getCms) {
2342
+ const template = await loadBoundTemplate(triggerKey, "email", audienceType, dataSource, entityMap);
2343
+ if (!template) return;
2344
+ const [branding, emailSettings] = await Promise.all([
2345
+ getSettingsGroup(dataSource, entityMap, "branding"),
2346
+ getSettingsGroup(dataSource, entityMap, "email")
2347
+ ]);
2348
+ const companyDetails = chunkBXYZDMTZ_cjs.mergeEmailLayoutCompanyDetails(branding, emailSettings);
2349
+ const subject = chunkOOTLNH63_cjs.applyTemplateVars(template.subject ?? "Order update", variables);
2350
+ const rawBody = chunkOOTLNH63_cjs.applyTemplateVars(template.body, variables);
2351
+ const bodyHtml = formatBodyForEmailHtml(rawBody);
2352
+ const html = renderLayout({
2353
+ bodyHtml,
2354
+ companyDetails
2355
+ });
2356
+ const cms = await getCms();
2357
+ const isCustomer = audienceType === "customers";
2358
+ await chunkV25RDUOU_cjs.queueEmail(cms, {
2359
+ to: contactEmail,
2360
+ subject,
2361
+ html,
2362
+ text: rawBody.replace(/<[^>]+>/g, "").trim(),
2363
+ ...isCustomer ? {
2364
+ orderId,
2365
+ attachInvoice: true
2366
+ } : {}
2367
+ });
2368
+ console.log("[order-notifications] email queued", {
2369
+ triggerKey,
2370
+ audienceType,
2371
+ orderId,
2372
+ to: contactEmail,
2373
+ attachInvoice: isCustomer
2374
+ });
2375
+ }
2376
+ chunkUSNT2KNT_cjs.__name(sendEmailForTrigger, "sendEmailForTrigger");
2377
+ async function sendWhatsAppForTrigger(triggerKey, orderId, phone, variables, dataSource, entityMap, getCms) {
2378
+ const template = await loadBoundTemplate(triggerKey, "whatsapp", "customers", dataSource, entityMap);
2379
+ if (!template) return;
2380
+ const paramOrder = template.providerMeta?.paramOrder ?? [];
2381
+ if (paramOrder.length === 0) {
2382
+ console.warn("[order-notifications] WhatsApp template has no paramOrder set \u2014 variables may send blank/wrong", {
2383
+ triggerKey
2384
+ });
2385
+ }
2386
+ const cms = await getCms();
2387
+ const metaTemplateName = resolveMetaWhatsAppTemplateName({
2388
+ externalTemplateRef: template.externalTemplateRef,
2389
+ name: template.name
2390
+ });
2391
+ if (!metaTemplateName) {
2392
+ console.warn("[order-notifications] bound WhatsApp template has no Meta template name", {
2393
+ triggerKey,
2394
+ templateId: template.id
2395
+ });
2396
+ return;
2397
+ }
2398
+ const sent = await queueWhatsApp(cms, {
2399
+ to: phone.trim(),
2400
+ templateKey: triggerKey,
2401
+ externalTemplateRef: metaTemplateName,
2402
+ templateLanguage: template.providerMeta?.language ?? void 0,
2403
+ body: template.body,
2404
+ variables,
2405
+ templateParamOrder: paramOrder
2406
+ });
2407
+ if (!sent) {
2408
+ console.warn("[order-notifications] WhatsApp send failed", {
2409
+ triggerKey,
2410
+ orderId,
2411
+ hint: "Register whatsappPlugin() + queuePlugin() in getCms(), or set WHATSAPP_ACCESS_TOKEN and WHATSAPP_PHONE_NUMBER_ID"
2412
+ });
2413
+ } else {
2414
+ console.log("[order-notifications] WhatsApp queued/sent", {
2415
+ triggerKey,
2416
+ orderId,
2417
+ phone: maskPhone3(phone)
2418
+ });
2419
+ }
2420
+ }
2421
+ chunkUSNT2KNT_cjs.__name(sendWhatsAppForTrigger, "sendWhatsAppForTrigger");
2422
+ async function isOrderPaymentConfirmed(orderId, dataSource, entityMap) {
2423
+ if (!entityMap.orders) return true;
2424
+ try {
2425
+ const orderRepo = dataSource.getRepository(entityMap.orders);
2426
+ const order = await orderRepo.findOne({
2427
+ where: {
2428
+ id: orderId
2429
+ }
2430
+ });
2431
+ if (!order) return false;
2432
+ const o = order;
2433
+ const status = String(o.status ?? "").toLowerCase().trim();
2434
+ const total = Number(o.total ?? 0);
2435
+ if (total <= 0) return true;
2436
+ const confirmedStatuses = [
2437
+ "confirmed",
2438
+ "completed",
2439
+ "processing",
2440
+ "paid",
2441
+ "fulfilled",
2442
+ "shipped"
2443
+ ];
2444
+ if (confirmedStatuses.includes(status)) {
2445
+ return true;
2446
+ }
2447
+ if (entityMap.payments) {
2448
+ const paymentRepo = dataSource.getRepository(entityMap.payments);
2449
+ const payment = await paymentRepo.findOne({
2450
+ where: {
2451
+ orderId
2452
+ }
2453
+ });
2454
+ if (payment) {
2455
+ const pStatus = String(payment.status ?? "").toLowerCase().trim();
2456
+ if (pStatus === "paid" || pStatus === "captured" || pStatus === "completed") {
2457
+ return true;
2458
+ }
2459
+ }
2460
+ }
2461
+ return false;
2462
+ } catch (err) {
2463
+ console.error("[order-notifications] failed to check order payment confirmation", err);
2464
+ return false;
2465
+ }
2466
+ }
2467
+ chunkUSNT2KNT_cjs.__name(isOrderPaymentConfirmed, "isOrderPaymentConfirmed");
2468
+ async function handleTrigger(triggerKey, payload, dataSource, entityMap, getCms) {
2469
+ console.log("[order-notifications] received", {
2470
+ triggerKey,
2471
+ orderId: payload.orderId,
2472
+ phone: maskPhone3(payload.customerPhone)
2473
+ });
2474
+ if (triggerKey === "order_placed") {
2475
+ const isConfirmed = await isOrderPaymentConfirmed(payload.orderId, dataSource, entityMap);
2476
+ if (!isConfirmed) {
2477
+ console.log("[order-notifications] skip order_placed: order payment not confirmed yet", {
2478
+ orderId: payload.orderId
2479
+ });
2480
+ return;
2481
+ }
2482
+ }
2483
+ const [emailChannelEnabled, whatsappChannelEnabled] = await Promise.all([
2484
+ isEmailNotificationsEnabled(dataSource, entityMap),
2485
+ isWhatsappNotificationsEnabled(dataSource, entityMap)
2486
+ ]);
2487
+ if (!emailChannelEnabled && !whatsappChannelEnabled) {
2488
+ console.warn("[order-notifications] skip: Both Email and WhatsApp notifications are disabled");
2489
+ return;
2490
+ }
2491
+ const triggerEnabled = await isTriggerCatalogEnabled(triggerKey, dataSource, entityMap);
2492
+ if (!triggerEnabled) return;
2493
+ const context = await loadOrderNotificationContexts(payload.orderId, {
2494
+ dataSource,
2495
+ entityMap
2496
+ });
2497
+ const variables = mergeTriggerVariables(payload, context?.variables);
2498
+ const contactEmail = context?.contact.email?.trim() || "";
2499
+ const contactPhone = context?.contact.phone?.trim() || payload.customerPhone?.trim() || "";
2500
+ if (emailChannelEnabled) {
2501
+ const [customerEnabled, vendorEnabled, adminEnabled] = await Promise.all([
2502
+ isAudienceEmailEnabled(dataSource, entityMap, "customer"),
2503
+ isAudienceEmailEnabled(dataSource, entityMap, "vendor"),
2504
+ isAudienceEmailEnabled(dataSource, entityMap, "admin")
2505
+ ]);
2506
+ if (customerEnabled && contactEmail) {
2507
+ await sendEmailForTrigger(triggerKey, payload.orderId, "customers", variables, contactEmail, dataSource, entityMap, getCms);
2508
+ }
2509
+ if (vendorEnabled) {
2510
+ const multiVendorSettings = await getSettingsGroup(dataSource, entityMap, "multi_vendor");
2511
+ if (multiVendorSettings.enabled !== "false" && multiVendorSettings.enabled !== "0") {
2512
+ const vendorEmails = await getOrderVendorEmails(payload.orderId, payload.vendorId, dataSource, entityMap);
2513
+ for (const vEmail of vendorEmails) {
2514
+ await sendEmailForTrigger(triggerKey, payload.orderId, "vendors", variables, vEmail, dataSource, entityMap, getCms);
2515
+ }
2516
+ }
2517
+ }
2518
+ if (adminEnabled) {
2519
+ const adminEmails = await getAdminRecipientEmails(dataSource, entityMap);
2520
+ for (const aEmail of adminEmails) {
2521
+ await sendEmailForTrigger(triggerKey, payload.orderId, "admin", variables, aEmail, dataSource, entityMap, getCms);
2522
+ }
2523
+ }
2524
+ } else {
2525
+ console.log("[order-notifications] skip email: Email notifications channel is disabled");
2526
+ }
2527
+ if (whatsappChannelEnabled) {
2528
+ if (contactPhone) {
2529
+ await sendWhatsAppForTrigger(triggerKey, payload.orderId, contactPhone, variables, dataSource, entityMap, getCms);
2530
+ } else {
2531
+ console.warn("[order-notifications] skip WhatsApp: no customer phone", {
2532
+ triggerKey,
2533
+ orderId: payload.orderId
2534
+ });
2535
+ }
2536
+ } else {
2537
+ console.log("[order-notifications] skip WhatsApp: WhatsApp notifications channel is disabled");
2538
+ }
2539
+ }
2540
+ chunkUSNT2KNT_cjs.__name(handleTrigger, "handleTrigger");
2541
+ function initWhatsappTriggerDispatcher(deps) {
2542
+ if (initialized) return;
2543
+ initialized = true;
2544
+ const { dataSource, entityMap, getCms } = deps;
2545
+ chunkUCKN4BBY_cjs.notificationTriggerEmitter.onAnyTrigger((triggerKey, payload) => {
2546
+ void handleTrigger(triggerKey, payload, dataSource, entityMap, getCms).catch((err) => {
2547
+ console.error("[order-notifications]", triggerKey, err);
2548
+ });
2549
+ });
2550
+ console.log("[order-notifications] dispatcher initialized (email + WhatsApp, unified via order_notification_bindings)");
2551
+ }
2552
+ chunkUSNT2KNT_cjs.__name(initWhatsappTriggerDispatcher, "initWhatsappTriggerDispatcher");
2553
+ async function resendOrderNotification(triggerKey, orderId, deps) {
2554
+ if (!deps.entityMap.orders) return {
2555
+ ok: false,
2556
+ error: "orders entity missing"
2557
+ };
2558
+ const [emailEnabled, whatsappEnabled] = await Promise.all([
2559
+ isEmailNotificationsEnabled(deps.dataSource, deps.entityMap),
2560
+ isWhatsappNotificationsEnabled(deps.dataSource, deps.entityMap)
2561
+ ]);
2562
+ if (!emailEnabled && !whatsappEnabled) {
2563
+ return {
2564
+ ok: false,
2565
+ error: "Both Email and WhatsApp notifications are disabled"
2566
+ };
2567
+ }
2568
+ const triggerEnabled = await isTriggerCatalogEnabled(triggerKey, deps.dataSource, deps.entityMap);
2569
+ if (!triggerEnabled) {
2570
+ return {
2571
+ ok: false,
2572
+ error: `Trigger "${triggerKey}" is disabled or migration 1781230000000 not applied`
2573
+ };
2574
+ }
2575
+ const order = await deps.dataSource.getRepository(deps.entityMap.orders).findOne({
2576
+ where: {
2577
+ id: orderId,
2578
+ deleted: false
2579
+ },
2580
+ relations: [
2581
+ "contact"
2582
+ ]
2583
+ });
2584
+ if (!order) return {
2585
+ ok: false,
2586
+ error: "Order not found"
2587
+ };
2588
+ const o = order;
2589
+ await handleTrigger(triggerKey, {
2590
+ orderId,
2591
+ orderNumber: String(o.orderNumber ?? ""),
2592
+ customerName: o.contact?.name ?? "",
2593
+ customerPhone: o.contact?.phone ?? null,
2594
+ total: o.total ?? 0,
2595
+ currency: String(o.currency ?? "INR"),
2596
+ vendorId: o.vendorId ?? null
2597
+ }, deps.dataSource, deps.entityMap, deps.getCms);
2598
+ return {
2599
+ ok: true
2600
+ };
2601
+ }
2602
+ chunkUSNT2KNT_cjs.__name(resendOrderNotification, "resendOrderNotification");
2603
+
1428
2604
  exports.CHAT_EMAIL_LOG = CHAT_EMAIL_LOG;
1429
2605
  exports.EmailService = EmailService;
2606
+ exports.WhatsAppService = WhatsAppService;
1430
2607
  exports.buildChatbotSystemPromptWithEmailTool = buildChatbotSystemPromptWithEmailTool;
2608
+ exports.buildEventOrderTemplateVariables = buildEventOrderTemplateVariables;
1431
2609
  exports.buildTranscriptForLeadEmail = buildTranscriptForLeadEmail;
1432
2610
  exports.detectChatLeadIntent = detectChatLeadIntent;
1433
2611
  exports.emailPlugin = emailPlugin;
1434
2612
  exports.emailTemplates = emailTemplates;
2613
+ exports.initWhatsappTriggerDispatcher = initWhatsappTriggerDispatcher;
2614
+ exports.isWhatsAppPluginEnabled = isWhatsAppPluginEnabled;
1435
2615
  exports.joinRecipientsForSend = joinRecipientsForSend;
2616
+ exports.mergeWhatsAppConfigLayers = mergeWhatsAppConfigLayers;
1436
2617
  exports.parseEmailRecipientsFromConfig = parseEmailRecipientsFromConfig;
1437
2618
  exports.parseIntentRecipientEmails = parseIntentRecipientEmails;
2619
+ exports.queueWhatsApp = queueWhatsApp;
2620
+ exports.registerWhatsAppQueueProcessor = registerWhatsAppQueueProcessor;
1438
2621
  exports.renderEmail = renderEmail;
1439
2622
  exports.renderLayout = renderLayout;
2623
+ exports.resendOrderNotification = resendOrderNotification;
1440
2624
  exports.resolveChatEmailToolSettings = resolveChatEmailToolSettings;
1441
2625
  exports.sendChatLeadEmail = sendChatLeadEmail;
1442
2626
  exports.serializeEmailRecipients = serializeEmailRecipients;
2627
+ exports.whatsAppConfigured = whatsAppConfigured;