@infuro/cms-core 1.0.44 → 1.0.46

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 (66) hide show
  1. package/README.md +19 -2
  2. package/dist/admin.cjs +1413 -620
  3. package/dist/admin.d.cts +8 -2
  4. package/dist/admin.d.ts +8 -2
  5. package/dist/admin.js +1414 -621
  6. package/dist/api.cjs +40 -37
  7. package/dist/api.d.cts +3 -1
  8. package/dist/api.d.ts +3 -1
  9. package/dist/api.js +8 -5
  10. package/dist/auth.cjs +23 -23
  11. package/dist/auth.d.cts +1 -0
  12. package/dist/auth.d.ts +1 -0
  13. package/dist/auth.js +2 -2
  14. package/dist/chunk-2KUCQVAQ.js +936 -0
  15. package/dist/chunk-3K5AIEIZ.js +256 -0
  16. package/dist/{chunk-I6NH72MO.js → chunk-57O3HZPG.js} +9 -1
  17. package/dist/chunk-7PMHZRF3.cjs +872 -0
  18. package/dist/chunk-BXYZDMTZ.cjs +953 -0
  19. package/dist/chunk-CQHXW4TR.js +107 -0
  20. package/dist/{chunk-NNQBWDCI.js → chunk-CRYKVJTO.js} +55 -959
  21. package/dist/chunk-DBPSJYLZ.js +47 -0
  22. package/dist/{chunk-TAHX46EI.cjs → chunk-FBKDMRQL.cjs} +9 -1
  23. package/dist/chunk-GUSHM5HN.js +862 -0
  24. package/dist/chunk-HY3AXLOI.cjs +265 -0
  25. package/dist/chunk-L3525VXI.js +243 -0
  26. package/dist/{chunk-ZMRQ72F6.cjs → chunk-NBY4NVTY.cjs} +3 -3
  27. package/dist/chunk-OY2YTBMP.cjs +50 -0
  28. package/dist/{chunk-BLR6H5GL.js → chunk-SF2XKMCI.js} +3 -3
  29. package/dist/{chunk-22FFHLTO.cjs → chunk-TCSGFAWB.cjs} +56 -970
  30. package/dist/{chunk-P2IWWBJV.cjs → chunk-UDVLFVEC.cjs} +1314 -601
  31. package/dist/{chunk-SQGBHYJF.js → chunk-UKC3HYXI.js} +1245 -548
  32. package/dist/chunk-V25RDUOU.cjs +248 -0
  33. package/dist/chunk-XMRMZ6NQ.cjs +109 -0
  34. package/dist/cli.cjs +9 -4
  35. package/dist/cli.js +9 -4
  36. package/dist/{email-queue-TPZWLTIV.cjs → email-queue-6NJY6HNM.cjs} +7 -5
  37. package/dist/email-queue-OI2HSCGE.js +4 -0
  38. package/dist/erp-order-invoice-3GXDE443.js +3 -0
  39. package/dist/erp-order-invoice-EYYNR526.cjs +24 -0
  40. package/dist/index.cjs +426 -359
  41. package/dist/index.d.cts +308 -20
  42. package/dist/index.d.ts +308 -20
  43. package/dist/index.js +31 -110
  44. package/dist/migrations/1781800000000-CustomerPhoneNullable.ts +53 -0
  45. package/dist/migrations/1781810000000-OrderItemsVariantId.ts +44 -0
  46. package/dist/migrations/1781900000000-UserInviteStatusToken.ts +46 -0
  47. package/dist/migrations/1782000000000-ComboSlug.ts +58 -0
  48. package/dist/migrations/1782100000000-DiscountDeviceType.ts +20 -0
  49. package/dist/order-inventory-2MYRKEPC.cjs +39 -0
  50. package/dist/order-inventory-76V4XIGW.js +2 -0
  51. package/dist/order-notification-dispatcher-SBQAMM5V.cjs +21 -0
  52. package/dist/{order-notification-dispatcher-XWIJYDGA.js → order-notification-dispatcher-ZJZB2HUN.js} +5 -2
  53. package/dist/retire-soft-deleted-unique-5VXBA5XX.cjs +15 -0
  54. package/dist/retire-soft-deleted-unique-NXQIH4BC.js +2 -0
  55. package/dist/send-order-placed-emails-B5ZVJT7T.cjs +15 -0
  56. package/dist/send-order-placed-emails-WPI37KRZ.js +6 -0
  57. package/package.json +5 -4
  58. package/src/admin/admin.css +27 -24
  59. package/dist/chunk-5ELSW2UP.js +0 -150
  60. package/dist/chunk-PNOKNSG2.cjs +0 -222
  61. package/dist/chunk-WEMDMVHQ.js +0 -216
  62. package/dist/chunk-XW5ATPRW.cjs +0 -155
  63. package/dist/email-queue-CGZBVVAS.js +0 -2
  64. package/dist/erp-order-invoice-3YISSOWW.js +0 -3
  65. package/dist/erp-order-invoice-ZR5F2KTA.cjs +0 -12
  66. package/dist/order-notification-dispatcher-RU7BTGGX.cjs +0 -18
@@ -0,0 +1,872 @@
1
+ 'use strict';
2
+
3
+ var chunkTCSGFAWB_cjs = require('./chunk-TCSGFAWB.cjs');
4
+ var chunkUCKN4BBY_cjs = require('./chunk-UCKN4BBY.cjs');
5
+ var chunkV25RDUOU_cjs = require('./chunk-V25RDUOU.cjs');
6
+ var chunkBXYZDMTZ_cjs = require('./chunk-BXYZDMTZ.cjs');
7
+ var chunkOOTLNH63_cjs = require('./chunk-OOTLNH63.cjs');
8
+ var chunkUSNT2KNT_cjs = require('./chunk-USNT2KNT.cjs');
9
+
10
+ // src/lib/event-order-template-context.ts
11
+ function resolvePublicSiteUrl() {
12
+ if (typeof process !== "undefined") {
13
+ const u = (process.env.CMS_PUBLIC_URL || process.env.NEXT_PUBLIC_SITE_URL || "").trim();
14
+ if (u) return u.replace(/\/+$/, "");
15
+ const v = (process.env.VERCEL_URL || "").trim();
16
+ if (v) return v.startsWith("http") ? v.replace(/\/+$/, "") : `https://${v.replace(/\/+$/, "")}`;
17
+ }
18
+ return "";
19
+ }
20
+ chunkUSNT2KNT_cjs.__name(resolvePublicSiteUrl, "resolvePublicSiteUrl");
21
+ function buildOrderTrackUrl(qrToken, siteUrl) {
22
+ const token = qrToken?.trim();
23
+ if (!token) return "";
24
+ const base = (siteUrl ?? resolvePublicSiteUrl()).replace(/\/+$/, "");
25
+ if (!base) return `/track/${token}`;
26
+ return `${base}/track/${token}`;
27
+ }
28
+ chunkUSNT2KNT_cjs.__name(buildOrderTrackUrl, "buildOrderTrackUrl");
29
+ function buildOrderQrImageUrl(trackUrl, size = 200) {
30
+ if (!trackUrl.trim()) return "";
31
+ return `https://api.qrserver.com/v1/create-qr-code/?size=${size}x${size}&data=${encodeURIComponent(trackUrl)}`;
32
+ }
33
+ chunkUSNT2KNT_cjs.__name(buildOrderQrImageUrl, "buildOrderQrImageUrl");
34
+ function formatOrderDetailsText(input) {
35
+ const { orderNumber, total, currency, lines } = input;
36
+ const header = `Order: ${orderNumber}
37
+ Total: ${total} ${currency}`;
38
+ if (!lines.length) return header;
39
+ const items = lines.map((line) => `- ${line.name} \xD7 ${line.quantity} (${line.total} ${line.currency})`).join("\n");
40
+ return `${header}
41
+
42
+ Items:
43
+ ${items}`;
44
+ }
45
+ chunkUSNT2KNT_cjs.__name(formatOrderDetailsText, "formatOrderDetailsText");
46
+ function formatOrderDetailsHtml(input) {
47
+ const text = formatOrderDetailsText(input);
48
+ const escaped = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
49
+ return `<pre style="font-family:inherit;white-space:pre-wrap;margin:0">${escaped}</pre>`;
50
+ }
51
+ chunkUSNT2KNT_cjs.__name(formatOrderDetailsHtml, "formatOrderDetailsHtml");
52
+ function buildEventOrderTemplateVariables(input) {
53
+ const { event, order, contact, productNames, orderLines = [], siteUrl } = input;
54
+ const trackUrl = buildOrderTrackUrl(order.qrToken, siteUrl);
55
+ const qrImageUrl = buildOrderQrImageUrl(trackUrl);
56
+ const orderDetailsPayload = {
57
+ orderNumber: String(order.orderNumber ?? ""),
58
+ total: String(order.total ?? ""),
59
+ currency: String(order.currency ?? "INR"),
60
+ lines: orderLines
61
+ };
62
+ return {
63
+ eventName: event.name ?? "",
64
+ eventSlug: event.slug ?? "",
65
+ venue: event.venue ?? "",
66
+ startDate: formatEventDate(event.startDate),
67
+ orderNumber: String(order.orderNumber ?? ""),
68
+ orderTotal: String(order.total ?? ""),
69
+ currency: String(order.currency ?? "INR"),
70
+ customerName: contact.name?.trim() || contact.email?.split("@")[0] || "Guest",
71
+ customerEmail: contact.email?.trim() || "",
72
+ customerPhone: contact.phone?.trim() || "",
73
+ productNames,
74
+ trackUrl,
75
+ qrImageUrl,
76
+ orderDetails: formatOrderDetailsText(orderDetailsPayload),
77
+ orderDetailsHtml: formatOrderDetailsHtml(orderDetailsPayload)
78
+ };
79
+ }
80
+ chunkUSNT2KNT_cjs.__name(buildEventOrderTemplateVariables, "buildEventOrderTemplateVariables");
81
+ function formatEventDate(d) {
82
+ if (!d) return "";
83
+ try {
84
+ return new Date(d).toLocaleString(void 0, {
85
+ dateStyle: "medium",
86
+ timeStyle: "short"
87
+ });
88
+ } catch {
89
+ return String(d);
90
+ }
91
+ }
92
+ chunkUSNT2KNT_cjs.__name(formatEventDate, "formatEventDate");
93
+
94
+ // src/lib/order-notification-context.ts
95
+ async function loadOrderLines(dataSource, entityMap, orderId, currency) {
96
+ if (!entityMap.order_items) return [];
97
+ const itemRows = await dataSource.getRepository(entityMap.order_items).find({
98
+ where: {
99
+ orderId
100
+ },
101
+ relations: [
102
+ "product"
103
+ ],
104
+ order: {
105
+ id: "ASC"
106
+ }
107
+ });
108
+ return itemRows.map((item) => {
109
+ const row = item;
110
+ const name = row.product?.name || row.product?.title || `Product #${row.productId}`;
111
+ return {
112
+ name: String(name),
113
+ quantity: Number(row.quantity) || 1,
114
+ total: row.total,
115
+ currency
116
+ };
117
+ });
118
+ }
119
+ chunkUSNT2KNT_cjs.__name(loadOrderLines, "loadOrderLines");
120
+ async function loadPrimaryEvent(dataSource, entityMap, itemRows) {
121
+ if (!entityMap.event_products || !entityMap.events) {
122
+ return {
123
+ productNames: []
124
+ };
125
+ }
126
+ const epRepo = dataSource.getRepository(entityMap.event_products);
127
+ const eventRepo = dataSource.getRepository(entityMap.events);
128
+ const eventMap = /* @__PURE__ */ new Map();
129
+ for (const item of itemRows) {
130
+ const productId = item.productId;
131
+ const productName = item.product?.name || item.product?.title || `Product #${productId}`;
132
+ const eps = await epRepo.find({
133
+ where: {
134
+ productId,
135
+ isActive: true
136
+ }
137
+ });
138
+ for (const ep of eps) {
139
+ const eventId = ep.eventId;
140
+ const existing = eventMap.get(eventId);
141
+ if (existing) {
142
+ if (!existing.productNames.includes(String(productName))) {
143
+ existing.productNames.push(String(productName));
144
+ }
145
+ } else {
146
+ const event = await eventRepo.findOne({
147
+ where: {
148
+ id: eventId,
149
+ deleted: false
150
+ }
151
+ });
152
+ if (event) {
153
+ eventMap.set(eventId, {
154
+ event,
155
+ productNames: [
156
+ String(productName)
157
+ ]
158
+ });
159
+ }
160
+ }
161
+ }
162
+ }
163
+ const first = eventMap.values().next().value;
164
+ if (!first) return {
165
+ productNames: []
166
+ };
167
+ return {
168
+ event: first.event,
169
+ productNames: first.productNames
170
+ };
171
+ }
172
+ chunkUSNT2KNT_cjs.__name(loadPrimaryEvent, "loadPrimaryEvent");
173
+ async function loadOrderNotificationContexts(orderId, deps) {
174
+ if (!deps.entityMap.orders) return null;
175
+ const order = await deps.dataSource.getRepository(deps.entityMap.orders).findOne({
176
+ where: {
177
+ id: orderId,
178
+ deleted: false
179
+ },
180
+ relations: [
181
+ "contact"
182
+ ]
183
+ });
184
+ if (!order) return null;
185
+ const o = order;
186
+ const contact = o.contact ?? {};
187
+ const currency = String(o.currency ?? "INR");
188
+ const siteUrl = resolvePublicSiteUrl();
189
+ const itemRows = await deps.dataSource.getRepository(deps.entityMap.order_items).find({
190
+ where: {
191
+ orderId
192
+ },
193
+ relations: [
194
+ "product"
195
+ ],
196
+ order: {
197
+ id: "ASC"
198
+ }
199
+ });
200
+ const orderLines = await loadOrderLines(deps.dataSource, deps.entityMap, orderId, currency);
201
+ const { event, productNames: eventProductNames } = await loadPrimaryEvent(deps.dataSource, deps.entityMap, itemRows);
202
+ const allProductNames = orderLines.map((line) => line.name);
203
+ const productNames = eventProductNames.length > 0 ? eventProductNames.join(", ") : allProductNames.join(", ");
204
+ const variables = event ? buildEventOrderTemplateVariables({
205
+ event,
206
+ order: {
207
+ orderNumber: o.orderNumber,
208
+ total: o.total,
209
+ currency,
210
+ qrToken: o.qrToken
211
+ },
212
+ contact,
213
+ productNames,
214
+ orderLines,
215
+ siteUrl
216
+ }) : buildEventOrderTemplateVariables({
217
+ event: {
218
+ name: productNames || "Your order",
219
+ slug: "",
220
+ venue: "",
221
+ startDate: null
222
+ },
223
+ order: {
224
+ orderNumber: o.orderNumber,
225
+ total: o.total,
226
+ currency,
227
+ qrToken: o.qrToken
228
+ },
229
+ contact,
230
+ productNames,
231
+ orderLines,
232
+ siteUrl
233
+ });
234
+ return {
235
+ order: {
236
+ id: o.id,
237
+ orderNumber: String(o.orderNumber ?? ""),
238
+ total: o.total,
239
+ currency,
240
+ qrToken: o.qrToken
241
+ },
242
+ contact,
243
+ orderLines,
244
+ productNames,
245
+ event,
246
+ variables
247
+ };
248
+ }
249
+ chunkUSNT2KNT_cjs.__name(loadOrderNotificationContexts, "loadOrderNotificationContexts");
250
+
251
+ // src/plugins/whatsapp/whatsapp-queue.ts
252
+ var WHATSAPP_QUEUE_NAME = "whatsapp";
253
+ function maskPhone(raw) {
254
+ const digits = raw.replace(/\D/g, "");
255
+ if (digits.length <= 4) return "****";
256
+ return `${"*".repeat(Math.max(0, digits.length - 4))}${digits.slice(-4)}`;
257
+ }
258
+ chunkUSNT2KNT_cjs.__name(maskPhone, "maskPhone");
259
+ function registerWhatsAppQueueProcessor(cms) {
260
+ const queue = cms.getPlugin("queue");
261
+ const whatsapp = cms.getPlugin("whatsapp");
262
+ if (!queue) {
263
+ console.warn("[whatsapp] queue processor not registered: queue plugin missing");
264
+ return;
265
+ }
266
+ if (!whatsapp || typeof whatsapp.send !== "function") {
267
+ console.warn("[whatsapp] queue processor not registered: whatsapp plugin missing or not configured");
268
+ return;
269
+ }
270
+ queue.registerProcessor(WHATSAPP_QUEUE_NAME, async (data) => {
271
+ const payload = data;
272
+ if (!payload.to) {
273
+ console.warn("[whatsapp] queue job skipped: missing recipient");
274
+ return;
275
+ }
276
+ console.log("[whatsapp] queue job processing", {
277
+ to: maskPhone(payload.to),
278
+ templateKey: payload.templateKey ?? null,
279
+ externalTemplateRef: payload.externalTemplateRef ?? null
280
+ });
281
+ try {
282
+ const ok = await whatsapp.send(payload);
283
+ if (ok) {
284
+ console.log("[whatsapp] queue job completed", {
285
+ to: maskPhone(payload.to),
286
+ sent: true
287
+ });
288
+ } else {
289
+ console.warn("[whatsapp] queue job completed without delivery", {
290
+ to: maskPhone(payload.to),
291
+ sent: false
292
+ });
293
+ }
294
+ } catch (err) {
295
+ console.error("[whatsapp] queue job failed", {
296
+ to: maskPhone(payload.to),
297
+ error: err instanceof Error ? err.message : String(err)
298
+ });
299
+ }
300
+ });
301
+ console.log("[whatsapp] queue processor registered", {
302
+ queue: WHATSAPP_QUEUE_NAME
303
+ });
304
+ }
305
+ chunkUSNT2KNT_cjs.__name(registerWhatsAppQueueProcessor, "registerWhatsAppQueueProcessor");
306
+ async function queueWhatsApp(cms, payload) {
307
+ const queue = cms.getPlugin("queue");
308
+ if (queue) {
309
+ console.log("[whatsapp] enqueue", {
310
+ to: maskPhone(payload.to),
311
+ templateKey: payload.templateKey ?? null,
312
+ externalTemplateRef: payload.externalTemplateRef ?? null
313
+ });
314
+ await queue.add(WHATSAPP_QUEUE_NAME, payload);
315
+ return true;
316
+ }
317
+ const whatsapp = cms.getPlugin("whatsapp");
318
+ if (whatsapp && typeof whatsapp.send === "function") {
319
+ console.log("[whatsapp] no queue plugin \u2014 sending immediately", {
320
+ to: maskPhone(payload.to)
321
+ });
322
+ return whatsapp.send(payload);
323
+ }
324
+ console.warn("[whatsapp] send skipped: neither queue nor whatsapp plugin available", {
325
+ to: maskPhone(payload.to)
326
+ });
327
+ return false;
328
+ }
329
+ chunkUSNT2KNT_cjs.__name(queueWhatsApp, "queueWhatsApp");
330
+
331
+ // src/plugins/whatsapp/whatsapp-service.ts
332
+ function norm(v) {
333
+ const t = v?.trim();
334
+ return t || void 0;
335
+ }
336
+ chunkUSNT2KNT_cjs.__name(norm, "norm");
337
+ function pick(plugin, db, env, ...keys) {
338
+ for (const key of keys) {
339
+ const pk = key;
340
+ if (pk in plugin && plugin[pk] !== void 0 && String(plugin[pk]).trim() !== "") {
341
+ const v = norm(String(plugin[pk]));
342
+ if (v) return v;
343
+ }
344
+ if (norm(db[key])) return norm(db[key]);
345
+ if (norm(env[key])) return norm(env[key]);
346
+ }
347
+ return void 0;
348
+ }
349
+ chunkUSNT2KNT_cjs.__name(pick, "pick");
350
+ function mergeWhatsAppConfigLayers(env, db, plugin) {
351
+ return {
352
+ accessToken: pick(plugin, db, env, "accessToken", "WHATSAPP_ACCESS_TOKEN"),
353
+ phoneNumberId: pick(plugin, db, env, "phoneNumberId", "WHATSAPP_PHONE_NUMBER_ID"),
354
+ apiVersion: pick(plugin, db, env, "apiVersion", "WHATSAPP_API_VERSION") ?? "v21.0"
355
+ };
356
+ }
357
+ chunkUSNT2KNT_cjs.__name(mergeWhatsAppConfigLayers, "mergeWhatsAppConfigLayers");
358
+ function isWhatsAppPluginEnabled(db) {
359
+ const v = (db.enabled ?? "true").toLowerCase();
360
+ return v !== "false" && v !== "0";
361
+ }
362
+ chunkUSNT2KNT_cjs.__name(isWhatsAppPluginEnabled, "isWhatsAppPluginEnabled");
363
+ function whatsAppConfigured(config) {
364
+ return Boolean(config.accessToken?.trim() && config.phoneNumberId?.trim());
365
+ }
366
+ chunkUSNT2KNT_cjs.__name(whatsAppConfigured, "whatsAppConfigured");
367
+ function normalizeWhatsAppRecipient(to) {
368
+ const digits = to.replace(/\D/g, "");
369
+ return digits;
370
+ }
371
+ chunkUSNT2KNT_cjs.__name(normalizeWhatsAppRecipient, "normalizeWhatsAppRecipient");
372
+ function resolveMetaWhatsAppTemplateName(opts) {
373
+ const ref = opts.externalTemplateRef?.trim();
374
+ const rowName = opts.name?.trim();
375
+ if (ref && !/^\d+$/.test(ref)) return ref;
376
+ if (rowName) return rowName;
377
+ return ref || void 0;
378
+ }
379
+ chunkUSNT2KNT_cjs.__name(resolveMetaWhatsAppTemplateName, "resolveMetaWhatsAppTemplateName");
380
+ function maskPhone2(digits) {
381
+ if (digits.length <= 4) return "****";
382
+ return `${"*".repeat(Math.max(0, digits.length - 4))}${digits.slice(-4)}`;
383
+ }
384
+ chunkUSNT2KNT_cjs.__name(maskPhone2, "maskPhone");
385
+ function logWhatsApp(level, message, extra) {
386
+ const line = extra ? `${message} ${JSON.stringify(extra)}` : message;
387
+ if (level === "info") console.log(`[whatsapp] ${line}`);
388
+ else if (level === "warn") console.warn(`[whatsapp] ${line}`);
389
+ else console.error(`[whatsapp] ${line}`);
390
+ }
391
+ chunkUSNT2KNT_cjs.__name(logWhatsApp, "logWhatsApp");
392
+ var WhatsAppService = class {
393
+ static {
394
+ chunkUSNT2KNT_cjs.__name(this, "WhatsAppService");
395
+ }
396
+ getWhatsAppSettings;
397
+ config;
398
+ getMessageTemplateRow;
399
+ constructor(env, plugin = {}, getWhatsAppSettings, getMessageTemplateRow) {
400
+ this.getWhatsAppSettings = getWhatsAppSettings;
401
+ this.getMessageTemplateRow = getMessageTemplateRow;
402
+ this.config = mergeWhatsAppConfigLayers(env, {}, plugin);
403
+ }
404
+ async mergedConfig() {
405
+ let db = {};
406
+ try {
407
+ db = await this.getWhatsAppSettings?.() ?? {};
408
+ } catch {
409
+ }
410
+ return mergeWhatsAppConfigLayers((typeof process !== "undefined" ? process.env : {}) ?? {}, db, this.config);
411
+ }
412
+ async send(opts) {
413
+ const cfg = await this.mergedConfig();
414
+ if (!whatsAppConfigured(cfg)) {
415
+ logWhatsApp("warn", "send skipped: not configured (Plugins \u2192 WhatsApp or WHATSAPP_ACCESS_TOKEN + WHATSAPP_PHONE_NUMBER_ID)");
416
+ return false;
417
+ }
418
+ const to = normalizeWhatsAppRecipient(opts.to);
419
+ if (!to) {
420
+ logWhatsApp("warn", "send skipped: invalid recipient", {
421
+ rawTo: opts.to
422
+ });
423
+ return false;
424
+ }
425
+ logWhatsApp("info", "send started", {
426
+ to: maskPhone2(to),
427
+ templateKey: opts.templateKey ?? null,
428
+ externalTemplateRef: opts.externalTemplateRef ?? null,
429
+ phoneNumberId: cfg.phoneNumberId
430
+ });
431
+ let templateName = resolveMetaWhatsAppTemplateName({
432
+ externalTemplateRef: opts.externalTemplateRef
433
+ });
434
+ let templateLanguage = opts.templateLanguage?.trim();
435
+ let body = opts.body?.trim() ?? "";
436
+ const variables = opts.variables ?? {};
437
+ if (opts.templateKey?.trim() && this.getMessageTemplateRow) {
438
+ const { resolveEventOrderTemplate, EVENT_ORDER_TEMPLATE_KEY } = await import('./event-order-defaults-EMWBIGL3.cjs');
439
+ if (opts.templateKey.trim() === EVENT_ORDER_TEMPLATE_KEY) {
440
+ const resolved = await resolveEventOrderTemplate("whatsapp", async (ch, key) => {
441
+ const row = await this.getMessageTemplateRow(ch, key);
442
+ if (!row) return null;
443
+ return {
444
+ subject: null,
445
+ body: row.body,
446
+ externalTemplateRef: row.externalTemplateRef,
447
+ enabled: row.enabled
448
+ };
449
+ });
450
+ if (resolved && !resolved.enabled) {
451
+ logWhatsApp("warn", "send skipped: template disabled in message_templates", {
452
+ templateKey: opts.templateKey
453
+ });
454
+ return false;
455
+ }
456
+ if (resolved) {
457
+ body = resolved.body;
458
+ if (resolved.externalTemplateRef) templateName = resolved.externalTemplateRef;
459
+ }
460
+ }
461
+ }
462
+ const { applyTemplateVars: applyTemplateVars2 } = await import('./interpolate-WAKPN56R.cjs');
463
+ if (body) body = applyTemplateVars2(body, variables);
464
+ if (templateName) {
465
+ const paramValues = opts.templateParamOrder?.length ? opts.templateParamOrder.map((key) => String(variables[key] ?? "").slice(0, 1024)) : Object.keys(variables).length ? Object.values(variables) : body ? [
466
+ body
467
+ ] : [];
468
+ logWhatsApp("info", "sending template message", {
469
+ to: maskPhone2(to),
470
+ templateName,
471
+ paramCount: paramValues.length,
472
+ paramOrder: opts.templateParamOrder ?? null
473
+ });
474
+ return this.sendTemplateMessage(cfg, to, templateName, paramValues, templateLanguage);
475
+ }
476
+ if (!body) {
477
+ logWhatsApp("warn", "send skipped: no template name and empty body", {
478
+ to: maskPhone2(to)
479
+ });
480
+ return false;
481
+ }
482
+ logWhatsApp("info", "sending text message", {
483
+ to: maskPhone2(to),
484
+ bodyLength: body.length
485
+ });
486
+ return this.sendTextMessage(cfg, to, body);
487
+ }
488
+ async sendTextMessage(cfg, to, body) {
489
+ const url = `https://graph.facebook.com/${cfg.apiVersion}/${cfg.phoneNumberId}/messages`;
490
+ const res = await fetch(url, {
491
+ method: "POST",
492
+ headers: {
493
+ Authorization: `Bearer ${cfg.accessToken}`,
494
+ "Content-Type": "application/json"
495
+ },
496
+ body: JSON.stringify({
497
+ messaging_product: "whatsapp",
498
+ to,
499
+ type: "text",
500
+ text: {
501
+ body
502
+ }
503
+ })
504
+ });
505
+ if (!res.ok) {
506
+ const errText = await res.text().catch(() => "");
507
+ logWhatsApp("error", "text send failed", {
508
+ status: res.status,
509
+ to: maskPhone2(to),
510
+ error: errText
511
+ });
512
+ return false;
513
+ }
514
+ const data = await res.json().catch(() => ({}));
515
+ logWhatsApp("info", "text send succeeded", {
516
+ to: maskPhone2(to),
517
+ messageId: data.messages?.[0]?.id ?? null
518
+ });
519
+ return true;
520
+ }
521
+ async sendTemplateMessage(cfg, to, templateName, paramValues, templateLanguage) {
522
+ const components = paramValues.length > 0 ? [
523
+ {
524
+ type: "body",
525
+ parameters: paramValues.map((text) => ({
526
+ type: "text",
527
+ text: String(text).slice(0, 1024)
528
+ }))
529
+ }
530
+ ] : void 0;
531
+ const url = `https://graph.facebook.com/${cfg.apiVersion}/${cfg.phoneNumberId}/messages`;
532
+ const payload = {
533
+ messaging_product: "whatsapp",
534
+ to,
535
+ type: "template",
536
+ template: {
537
+ name: templateName,
538
+ language: {
539
+ code: templateLanguage?.trim() || "en"
540
+ },
541
+ ...components ? {
542
+ components
543
+ } : {}
544
+ }
545
+ };
546
+ const res = await fetch(url, {
547
+ method: "POST",
548
+ headers: {
549
+ Authorization: `Bearer ${cfg.accessToken}`,
550
+ "Content-Type": "application/json"
551
+ },
552
+ body: JSON.stringify(payload)
553
+ });
554
+ if (!res.ok) {
555
+ const errText = await res.text().catch(() => "");
556
+ logWhatsApp("error", "template send failed", {
557
+ status: res.status,
558
+ to: maskPhone2(to),
559
+ templateName,
560
+ paramCount: paramValues.length,
561
+ error: errText
562
+ });
563
+ return false;
564
+ }
565
+ const data = await res.json().catch(() => ({}));
566
+ logWhatsApp("info", "template send succeeded", {
567
+ to: maskPhone2(to),
568
+ templateName,
569
+ messageId: data.messages?.[0]?.id ?? null
570
+ });
571
+ return true;
572
+ }
573
+ };
574
+
575
+ // src/lib/order-notification-dispatcher.ts
576
+ var initialized = false;
577
+ function maskPhone3(raw) {
578
+ const digits = String(raw ?? "").replace(/\D/g, "");
579
+ if (digits.length <= 4) return "(none)";
580
+ return `***${digits.slice(-4)}`;
581
+ }
582
+ chunkUSNT2KNT_cjs.__name(maskPhone3, "maskPhone");
583
+ async function getSettingsGroup(dataSource, entityMap, group) {
584
+ if (!entityMap.configs) return {};
585
+ const rows = await dataSource.getRepository(entityMap.configs).find({
586
+ where: {
587
+ settings: group,
588
+ deleted: false
589
+ }
590
+ });
591
+ return Object.fromEntries(rows.map((r) => [
592
+ r.key,
593
+ r.value
594
+ ]));
595
+ }
596
+ chunkUSNT2KNT_cjs.__name(getSettingsGroup, "getSettingsGroup");
597
+ async function isOrderNotificationsEnabled(dataSource, entityMap) {
598
+ const settings = await getSettingsGroup(dataSource, entityMap, "event_notifications");
599
+ return settings.enabled !== "false" && settings.enabled !== "0";
600
+ }
601
+ chunkUSNT2KNT_cjs.__name(isOrderNotificationsEnabled, "isOrderNotificationsEnabled");
602
+ async function isTriggerCatalogEnabled(triggerKey, dataSource, entityMap) {
603
+ if (!entityMap.order_notification_triggers) {
604
+ console.warn("[order-notifications] order_notification_triggers missing \u2014 run migration 1781230000000-AddOrderNotificationTriggers");
605
+ return false;
606
+ }
607
+ try {
608
+ const row = await dataSource.getRepository(entityMap.order_notification_triggers).findOne({
609
+ where: {
610
+ triggerKey,
611
+ enabled: true
612
+ }
613
+ });
614
+ if (!row) {
615
+ console.warn("[order-notifications] trigger disabled or not in DB", {
616
+ triggerKey
617
+ });
618
+ }
619
+ return Boolean(row);
620
+ } catch (err) {
621
+ console.error("[order-notifications] failed to load trigger catalog", {
622
+ triggerKey,
623
+ err
624
+ });
625
+ return false;
626
+ }
627
+ }
628
+ chunkUSNT2KNT_cjs.__name(isTriggerCatalogEnabled, "isTriggerCatalogEnabled");
629
+ async function loadBoundTemplate(triggerKey, channel, dataSource, entityMap) {
630
+ if (!entityMap.order_notification_bindings || !entityMap.message_templates) {
631
+ console.warn("[order-notifications] order_notification_bindings or message_templates missing from entityMap");
632
+ return null;
633
+ }
634
+ const binding = await dataSource.getRepository(entityMap.order_notification_bindings).findOne({
635
+ where: {
636
+ triggerKey,
637
+ channel,
638
+ enabled: true
639
+ }
640
+ });
641
+ if (!binding) {
642
+ console.warn("[order-notifications] no order_notification_bindings row", {
643
+ triggerKey,
644
+ channel
645
+ });
646
+ return null;
647
+ }
648
+ const templateId = binding.messageTemplateId;
649
+ if (templateId == null) {
650
+ console.warn("[order-notifications] binding exists but no template selected", {
651
+ triggerKey,
652
+ channel
653
+ });
654
+ return null;
655
+ }
656
+ const template = await dataSource.getRepository(entityMap.message_templates).findOne({
657
+ where: {
658
+ id: templateId,
659
+ channel,
660
+ deleted: false,
661
+ enabled: true
662
+ }
663
+ });
664
+ if (!template) {
665
+ console.warn("[order-notifications] bound template not found or disabled", {
666
+ triggerKey,
667
+ channel,
668
+ templateId
669
+ });
670
+ return null;
671
+ }
672
+ return template;
673
+ }
674
+ chunkUSNT2KNT_cjs.__name(loadBoundTemplate, "loadBoundTemplate");
675
+ function mergeTriggerVariables(payload, rich) {
676
+ const basic = {
677
+ orderId: String(payload.orderId ?? ""),
678
+ orderNumber: payload.orderNumber,
679
+ customerName: payload.customerName,
680
+ customerPhone: payload.customerPhone ?? "",
681
+ total: String(payload.total),
682
+ orderTotal: String(payload.total),
683
+ currency: payload.currency,
684
+ vendorId: String(payload.vendorId ?? ""),
685
+ ...payload.refundAmount != null ? {
686
+ refundAmount: String(payload.refundAmount)
687
+ } : {}
688
+ };
689
+ return {
690
+ ...basic,
691
+ ...rich ?? {}
692
+ };
693
+ }
694
+ chunkUSNT2KNT_cjs.__name(mergeTriggerVariables, "mergeTriggerVariables");
695
+ async function sendEmailForTrigger(triggerKey, orderId, variables, contactEmail, dataSource, entityMap, getCms) {
696
+ const template = await loadBoundTemplate(triggerKey, "email", dataSource, entityMap);
697
+ if (!template) return;
698
+ const [branding, emailSettings] = await Promise.all([
699
+ getSettingsGroup(dataSource, entityMap, "branding"),
700
+ getSettingsGroup(dataSource, entityMap, "email")
701
+ ]);
702
+ const companyDetails = chunkBXYZDMTZ_cjs.mergeEmailLayoutCompanyDetails(branding, emailSettings);
703
+ const subject = chunkOOTLNH63_cjs.applyTemplateVars(template.subject ?? "Order update", variables);
704
+ const bodyHtml = chunkOOTLNH63_cjs.applyTemplateVars(template.body, variables);
705
+ const html = chunkTCSGFAWB_cjs.renderLayout({
706
+ bodyHtml,
707
+ companyDetails
708
+ });
709
+ const cms = await getCms();
710
+ await chunkV25RDUOU_cjs.queueEmail(cms, {
711
+ to: contactEmail,
712
+ subject,
713
+ html,
714
+ text: bodyHtml.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim()
715
+ });
716
+ console.log("[order-notifications] email queued", {
717
+ triggerKey,
718
+ orderId,
719
+ to: contactEmail
720
+ });
721
+ }
722
+ chunkUSNT2KNT_cjs.__name(sendEmailForTrigger, "sendEmailForTrigger");
723
+ async function sendWhatsAppForTrigger(triggerKey, orderId, phone, variables, dataSource, entityMap, getCms) {
724
+ const template = await loadBoundTemplate(triggerKey, "whatsapp", dataSource, entityMap);
725
+ if (!template) return;
726
+ const paramOrder = template.providerMeta?.paramOrder ?? [];
727
+ if (paramOrder.length === 0) {
728
+ console.warn("[order-notifications] WhatsApp template has no paramOrder set \u2014 variables may send blank/wrong", {
729
+ triggerKey
730
+ });
731
+ }
732
+ const cms = await getCms();
733
+ const metaTemplateName = resolveMetaWhatsAppTemplateName({
734
+ externalTemplateRef: template.externalTemplateRef,
735
+ name: template.name
736
+ });
737
+ if (!metaTemplateName) {
738
+ console.warn("[order-notifications] bound WhatsApp template has no Meta template name", {
739
+ triggerKey,
740
+ templateId: template.id
741
+ });
742
+ return;
743
+ }
744
+ const sent = await queueWhatsApp(cms, {
745
+ to: phone.trim(),
746
+ templateKey: triggerKey,
747
+ externalTemplateRef: metaTemplateName,
748
+ templateLanguage: template.providerMeta?.language ?? void 0,
749
+ body: template.body,
750
+ variables,
751
+ templateParamOrder: paramOrder
752
+ });
753
+ if (!sent) {
754
+ console.warn("[order-notifications] WhatsApp send failed", {
755
+ triggerKey,
756
+ orderId,
757
+ hint: "Register whatsappPlugin() + queuePlugin() in getCms(), or set WHATSAPP_ACCESS_TOKEN and WHATSAPP_PHONE_NUMBER_ID"
758
+ });
759
+ } else {
760
+ console.log("[order-notifications] WhatsApp queued/sent", {
761
+ triggerKey,
762
+ orderId,
763
+ phone: maskPhone3(phone)
764
+ });
765
+ }
766
+ }
767
+ chunkUSNT2KNT_cjs.__name(sendWhatsAppForTrigger, "sendWhatsAppForTrigger");
768
+ async function handleTrigger(triggerKey, payload, dataSource, entityMap, getCms) {
769
+ console.log("[order-notifications] received", {
770
+ triggerKey,
771
+ orderId: payload.orderId,
772
+ phone: maskPhone3(payload.customerPhone)
773
+ });
774
+ const pluginEnabled = await isOrderNotificationsEnabled(dataSource, entityMap);
775
+ if (!pluginEnabled) {
776
+ console.warn("[order-notifications] skip: Plugins \u2192 Order notifications disabled");
777
+ return;
778
+ }
779
+ const triggerEnabled = await isTriggerCatalogEnabled(triggerKey, dataSource, entityMap);
780
+ if (!triggerEnabled) return;
781
+ const context = await loadOrderNotificationContexts(payload.orderId, {
782
+ dataSource,
783
+ entityMap
784
+ });
785
+ const variables = mergeTriggerVariables(payload, context?.variables);
786
+ const contactEmail = context?.contact.email?.trim() || "";
787
+ const contactPhone = context?.contact.phone?.trim() || payload.customerPhone?.trim() || "";
788
+ if (contactEmail) {
789
+ await sendEmailForTrigger(triggerKey, payload.orderId, variables, contactEmail, dataSource, entityMap, getCms);
790
+ } else {
791
+ console.warn("[order-notifications] skip email: no customer email", {
792
+ triggerKey,
793
+ orderId: payload.orderId
794
+ });
795
+ }
796
+ if (contactPhone) {
797
+ await sendWhatsAppForTrigger(triggerKey, payload.orderId, contactPhone, variables, dataSource, entityMap, getCms);
798
+ } else {
799
+ console.warn("[order-notifications] skip WhatsApp: no customer phone", {
800
+ triggerKey,
801
+ orderId: payload.orderId
802
+ });
803
+ }
804
+ }
805
+ chunkUSNT2KNT_cjs.__name(handleTrigger, "handleTrigger");
806
+ function initWhatsappTriggerDispatcher(deps) {
807
+ if (initialized) return;
808
+ initialized = true;
809
+ const { dataSource, entityMap, getCms } = deps;
810
+ chunkUCKN4BBY_cjs.notificationTriggerEmitter.onAnyTrigger((triggerKey, payload) => {
811
+ void handleTrigger(triggerKey, payload, dataSource, entityMap, getCms).catch((err) => {
812
+ console.error("[order-notifications]", triggerKey, err);
813
+ });
814
+ });
815
+ console.log("[order-notifications] dispatcher initialized (email + WhatsApp, unified via order_notification_bindings)");
816
+ }
817
+ chunkUSNT2KNT_cjs.__name(initWhatsappTriggerDispatcher, "initWhatsappTriggerDispatcher");
818
+ async function resendOrderNotification(triggerKey, orderId, deps) {
819
+ if (!deps.entityMap.orders) return {
820
+ ok: false,
821
+ error: "orders entity missing"
822
+ };
823
+ const pluginEnabled = await isOrderNotificationsEnabled(deps.dataSource, deps.entityMap);
824
+ if (!pluginEnabled) return {
825
+ ok: false,
826
+ error: "Order notifications plugin is disabled"
827
+ };
828
+ const triggerEnabled = await isTriggerCatalogEnabled(triggerKey, deps.dataSource, deps.entityMap);
829
+ if (!triggerEnabled) {
830
+ return {
831
+ ok: false,
832
+ error: `Trigger "${triggerKey}" is disabled or migration 1781230000000 not applied`
833
+ };
834
+ }
835
+ const order = await deps.dataSource.getRepository(deps.entityMap.orders).findOne({
836
+ where: {
837
+ id: orderId,
838
+ deleted: false
839
+ },
840
+ relations: [
841
+ "contact"
842
+ ]
843
+ });
844
+ if (!order) return {
845
+ ok: false,
846
+ error: "Order not found"
847
+ };
848
+ const o = order;
849
+ await handleTrigger(triggerKey, {
850
+ orderId,
851
+ orderNumber: String(o.orderNumber ?? ""),
852
+ customerName: o.contact?.name ?? "",
853
+ customerPhone: o.contact?.phone ?? null,
854
+ total: o.total ?? 0,
855
+ currency: String(o.currency ?? "INR"),
856
+ vendorId: o.vendorId ?? null
857
+ }, deps.dataSource, deps.entityMap, deps.getCms);
858
+ return {
859
+ ok: true
860
+ };
861
+ }
862
+ chunkUSNT2KNT_cjs.__name(resendOrderNotification, "resendOrderNotification");
863
+
864
+ exports.WhatsAppService = WhatsAppService;
865
+ exports.buildEventOrderTemplateVariables = buildEventOrderTemplateVariables;
866
+ exports.initWhatsappTriggerDispatcher = initWhatsappTriggerDispatcher;
867
+ exports.isWhatsAppPluginEnabled = isWhatsAppPluginEnabled;
868
+ exports.mergeWhatsAppConfigLayers = mergeWhatsAppConfigLayers;
869
+ exports.queueWhatsApp = queueWhatsApp;
870
+ exports.registerWhatsAppQueueProcessor = registerWhatsAppQueueProcessor;
871
+ exports.resendOrderNotification = resendOrderNotification;
872
+ exports.whatsAppConfigured = whatsAppConfigured;