@infuro/cms-core 1.0.46 → 1.0.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -16
- package/dist/admin.cjs +4568 -2716
- package/dist/admin.d.cts +18 -4
- package/dist/admin.d.ts +18 -4
- package/dist/admin.js +3475 -1625
- package/dist/api.cjs +38 -39
- package/dist/api.d.cts +9 -3
- package/dist/api.d.ts +9 -3
- package/dist/api.js +6 -7
- package/dist/auth.cjs +84 -51
- package/dist/auth.d.cts +105 -4
- package/dist/auth.d.ts +105 -4
- package/dist/auth.js +4 -3
- package/dist/chunk-2IAXTKQN.cjs +19 -0
- package/dist/chunk-3EOM4V2M.cjs +452 -0
- package/dist/{chunk-X6UQFV4X.cjs → chunk-77NUXO6A.cjs} +14 -12
- package/dist/{chunk-KFFB6EMZ.js → chunk-AYERBA7I.js} +3 -7
- package/dist/chunk-EIZKW2PF.js +13 -0
- package/dist/{chunk-CRYKVJTO.js → chunk-IPDHT2UV.js} +1420 -3
- package/dist/{chunk-UDVLFVEC.cjs → chunk-JCMOOPNX.cjs} +3820 -2907
- package/dist/{chunk-X47PBV4I.js → chunk-JE22VP6S.js} +1 -0
- package/dist/{chunk-DN65KDIA.js → chunk-JIWUVQ6B.js} +14 -13
- package/dist/{chunk-HT5DI3XW.cjs → chunk-KOTXDSQB.cjs} +1 -0
- package/dist/{chunk-UKC3HYXI.js → chunk-LT2WOPKA.js} +3733 -2813
- package/dist/chunk-RK5ETF2I.js +434 -0
- package/dist/{chunk-SPQFNIYJ.cjs → chunk-TJ2MIQUT.cjs} +3 -7
- package/dist/{chunk-TCSGFAWB.cjs → chunk-UUWAUHUW.cjs} +1451 -3
- package/dist/{chunk-57O3HZPG.js → chunk-WRKV6MHB.js} +415 -3
- package/dist/{chunk-FBKDMRQL.cjs → chunk-YV5PK4JW.cjs} +427 -7
- package/dist/cli.cjs +13 -22
- package/dist/cli.js +13 -22
- package/dist/{emit-order-notification-trigger-IVLWKKJL.js → emit-order-notification-trigger-6XX7LSC4.js} +1 -1
- package/dist/{emit-order-notification-trigger-D2N3KFHL.cjs → emit-order-notification-trigger-NASG7ZEO.cjs} +3 -3
- package/dist/{event-order-defaults-EMWBIGL3.cjs → event-order-defaults-DU7V3YND.cjs} +9 -9
- package/dist/{event-order-defaults-SDRNAZHC.js → event-order-defaults-H4RT7NMR.js} +1 -1
- package/dist/hooks.cjs +43 -0
- package/dist/hooks.d.cts +10 -1
- package/dist/hooks.d.ts +10 -1
- package/dist/hooks.js +39 -1
- package/dist/index.cjs +479 -339
- package/dist/index.d.cts +181 -20
- package/dist/index.d.ts +181 -20
- package/dist/index.js +115 -13
- package/dist/migrations/1782200000000-VendorTeamProfiles.ts +233 -0
- package/dist/migrations/1782300000000-AddTypeToOrderNotificationBindings.ts +75 -0
- package/dist/migrations/1782400000000-CreateUserDeviceTokens.ts +36 -0
- package/dist/migrations/1782500000000-AddPushToOrderNotificationBindingsChannelEnum.ts +15 -0
- package/dist/{order-notification-dispatcher-ZJZB2HUN.js → order-notification-dispatcher-3TA4ETYJ.js} +1 -2
- package/dist/{order-notification-dispatcher-SBQAMM5V.cjs → order-notification-dispatcher-ZEAZ5SHV.cjs} +7 -4
- package/dist/{rbac-debug-db-2SGNLANF.js → rbac-debug-db-6EMIVMGF.js} +1 -1
- package/dist/{rbac-debug-db-4Z6A425H.cjs → rbac-debug-db-6PWTLBEL.cjs} +2 -2
- package/dist/theme.cjs +6 -1
- package/dist/theme.d.cts +4 -1
- package/dist/theme.d.ts +4 -1
- package/dist/theme.js +3 -1
- package/package.json +1 -1
- package/src/admin/admin.css +7 -0
- package/dist/chunk-7PMHZRF3.cjs +0 -872
- package/dist/chunk-CQHXW4TR.js +0 -107
- package/dist/chunk-GUSHM5HN.js +0 -862
- package/dist/chunk-NBY4NVTY.cjs +0 -182
- package/dist/chunk-SF2XKMCI.js +0 -171
- package/dist/chunk-XMRMZ6NQ.cjs +0 -109
- package/dist/send-order-placed-emails-B5ZVJT7T.cjs +0 -15
- package/dist/send-order-placed-emails-WPI37KRZ.js +0 -6
|
@@ -1,16 +1,655 @@
|
|
|
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');
|
|
8
10
|
var nodemailer = require('nodemailer');
|
|
11
|
+
var crypto = require('crypto');
|
|
9
12
|
|
|
10
13
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
11
14
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
15
|
|
|
16
|
+
function _interopNamespace(e) {
|
|
17
|
+
if (e && e.__esModule) return e;
|
|
18
|
+
var n = Object.create(null);
|
|
19
|
+
if (e) {
|
|
20
|
+
Object.keys(e).forEach(function (k) {
|
|
21
|
+
if (k !== 'default') {
|
|
22
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
23
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
get: function () { return e[k]; }
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
n.default = e;
|
|
31
|
+
return Object.freeze(n);
|
|
32
|
+
}
|
|
33
|
+
|
|
13
34
|
var nodemailer__default = /*#__PURE__*/_interopDefault(nodemailer);
|
|
35
|
+
var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
|
|
36
|
+
|
|
37
|
+
// src/lib/event-order-template-context.ts
|
|
38
|
+
function resolvePublicSiteUrl() {
|
|
39
|
+
if (typeof process !== "undefined") {
|
|
40
|
+
const u = (process.env.CMS_PUBLIC_URL || process.env.NEXT_PUBLIC_SITE_URL || "").trim();
|
|
41
|
+
if (u) return u.replace(/\/+$/, "");
|
|
42
|
+
const v = (process.env.VERCEL_URL || "").trim();
|
|
43
|
+
if (v) return v.startsWith("http") ? v.replace(/\/+$/, "") : `https://${v.replace(/\/+$/, "")}`;
|
|
44
|
+
}
|
|
45
|
+
return "";
|
|
46
|
+
}
|
|
47
|
+
chunkUSNT2KNT_cjs.__name(resolvePublicSiteUrl, "resolvePublicSiteUrl");
|
|
48
|
+
function buildOrderTrackUrl(qrToken, siteUrl, orderNumber) {
|
|
49
|
+
const token = qrToken?.trim() || orderNumber?.trim();
|
|
50
|
+
if (!token) return "";
|
|
51
|
+
const base = (siteUrl ?? resolvePublicSiteUrl()).replace(/\/+$/, "");
|
|
52
|
+
if (!base) return `/track/${token}`;
|
|
53
|
+
return `${base}/track/${token}`;
|
|
54
|
+
}
|
|
55
|
+
chunkUSNT2KNT_cjs.__name(buildOrderTrackUrl, "buildOrderTrackUrl");
|
|
56
|
+
function formatOrderDetailsText(input) {
|
|
57
|
+
const { orderNumber, total, currency, lines } = input;
|
|
58
|
+
const header = `Order: ${orderNumber}
|
|
59
|
+
Total: ${total} ${currency}`;
|
|
60
|
+
if (!lines.length) return header;
|
|
61
|
+
const items = lines.map((line) => `- ${line.name} \xD7 ${line.quantity} (${line.total} ${line.currency})`).join("\n");
|
|
62
|
+
return `${header}
|
|
63
|
+
|
|
64
|
+
Items:
|
|
65
|
+
${items}`;
|
|
66
|
+
}
|
|
67
|
+
chunkUSNT2KNT_cjs.__name(formatOrderDetailsText, "formatOrderDetailsText");
|
|
68
|
+
function formatOrderDetailsHtml(input) {
|
|
69
|
+
const text = formatOrderDetailsText(input);
|
|
70
|
+
const escaped = text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
71
|
+
return `<pre style="font-family:inherit;white-space:pre-wrap;margin:0">${escaped}</pre>`;
|
|
72
|
+
}
|
|
73
|
+
chunkUSNT2KNT_cjs.__name(formatOrderDetailsHtml, "formatOrderDetailsHtml");
|
|
74
|
+
function buildEventOrderTemplateVariables(input) {
|
|
75
|
+
const { event, order, contact, productNames, vendorName = "", orderLines = [], siteUrl } = input;
|
|
76
|
+
const trackUrl = buildOrderTrackUrl(order.qrToken, siteUrl, order.orderNumber);
|
|
77
|
+
const baseSite = (siteUrl ?? resolvePublicSiteUrl()).replace(/\/+$/, "");
|
|
78
|
+
const invoiceNumber = `INV-${order.orderNumber ?? ""}`;
|
|
79
|
+
const invoiceUrl = baseSite ? `${baseSite}/api/orders/${order.orderNumber ?? ""}/invoice` : `/api/orders/${order.orderNumber ?? ""}/invoice`;
|
|
80
|
+
const orderDetailsPayload = {
|
|
81
|
+
orderNumber: String(order.orderNumber ?? ""),
|
|
82
|
+
total: String(order.total ?? ""),
|
|
83
|
+
currency: String(order.currency ?? "INR"),
|
|
84
|
+
lines: orderLines
|
|
85
|
+
};
|
|
86
|
+
return {
|
|
87
|
+
eventName: event.name ?? productNames ?? "",
|
|
88
|
+
vendorName: vendorName.trim(),
|
|
89
|
+
eventSlug: event.slug ?? "",
|
|
90
|
+
venue: event.venue ?? "",
|
|
91
|
+
startDate: formatEventDate(event.startDate),
|
|
92
|
+
orderNumber: String(order.orderNumber ?? ""),
|
|
93
|
+
orderTotal: String(order.total ?? ""),
|
|
94
|
+
currency: String(order.currency ?? "INR"),
|
|
95
|
+
customerName: contact.name?.trim() || contact.email?.split("@")[0] || "Guest",
|
|
96
|
+
customerEmail: contact.email?.trim() || "",
|
|
97
|
+
customerPhone: contact.phone?.trim() || "",
|
|
98
|
+
productNames,
|
|
99
|
+
trackUrl,
|
|
100
|
+
invoiceNumber,
|
|
101
|
+
invoiceUrl,
|
|
102
|
+
orderDetails: formatOrderDetailsText(orderDetailsPayload),
|
|
103
|
+
orderDetailsHtml: formatOrderDetailsHtml(orderDetailsPayload)
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
chunkUSNT2KNT_cjs.__name(buildEventOrderTemplateVariables, "buildEventOrderTemplateVariables");
|
|
107
|
+
function formatEventDate(d) {
|
|
108
|
+
if (!d) return "";
|
|
109
|
+
try {
|
|
110
|
+
return new Date(d).toLocaleString(void 0, {
|
|
111
|
+
dateStyle: "medium",
|
|
112
|
+
timeStyle: "short"
|
|
113
|
+
});
|
|
114
|
+
} catch {
|
|
115
|
+
return String(d);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
chunkUSNT2KNT_cjs.__name(formatEventDate, "formatEventDate");
|
|
119
|
+
|
|
120
|
+
// src/lib/order-notification-context.ts
|
|
121
|
+
async function loadOrderLines(dataSource, entityMap, orderId, currency) {
|
|
122
|
+
if (!entityMap.order_items) return [];
|
|
123
|
+
const itemRows = await dataSource.getRepository(entityMap.order_items).find({
|
|
124
|
+
where: {
|
|
125
|
+
orderId
|
|
126
|
+
},
|
|
127
|
+
relations: [
|
|
128
|
+
"product"
|
|
129
|
+
],
|
|
130
|
+
order: {
|
|
131
|
+
id: "ASC"
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
return itemRows.map((item) => {
|
|
135
|
+
const row = item;
|
|
136
|
+
const name = row.product?.name || row.product?.title || `Product #${row.productId}`;
|
|
137
|
+
return {
|
|
138
|
+
name: String(name),
|
|
139
|
+
quantity: Number(row.quantity) || 1,
|
|
140
|
+
total: row.total,
|
|
141
|
+
currency
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
chunkUSNT2KNT_cjs.__name(loadOrderLines, "loadOrderLines");
|
|
146
|
+
async function loadPrimaryEvent(dataSource, entityMap, itemRows) {
|
|
147
|
+
if (!entityMap.event_products || !entityMap.events) {
|
|
148
|
+
return {
|
|
149
|
+
productNames: []
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const epRepo = dataSource.getRepository(entityMap.event_products);
|
|
153
|
+
const eventRepo = dataSource.getRepository(entityMap.events);
|
|
154
|
+
const eventMap = /* @__PURE__ */ new Map();
|
|
155
|
+
for (const item of itemRows) {
|
|
156
|
+
const productId = item.productId;
|
|
157
|
+
const productName = item.product?.name || item.product?.title || `Product #${productId}`;
|
|
158
|
+
const eps = await epRepo.find({
|
|
159
|
+
where: {
|
|
160
|
+
productId,
|
|
161
|
+
isActive: true
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
for (const ep of eps) {
|
|
165
|
+
const eventId = ep.eventId;
|
|
166
|
+
const existing = eventMap.get(eventId);
|
|
167
|
+
if (existing) {
|
|
168
|
+
if (!existing.productNames.includes(String(productName))) {
|
|
169
|
+
existing.productNames.push(String(productName));
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
const event = await eventRepo.findOne({
|
|
173
|
+
where: {
|
|
174
|
+
id: eventId,
|
|
175
|
+
deleted: false
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
if (event) {
|
|
179
|
+
eventMap.set(eventId, {
|
|
180
|
+
event,
|
|
181
|
+
productNames: [
|
|
182
|
+
String(productName)
|
|
183
|
+
]
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const first = eventMap.values().next().value;
|
|
190
|
+
if (!first) return {
|
|
191
|
+
productNames: []
|
|
192
|
+
};
|
|
193
|
+
return {
|
|
194
|
+
event: first.event,
|
|
195
|
+
productNames: first.productNames
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
chunkUSNT2KNT_cjs.__name(loadPrimaryEvent, "loadPrimaryEvent");
|
|
199
|
+
async function isFeatureGroupEnabled(dataSource, entityMap, group) {
|
|
200
|
+
if (!entityMap.configs) return true;
|
|
201
|
+
try {
|
|
202
|
+
const rows = await dataSource.getRepository(entityMap.configs).find({
|
|
203
|
+
where: {
|
|
204
|
+
settings: group,
|
|
205
|
+
deleted: false
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
const settings = Object.fromEntries(rows.map((r) => [
|
|
209
|
+
r.key,
|
|
210
|
+
r.value
|
|
211
|
+
]));
|
|
212
|
+
return settings.enabled !== "false" && settings.enabled !== "0";
|
|
213
|
+
} catch {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
chunkUSNT2KNT_cjs.__name(isFeatureGroupEnabled, "isFeatureGroupEnabled");
|
|
218
|
+
async function loadOrderNotificationContexts(orderId, deps) {
|
|
219
|
+
if (!deps.entityMap.orders) return null;
|
|
220
|
+
const order = await deps.dataSource.getRepository(deps.entityMap.orders).findOne({
|
|
221
|
+
where: {
|
|
222
|
+
id: orderId,
|
|
223
|
+
deleted: false
|
|
224
|
+
},
|
|
225
|
+
relations: [
|
|
226
|
+
"contact"
|
|
227
|
+
]
|
|
228
|
+
});
|
|
229
|
+
if (!order) return null;
|
|
230
|
+
const o = order;
|
|
231
|
+
const meta = o.metadata && typeof o.metadata === "object" && !Array.isArray(o.metadata) ? o.metadata : {};
|
|
232
|
+
const metaBuyerEmail = typeof meta.buyerEmail === "string" && meta.buyerEmail.includes("@") ? meta.buyerEmail.trim() : null;
|
|
233
|
+
const metaCheckoutEmail = typeof meta.checkoutEmail === "string" && meta.checkoutEmail.includes("@") ? meta.checkoutEmail.trim() : null;
|
|
234
|
+
const metaBuyerName = typeof meta.buyerName === "string" && meta.buyerName.trim() ? meta.buyerName.trim() : null;
|
|
235
|
+
const rawContact = o.contact ?? {};
|
|
236
|
+
const contact = {
|
|
237
|
+
name: metaBuyerName || rawContact.name,
|
|
238
|
+
email: metaBuyerEmail || metaCheckoutEmail || rawContact.email,
|
|
239
|
+
phone: rawContact.phone
|
|
240
|
+
};
|
|
241
|
+
const currency = String(o.currency ?? "INR");
|
|
242
|
+
const siteUrl = resolvePublicSiteUrl();
|
|
243
|
+
const itemRows = await deps.dataSource.getRepository(deps.entityMap.order_items).find({
|
|
244
|
+
where: {
|
|
245
|
+
orderId
|
|
246
|
+
},
|
|
247
|
+
relations: [
|
|
248
|
+
"product"
|
|
249
|
+
],
|
|
250
|
+
order: {
|
|
251
|
+
id: "ASC"
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
const orderLines = await loadOrderLines(deps.dataSource, deps.entityMap, orderId, currency);
|
|
255
|
+
const { event, productNames: eventProductNames } = await loadPrimaryEvent(deps.dataSource, deps.entityMap, itemRows);
|
|
256
|
+
const allProductNames = orderLines.map((line) => line.name);
|
|
257
|
+
const productNames = eventProductNames.length > 0 ? eventProductNames.join(", ") : allProductNames.join(", ");
|
|
258
|
+
const [multiVendorEnabled, eventsEnabled] = await Promise.all([
|
|
259
|
+
isFeatureGroupEnabled(deps.dataSource, deps.entityMap, "multi_vendor"),
|
|
260
|
+
isFeatureGroupEnabled(deps.dataSource, deps.entityMap, "events")
|
|
261
|
+
]);
|
|
262
|
+
let vendorName = "";
|
|
263
|
+
if (multiVendorEnabled) {
|
|
264
|
+
const vendorId = o.vendorId ?? event?.vendorId;
|
|
265
|
+
if (vendorId && deps.entityMap.vendors) {
|
|
266
|
+
try {
|
|
267
|
+
const vRow = await deps.dataSource.getRepository(deps.entityMap.vendors).findOne({
|
|
268
|
+
where: {
|
|
269
|
+
id: vendorId
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
if (vRow) {
|
|
273
|
+
const v = vRow;
|
|
274
|
+
vendorName = String(v.storeName?.trim() || v.name?.trim() || v.companyName?.trim() || "");
|
|
275
|
+
}
|
|
276
|
+
} catch {
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const effectiveEvent = eventsEnabled ? event : void 0;
|
|
281
|
+
const variables = effectiveEvent ? buildEventOrderTemplateVariables({
|
|
282
|
+
event: effectiveEvent,
|
|
283
|
+
order: {
|
|
284
|
+
orderNumber: o.orderNumber,
|
|
285
|
+
total: o.total,
|
|
286
|
+
currency,
|
|
287
|
+
qrToken: o.qrToken
|
|
288
|
+
},
|
|
289
|
+
contact,
|
|
290
|
+
productNames,
|
|
291
|
+
vendorName,
|
|
292
|
+
orderLines,
|
|
293
|
+
siteUrl
|
|
294
|
+
}) : buildEventOrderTemplateVariables({
|
|
295
|
+
event: {
|
|
296
|
+
name: eventsEnabled ? productNames || "Your order" : "",
|
|
297
|
+
slug: "",
|
|
298
|
+
venue: "",
|
|
299
|
+
startDate: null
|
|
300
|
+
},
|
|
301
|
+
order: {
|
|
302
|
+
orderNumber: o.orderNumber,
|
|
303
|
+
total: o.total,
|
|
304
|
+
currency,
|
|
305
|
+
qrToken: o.qrToken
|
|
306
|
+
},
|
|
307
|
+
contact,
|
|
308
|
+
productNames,
|
|
309
|
+
vendorName,
|
|
310
|
+
orderLines,
|
|
311
|
+
siteUrl
|
|
312
|
+
});
|
|
313
|
+
return {
|
|
314
|
+
order: {
|
|
315
|
+
id: o.id,
|
|
316
|
+
orderNumber: String(o.orderNumber ?? ""),
|
|
317
|
+
total: o.total,
|
|
318
|
+
currency,
|
|
319
|
+
qrToken: o.qrToken
|
|
320
|
+
},
|
|
321
|
+
contact,
|
|
322
|
+
orderLines,
|
|
323
|
+
productNames,
|
|
324
|
+
event,
|
|
325
|
+
variables
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
chunkUSNT2KNT_cjs.__name(loadOrderNotificationContexts, "loadOrderNotificationContexts");
|
|
329
|
+
|
|
330
|
+
// src/plugins/whatsapp/whatsapp-queue.ts
|
|
331
|
+
var WHATSAPP_QUEUE_NAME = "whatsapp";
|
|
332
|
+
function maskPhone(raw) {
|
|
333
|
+
const digits = raw.replace(/\D/g, "");
|
|
334
|
+
if (digits.length <= 4) return "****";
|
|
335
|
+
return `${"*".repeat(Math.max(0, digits.length - 4))}${digits.slice(-4)}`;
|
|
336
|
+
}
|
|
337
|
+
chunkUSNT2KNT_cjs.__name(maskPhone, "maskPhone");
|
|
338
|
+
function registerWhatsAppQueueProcessor(cms) {
|
|
339
|
+
const queue = cms.getPlugin("queue");
|
|
340
|
+
const whatsapp = cms.getPlugin("whatsapp");
|
|
341
|
+
if (!queue) {
|
|
342
|
+
console.warn("[whatsapp] queue processor not registered: queue plugin missing");
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (!whatsapp || typeof whatsapp.send !== "function") {
|
|
346
|
+
console.warn("[whatsapp] queue processor not registered: whatsapp plugin missing or not configured");
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
queue.registerProcessor(WHATSAPP_QUEUE_NAME, async (data) => {
|
|
350
|
+
const payload = data;
|
|
351
|
+
if (!payload.to) {
|
|
352
|
+
console.warn("[whatsapp] queue job skipped: missing recipient");
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
console.log("[whatsapp] queue job processing", {
|
|
356
|
+
to: maskPhone(payload.to),
|
|
357
|
+
templateKey: payload.templateKey ?? null,
|
|
358
|
+
externalTemplateRef: payload.externalTemplateRef ?? null
|
|
359
|
+
});
|
|
360
|
+
try {
|
|
361
|
+
const ok = await whatsapp.send(payload);
|
|
362
|
+
if (ok) {
|
|
363
|
+
console.log("[whatsapp] queue job completed", {
|
|
364
|
+
to: maskPhone(payload.to),
|
|
365
|
+
sent: true
|
|
366
|
+
});
|
|
367
|
+
} else {
|
|
368
|
+
console.warn("[whatsapp] queue job completed without delivery", {
|
|
369
|
+
to: maskPhone(payload.to),
|
|
370
|
+
sent: false
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
} catch (err) {
|
|
374
|
+
console.error("[whatsapp] queue job failed", {
|
|
375
|
+
to: maskPhone(payload.to),
|
|
376
|
+
error: err instanceof Error ? err.message : String(err)
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
console.log("[whatsapp] queue processor registered", {
|
|
381
|
+
queue: WHATSAPP_QUEUE_NAME
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
chunkUSNT2KNT_cjs.__name(registerWhatsAppQueueProcessor, "registerWhatsAppQueueProcessor");
|
|
385
|
+
async function queueWhatsApp(cms, payload) {
|
|
386
|
+
const queue = cms.getPlugin("queue");
|
|
387
|
+
if (queue) {
|
|
388
|
+
console.log("[whatsapp] enqueue", {
|
|
389
|
+
to: maskPhone(payload.to),
|
|
390
|
+
templateKey: payload.templateKey ?? null,
|
|
391
|
+
externalTemplateRef: payload.externalTemplateRef ?? null
|
|
392
|
+
});
|
|
393
|
+
await queue.add(WHATSAPP_QUEUE_NAME, payload);
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
const whatsapp = cms.getPlugin("whatsapp");
|
|
397
|
+
if (whatsapp && typeof whatsapp.send === "function") {
|
|
398
|
+
console.log("[whatsapp] no queue plugin \u2014 sending immediately", {
|
|
399
|
+
to: maskPhone(payload.to)
|
|
400
|
+
});
|
|
401
|
+
return whatsapp.send(payload);
|
|
402
|
+
}
|
|
403
|
+
console.warn("[whatsapp] send skipped: neither queue nor whatsapp plugin available", {
|
|
404
|
+
to: maskPhone(payload.to)
|
|
405
|
+
});
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
chunkUSNT2KNT_cjs.__name(queueWhatsApp, "queueWhatsApp");
|
|
409
|
+
|
|
410
|
+
// src/plugins/whatsapp/whatsapp-service.ts
|
|
411
|
+
function norm(v) {
|
|
412
|
+
const t = v?.trim();
|
|
413
|
+
return t || void 0;
|
|
414
|
+
}
|
|
415
|
+
chunkUSNT2KNT_cjs.__name(norm, "norm");
|
|
416
|
+
function pick(plugin, db, env, ...keys) {
|
|
417
|
+
for (const key of keys) {
|
|
418
|
+
const pk = key;
|
|
419
|
+
if (pk in plugin && plugin[pk] !== void 0 && String(plugin[pk]).trim() !== "") {
|
|
420
|
+
const v = norm(String(plugin[pk]));
|
|
421
|
+
if (v) return v;
|
|
422
|
+
}
|
|
423
|
+
if (norm(db[key])) return norm(db[key]);
|
|
424
|
+
if (norm(env[key])) return norm(env[key]);
|
|
425
|
+
}
|
|
426
|
+
return void 0;
|
|
427
|
+
}
|
|
428
|
+
chunkUSNT2KNT_cjs.__name(pick, "pick");
|
|
429
|
+
function mergeWhatsAppConfigLayers(env, db, plugin) {
|
|
430
|
+
return {
|
|
431
|
+
accessToken: pick(plugin, db, env, "accessToken", "WHATSAPP_ACCESS_TOKEN"),
|
|
432
|
+
phoneNumberId: pick(plugin, db, env, "phoneNumberId", "WHATSAPP_PHONE_NUMBER_ID"),
|
|
433
|
+
apiVersion: pick(plugin, db, env, "apiVersion", "WHATSAPP_API_VERSION") ?? "v21.0"
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
chunkUSNT2KNT_cjs.__name(mergeWhatsAppConfigLayers, "mergeWhatsAppConfigLayers");
|
|
437
|
+
function isWhatsAppPluginEnabled(db) {
|
|
438
|
+
const v = (db.enabled ?? "true").toLowerCase();
|
|
439
|
+
return v !== "false" && v !== "0";
|
|
440
|
+
}
|
|
441
|
+
chunkUSNT2KNT_cjs.__name(isWhatsAppPluginEnabled, "isWhatsAppPluginEnabled");
|
|
442
|
+
function whatsAppConfigured(config) {
|
|
443
|
+
return Boolean(config.accessToken?.trim() && config.phoneNumberId?.trim());
|
|
444
|
+
}
|
|
445
|
+
chunkUSNT2KNT_cjs.__name(whatsAppConfigured, "whatsAppConfigured");
|
|
446
|
+
function normalizeWhatsAppRecipient(to) {
|
|
447
|
+
const digits = to.replace(/\D/g, "");
|
|
448
|
+
return digits;
|
|
449
|
+
}
|
|
450
|
+
chunkUSNT2KNT_cjs.__name(normalizeWhatsAppRecipient, "normalizeWhatsAppRecipient");
|
|
451
|
+
function resolveMetaWhatsAppTemplateName(opts) {
|
|
452
|
+
const ref = opts.externalTemplateRef?.trim();
|
|
453
|
+
const rowName = opts.name?.trim();
|
|
454
|
+
if (ref && !/^\d+$/.test(ref)) return ref;
|
|
455
|
+
if (rowName) return rowName;
|
|
456
|
+
return ref || void 0;
|
|
457
|
+
}
|
|
458
|
+
chunkUSNT2KNT_cjs.__name(resolveMetaWhatsAppTemplateName, "resolveMetaWhatsAppTemplateName");
|
|
459
|
+
function maskPhone2(digits) {
|
|
460
|
+
if (digits.length <= 4) return "****";
|
|
461
|
+
return `${"*".repeat(Math.max(0, digits.length - 4))}${digits.slice(-4)}`;
|
|
462
|
+
}
|
|
463
|
+
chunkUSNT2KNT_cjs.__name(maskPhone2, "maskPhone");
|
|
464
|
+
function logWhatsApp(level, message, extra) {
|
|
465
|
+
const line = extra ? `${message} ${JSON.stringify(extra)}` : message;
|
|
466
|
+
if (level === "info") console.log(`[whatsapp] ${line}`);
|
|
467
|
+
else if (level === "warn") console.warn(`[whatsapp] ${line}`);
|
|
468
|
+
else console.error(`[whatsapp] ${line}`);
|
|
469
|
+
}
|
|
470
|
+
chunkUSNT2KNT_cjs.__name(logWhatsApp, "logWhatsApp");
|
|
471
|
+
var WhatsAppService = class {
|
|
472
|
+
static {
|
|
473
|
+
chunkUSNT2KNT_cjs.__name(this, "WhatsAppService");
|
|
474
|
+
}
|
|
475
|
+
getWhatsAppSettings;
|
|
476
|
+
config;
|
|
477
|
+
getMessageTemplateRow;
|
|
478
|
+
constructor(env, plugin = {}, getWhatsAppSettings, getMessageTemplateRow) {
|
|
479
|
+
this.getWhatsAppSettings = getWhatsAppSettings;
|
|
480
|
+
this.getMessageTemplateRow = getMessageTemplateRow;
|
|
481
|
+
this.config = mergeWhatsAppConfigLayers(env, {}, plugin);
|
|
482
|
+
}
|
|
483
|
+
async mergedConfig() {
|
|
484
|
+
let db = {};
|
|
485
|
+
try {
|
|
486
|
+
db = await this.getWhatsAppSettings?.() ?? {};
|
|
487
|
+
} catch {
|
|
488
|
+
}
|
|
489
|
+
return mergeWhatsAppConfigLayers((typeof process !== "undefined" ? process.env : {}) ?? {}, db, this.config);
|
|
490
|
+
}
|
|
491
|
+
async send(opts) {
|
|
492
|
+
const cfg = await this.mergedConfig();
|
|
493
|
+
if (!whatsAppConfigured(cfg)) {
|
|
494
|
+
logWhatsApp("warn", "send skipped: not configured (Plugins \u2192 WhatsApp or WHATSAPP_ACCESS_TOKEN + WHATSAPP_PHONE_NUMBER_ID)");
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
const to = normalizeWhatsAppRecipient(opts.to);
|
|
498
|
+
if (!to) {
|
|
499
|
+
logWhatsApp("warn", "send skipped: invalid recipient", {
|
|
500
|
+
rawTo: opts.to
|
|
501
|
+
});
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
504
|
+
logWhatsApp("info", "send started", {
|
|
505
|
+
to: maskPhone2(to),
|
|
506
|
+
templateKey: opts.templateKey ?? null,
|
|
507
|
+
externalTemplateRef: opts.externalTemplateRef ?? null,
|
|
508
|
+
phoneNumberId: cfg.phoneNumberId
|
|
509
|
+
});
|
|
510
|
+
let templateName = resolveMetaWhatsAppTemplateName({
|
|
511
|
+
externalTemplateRef: opts.externalTemplateRef
|
|
512
|
+
});
|
|
513
|
+
let templateLanguage = opts.templateLanguage?.trim();
|
|
514
|
+
let body = opts.body?.trim() ?? "";
|
|
515
|
+
const variables = opts.variables ?? {};
|
|
516
|
+
if (opts.templateKey?.trim() && this.getMessageTemplateRow) {
|
|
517
|
+
const { resolveEventOrderTemplate, EVENT_ORDER_TEMPLATE_KEY } = await import('./event-order-defaults-DU7V3YND.cjs');
|
|
518
|
+
if (opts.templateKey.trim() === EVENT_ORDER_TEMPLATE_KEY) {
|
|
519
|
+
const resolved = await resolveEventOrderTemplate("whatsapp", async (ch, key) => {
|
|
520
|
+
const row = await this.getMessageTemplateRow(ch, key);
|
|
521
|
+
if (!row) return null;
|
|
522
|
+
return {
|
|
523
|
+
subject: null,
|
|
524
|
+
body: row.body,
|
|
525
|
+
externalTemplateRef: row.externalTemplateRef,
|
|
526
|
+
enabled: row.enabled
|
|
527
|
+
};
|
|
528
|
+
});
|
|
529
|
+
if (resolved && !resolved.enabled) {
|
|
530
|
+
logWhatsApp("warn", "send skipped: template disabled in message_templates", {
|
|
531
|
+
templateKey: opts.templateKey
|
|
532
|
+
});
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
if (resolved) {
|
|
536
|
+
body = resolved.body;
|
|
537
|
+
if (resolved.externalTemplateRef) templateName = resolved.externalTemplateRef;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
const { applyTemplateVars: applyTemplateVars2 } = await import('./interpolate-WAKPN56R.cjs');
|
|
542
|
+
if (body) body = applyTemplateVars2(body, variables);
|
|
543
|
+
if (templateName) {
|
|
544
|
+
const paramValues = opts.templateParamOrder?.length ? opts.templateParamOrder.map((key) => String(variables[key] ?? "").slice(0, 1024)) : Object.keys(variables).length ? Object.values(variables) : body ? [
|
|
545
|
+
body
|
|
546
|
+
] : [];
|
|
547
|
+
logWhatsApp("info", "sending template message", {
|
|
548
|
+
to: maskPhone2(to),
|
|
549
|
+
templateName,
|
|
550
|
+
paramCount: paramValues.length,
|
|
551
|
+
paramOrder: opts.templateParamOrder ?? null
|
|
552
|
+
});
|
|
553
|
+
return this.sendTemplateMessage(cfg, to, templateName, paramValues, templateLanguage);
|
|
554
|
+
}
|
|
555
|
+
if (!body) {
|
|
556
|
+
logWhatsApp("warn", "send skipped: no template name and empty body", {
|
|
557
|
+
to: maskPhone2(to)
|
|
558
|
+
});
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
logWhatsApp("info", "sending text message", {
|
|
562
|
+
to: maskPhone2(to),
|
|
563
|
+
bodyLength: body.length
|
|
564
|
+
});
|
|
565
|
+
return this.sendTextMessage(cfg, to, body);
|
|
566
|
+
}
|
|
567
|
+
async sendTextMessage(cfg, to, body) {
|
|
568
|
+
const url = `https://graph.facebook.com/${cfg.apiVersion}/${cfg.phoneNumberId}/messages`;
|
|
569
|
+
const res = await fetch(url, {
|
|
570
|
+
method: "POST",
|
|
571
|
+
headers: {
|
|
572
|
+
Authorization: `Bearer ${cfg.accessToken}`,
|
|
573
|
+
"Content-Type": "application/json"
|
|
574
|
+
},
|
|
575
|
+
body: JSON.stringify({
|
|
576
|
+
messaging_product: "whatsapp",
|
|
577
|
+
to,
|
|
578
|
+
type: "text",
|
|
579
|
+
text: {
|
|
580
|
+
body
|
|
581
|
+
}
|
|
582
|
+
})
|
|
583
|
+
});
|
|
584
|
+
if (!res.ok) {
|
|
585
|
+
const errText = await res.text().catch(() => "");
|
|
586
|
+
logWhatsApp("error", "text send failed", {
|
|
587
|
+
status: res.status,
|
|
588
|
+
to: maskPhone2(to),
|
|
589
|
+
error: errText
|
|
590
|
+
});
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
const data = await res.json().catch(() => ({}));
|
|
594
|
+
logWhatsApp("info", "text send succeeded", {
|
|
595
|
+
to: maskPhone2(to),
|
|
596
|
+
messageId: data.messages?.[0]?.id ?? null
|
|
597
|
+
});
|
|
598
|
+
return true;
|
|
599
|
+
}
|
|
600
|
+
async sendTemplateMessage(cfg, to, templateName, paramValues, templateLanguage) {
|
|
601
|
+
const components = paramValues.length > 0 ? [
|
|
602
|
+
{
|
|
603
|
+
type: "body",
|
|
604
|
+
parameters: paramValues.map((text) => ({
|
|
605
|
+
type: "text",
|
|
606
|
+
text: String(text).slice(0, 1024)
|
|
607
|
+
}))
|
|
608
|
+
}
|
|
609
|
+
] : void 0;
|
|
610
|
+
const url = `https://graph.facebook.com/${cfg.apiVersion}/${cfg.phoneNumberId}/messages`;
|
|
611
|
+
const payload = {
|
|
612
|
+
messaging_product: "whatsapp",
|
|
613
|
+
to,
|
|
614
|
+
type: "template",
|
|
615
|
+
template: {
|
|
616
|
+
name: templateName,
|
|
617
|
+
language: {
|
|
618
|
+
code: templateLanguage?.trim() || "en"
|
|
619
|
+
},
|
|
620
|
+
...components ? {
|
|
621
|
+
components
|
|
622
|
+
} : {}
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
const res = await fetch(url, {
|
|
626
|
+
method: "POST",
|
|
627
|
+
headers: {
|
|
628
|
+
Authorization: `Bearer ${cfg.accessToken}`,
|
|
629
|
+
"Content-Type": "application/json"
|
|
630
|
+
},
|
|
631
|
+
body: JSON.stringify(payload)
|
|
632
|
+
});
|
|
633
|
+
if (!res.ok) {
|
|
634
|
+
const errText = await res.text().catch(() => "");
|
|
635
|
+
logWhatsApp("error", "template send failed", {
|
|
636
|
+
status: res.status,
|
|
637
|
+
to: maskPhone2(to),
|
|
638
|
+
templateName,
|
|
639
|
+
paramCount: paramValues.length,
|
|
640
|
+
error: errText
|
|
641
|
+
});
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
const data = await res.json().catch(() => ({}));
|
|
645
|
+
logWhatsApp("info", "template send succeeded", {
|
|
646
|
+
to: maskPhone2(to),
|
|
647
|
+
templateName,
|
|
648
|
+
messageId: data.messages?.[0]?.id ?? null
|
|
649
|
+
});
|
|
650
|
+
return true;
|
|
651
|
+
}
|
|
652
|
+
};
|
|
14
653
|
|
|
15
654
|
// src/plugins/email/templates/layout.ts
|
|
16
655
|
function escapeHtml(s) {
|
|
@@ -574,6 +1213,19 @@ ${slugNote}
|
|
|
574
1213
|
`Owner / invitee: ${ownerName?.trim() || "\u2014"} (${ownerEmail})`,
|
|
575
1214
|
`Activation: ${activation === "invite" ? "Invitation" : "Password set"}`
|
|
576
1215
|
].filter(Boolean).join("\n");
|
|
1216
|
+
} else if (kind === "team_invite" && activation === "membership" && inviteLink) {
|
|
1217
|
+
subject = `You're invited to ${storeLabel}`;
|
|
1218
|
+
bodyHtml = `<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">${greeting}</p>
|
|
1219
|
+
<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>
|
|
1220
|
+
${primaryCtaButton(inviteLink, "Accept invitation")}
|
|
1221
|
+
<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>`;
|
|
1222
|
+
text = [
|
|
1223
|
+
ownerName?.trim() ? `Hello ${ownerName.trim()},` : "Hello,",
|
|
1224
|
+
"",
|
|
1225
|
+
`You have been invited to join ${storeLabel}.`,
|
|
1226
|
+
"Open this link to accept (no new password required):",
|
|
1227
|
+
inviteLink
|
|
1228
|
+
].join("\n");
|
|
577
1229
|
} else if (kind === "team_invite" && activation === "invite" && inviteLink) {
|
|
578
1230
|
subject = `You're invited to ${storeLabel}`;
|
|
579
1231
|
bodyHtml = `<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">${greeting}</p>
|
|
@@ -672,7 +1324,9 @@ function renderEmail(templateName, ctx, options) {
|
|
|
672
1324
|
return render12(ctx);
|
|
673
1325
|
}
|
|
674
1326
|
chunkUSNT2KNT_cjs.__name(renderEmail, "renderEmail");
|
|
675
|
-
|
|
1327
|
+
|
|
1328
|
+
// src/plugins/email/email-service.ts
|
|
1329
|
+
var require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('chunk-UUWAUHUW.cjs', document.baseURI).href)));
|
|
676
1330
|
var MailComposer = require2("nodemailer/lib/mail-composer");
|
|
677
1331
|
var SES_REGIONS_WITHOUT_SMTP = /* @__PURE__ */ new Set([
|
|
678
1332
|
"af-south-1",
|
|
@@ -1038,8 +1692,8 @@ function resolveChatEmailToolSettings(configMap, sources) {
|
|
|
1038
1692
|
chunkUSNT2KNT_cjs.__name(resolveChatEmailToolSettings, "resolveChatEmailToolSettings");
|
|
1039
1693
|
function intentByKey(intents, key) {
|
|
1040
1694
|
if (!key) return null;
|
|
1041
|
-
const
|
|
1042
|
-
return intents.find((i) => i.intent ===
|
|
1695
|
+
const norm2 = normalizeIntentKey(key);
|
|
1696
|
+
return intents.find((i) => i.intent === norm2) ?? null;
|
|
1043
1697
|
}
|
|
1044
1698
|
chunkUSNT2KNT_cjs.__name(intentByKey, "intentByKey");
|
|
1045
1699
|
function buildIntentTableForPrompt(intents) {
|
|
@@ -1424,19 +2078,813 @@ function emailPlugin(config) {
|
|
|
1424
2078
|
};
|
|
1425
2079
|
}
|
|
1426
2080
|
chunkUSNT2KNT_cjs.__name(emailPlugin, "emailPlugin");
|
|
2081
|
+
function base64UrlEncode(str) {
|
|
2082
|
+
const buf = typeof str === "string" ? Buffer.from(str, "utf8") : str;
|
|
2083
|
+
return buf.toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
2084
|
+
}
|
|
2085
|
+
chunkUSNT2KNT_cjs.__name(base64UrlEncode, "base64UrlEncode");
|
|
2086
|
+
async function getGoogleAccessToken(clientEmail, privateKey) {
|
|
2087
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2088
|
+
const header = {
|
|
2089
|
+
alg: "RS256",
|
|
2090
|
+
typ: "JWT"
|
|
2091
|
+
};
|
|
2092
|
+
const claimSet = {
|
|
2093
|
+
iss: clientEmail,
|
|
2094
|
+
scope: "https://www.googleapis.com/auth/firebase.messaging",
|
|
2095
|
+
aud: "https://oauth2.googleapis.com/token",
|
|
2096
|
+
exp: now + 3600,
|
|
2097
|
+
iat: now
|
|
2098
|
+
};
|
|
2099
|
+
const encodedHeader = base64UrlEncode(JSON.stringify(header));
|
|
2100
|
+
const encodedClaimSet = base64UrlEncode(JSON.stringify(claimSet));
|
|
2101
|
+
const signatureInput = `${encodedHeader}.${encodedClaimSet}`;
|
|
2102
|
+
const formattedKey = privateKey.replace(/\\n/g, "\n");
|
|
2103
|
+
const signer = crypto__namespace.createSign("RSA-SHA256");
|
|
2104
|
+
signer.update(signatureInput);
|
|
2105
|
+
const signature = signer.sign(formattedKey, "base64");
|
|
2106
|
+
const jwt = `${signatureInput}.${signature.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_")}`;
|
|
2107
|
+
const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
|
|
2108
|
+
method: "POST",
|
|
2109
|
+
headers: {
|
|
2110
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
2111
|
+
},
|
|
2112
|
+
body: new URLSearchParams({
|
|
2113
|
+
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
|
2114
|
+
assertion: jwt
|
|
2115
|
+
}).toString()
|
|
2116
|
+
});
|
|
2117
|
+
if (!tokenRes.ok) {
|
|
2118
|
+
const errText = await tokenRes.text();
|
|
2119
|
+
throw new Error(`Failed to obtain Google access token: ${errText}`);
|
|
2120
|
+
}
|
|
2121
|
+
const tokenData = await tokenRes.json();
|
|
2122
|
+
if (!tokenData.access_token) {
|
|
2123
|
+
throw new Error("Google OAuth token response missing access_token");
|
|
2124
|
+
}
|
|
2125
|
+
return tokenData.access_token;
|
|
2126
|
+
}
|
|
2127
|
+
chunkUSNT2KNT_cjs.__name(getGoogleAccessToken, "getGoogleAccessToken");
|
|
2128
|
+
async function resolveFcmServiceAccount(dataSource, entityMap) {
|
|
2129
|
+
const pushSettings = await getSettingsGroup(dataSource, entityMap, "push");
|
|
2130
|
+
let projectId = pushSettings.firebase_project_id?.trim();
|
|
2131
|
+
let clientEmail = pushSettings.firebase_client_email?.trim();
|
|
2132
|
+
let privateKey = pushSettings.firebase_private_key?.trim();
|
|
2133
|
+
if (pushSettings.firebase_service_account_json?.trim()) {
|
|
2134
|
+
try {
|
|
2135
|
+
const parsed = JSON.parse(pushSettings.firebase_service_account_json.trim());
|
|
2136
|
+
if (parsed.project_id) projectId = parsed.project_id;
|
|
2137
|
+
if (parsed.client_email) clientEmail = parsed.client_email;
|
|
2138
|
+
if (parsed.private_key) privateKey = parsed.private_key;
|
|
2139
|
+
} catch {
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
if (!projectId || !clientEmail || !privateKey) {
|
|
2143
|
+
return {
|
|
2144
|
+
ok: false,
|
|
2145
|
+
error: "Firebase Service Account JSON / credentials are not configured"
|
|
2146
|
+
};
|
|
2147
|
+
}
|
|
2148
|
+
return {
|
|
2149
|
+
ok: true,
|
|
2150
|
+
account: {
|
|
2151
|
+
projectId,
|
|
2152
|
+
clientEmail,
|
|
2153
|
+
privateKey
|
|
2154
|
+
}
|
|
2155
|
+
};
|
|
2156
|
+
}
|
|
2157
|
+
chunkUSNT2KNT_cjs.__name(resolveFcmServiceAccount, "resolveFcmServiceAccount");
|
|
2158
|
+
async function validateFcmCredentials(dataSource, entityMap) {
|
|
2159
|
+
try {
|
|
2160
|
+
const resolved = await resolveFcmServiceAccount(dataSource, entityMap);
|
|
2161
|
+
if (!resolved.ok) {
|
|
2162
|
+
return {
|
|
2163
|
+
success: false,
|
|
2164
|
+
error: resolved.error
|
|
2165
|
+
};
|
|
2166
|
+
}
|
|
2167
|
+
const { projectId, clientEmail, privateKey } = resolved.account;
|
|
2168
|
+
await getGoogleAccessToken(clientEmail, privateKey);
|
|
2169
|
+
return {
|
|
2170
|
+
success: true,
|
|
2171
|
+
projectId,
|
|
2172
|
+
clientEmail
|
|
2173
|
+
};
|
|
2174
|
+
} catch (err) {
|
|
2175
|
+
const errorMsg = err instanceof Error ? err.message : "Unknown credential validation error";
|
|
2176
|
+
console.error("[fcm-push] Exception in validateFcmCredentials", errorMsg);
|
|
2177
|
+
return {
|
|
2178
|
+
success: false,
|
|
2179
|
+
error: errorMsg
|
|
2180
|
+
};
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
chunkUSNT2KNT_cjs.__name(validateFcmCredentials, "validateFcmCredentials");
|
|
2184
|
+
async function sendFcmPushNotification(dataSource, entityMap, msg) {
|
|
2185
|
+
try {
|
|
2186
|
+
const resolved = await resolveFcmServiceAccount(dataSource, entityMap);
|
|
2187
|
+
if (!resolved.ok) {
|
|
2188
|
+
return {
|
|
2189
|
+
success: false,
|
|
2190
|
+
error: resolved.error
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
const { projectId, clientEmail, privateKey } = resolved.account;
|
|
2194
|
+
const accessToken = await getGoogleAccessToken(clientEmail, privateKey);
|
|
2195
|
+
const fcmEndpoint = `https://fcm.googleapis.com/v1/projects/${projectId}/messages:send`;
|
|
2196
|
+
const fcmPayload = {
|
|
2197
|
+
message: {
|
|
2198
|
+
token: msg.token,
|
|
2199
|
+
notification: {
|
|
2200
|
+
title: msg.title,
|
|
2201
|
+
body: msg.body
|
|
2202
|
+
},
|
|
2203
|
+
data: msg.data || {}
|
|
2204
|
+
}
|
|
2205
|
+
};
|
|
2206
|
+
const response = await fetch(fcmEndpoint, {
|
|
2207
|
+
method: "POST",
|
|
2208
|
+
headers: {
|
|
2209
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
2210
|
+
"Content-Type": "application/json"
|
|
2211
|
+
},
|
|
2212
|
+
body: JSON.stringify(fcmPayload)
|
|
2213
|
+
});
|
|
2214
|
+
const resData = await response.json();
|
|
2215
|
+
if (!response.ok) {
|
|
2216
|
+
const errorMsg = resData.error?.message || "FCM request failed";
|
|
2217
|
+
console.error("[fcm-push] Error sending push notification", errorMsg);
|
|
2218
|
+
return {
|
|
2219
|
+
success: false,
|
|
2220
|
+
error: errorMsg
|
|
2221
|
+
};
|
|
2222
|
+
}
|
|
2223
|
+
return {
|
|
2224
|
+
success: true,
|
|
2225
|
+
messageId: resData.name
|
|
2226
|
+
};
|
|
2227
|
+
} catch (err) {
|
|
2228
|
+
const errorMsg = err instanceof Error ? err.message : "Unknown push error";
|
|
2229
|
+
console.error("[fcm-push] Exception in sendFcmPushNotification", errorMsg);
|
|
2230
|
+
return {
|
|
2231
|
+
success: false,
|
|
2232
|
+
error: errorMsg
|
|
2233
|
+
};
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
chunkUSNT2KNT_cjs.__name(sendFcmPushNotification, "sendFcmPushNotification");
|
|
2237
|
+
|
|
2238
|
+
// src/lib/order-notification-dispatcher.ts
|
|
2239
|
+
var initialized = false;
|
|
2240
|
+
function maskPhone3(raw) {
|
|
2241
|
+
const digits = String(raw ?? "").replace(/\D/g, "");
|
|
2242
|
+
if (digits.length <= 4) return "(none)";
|
|
2243
|
+
return `***${digits.slice(-4)}`;
|
|
2244
|
+
}
|
|
2245
|
+
chunkUSNT2KNT_cjs.__name(maskPhone3, "maskPhone");
|
|
2246
|
+
async function getSettingsGroup(dataSource, entityMap, group) {
|
|
2247
|
+
if (!entityMap.configs) return {};
|
|
2248
|
+
const rows = await dataSource.getRepository(entityMap.configs).find({
|
|
2249
|
+
where: {
|
|
2250
|
+
settings: group,
|
|
2251
|
+
deleted: false
|
|
2252
|
+
}
|
|
2253
|
+
});
|
|
2254
|
+
return Object.fromEntries(rows.map((r) => [
|
|
2255
|
+
r.key,
|
|
2256
|
+
r.value
|
|
2257
|
+
]));
|
|
2258
|
+
}
|
|
2259
|
+
chunkUSNT2KNT_cjs.__name(getSettingsGroup, "getSettingsGroup");
|
|
2260
|
+
async function isEmailNotificationsEnabled(dataSource, entityMap) {
|
|
2261
|
+
const settings = await getSettingsGroup(dataSource, entityMap, "event_notifications");
|
|
2262
|
+
if (settings.enabled === "false" || settings.enabled === "0") return false;
|
|
2263
|
+
return settings.email_enabled !== "false" && settings.email_enabled !== "0";
|
|
2264
|
+
}
|
|
2265
|
+
chunkUSNT2KNT_cjs.__name(isEmailNotificationsEnabled, "isEmailNotificationsEnabled");
|
|
2266
|
+
async function isWhatsappNotificationsEnabled(dataSource, entityMap) {
|
|
2267
|
+
const settings = await getSettingsGroup(dataSource, entityMap, "event_notifications");
|
|
2268
|
+
if (settings.enabled === "false" || settings.enabled === "0") return false;
|
|
2269
|
+
return settings.whatsapp_enabled !== "false" && settings.whatsapp_enabled !== "0";
|
|
2270
|
+
}
|
|
2271
|
+
chunkUSNT2KNT_cjs.__name(isWhatsappNotificationsEnabled, "isWhatsappNotificationsEnabled");
|
|
2272
|
+
async function isTriggerCatalogEnabled(triggerKey, dataSource, entityMap) {
|
|
2273
|
+
if (!entityMap.order_notification_triggers) {
|
|
2274
|
+
console.warn("[order-notifications] order_notification_triggers missing \u2014 run migration 1781230000000-AddOrderNotificationTriggers");
|
|
2275
|
+
return false;
|
|
2276
|
+
}
|
|
2277
|
+
try {
|
|
2278
|
+
const row = await dataSource.getRepository(entityMap.order_notification_triggers).findOne({
|
|
2279
|
+
where: {
|
|
2280
|
+
triggerKey,
|
|
2281
|
+
enabled: true
|
|
2282
|
+
}
|
|
2283
|
+
});
|
|
2284
|
+
if (!row) {
|
|
2285
|
+
console.warn("[order-notifications] trigger disabled or not in DB", {
|
|
2286
|
+
triggerKey
|
|
2287
|
+
});
|
|
2288
|
+
}
|
|
2289
|
+
return Boolean(row);
|
|
2290
|
+
} catch (err) {
|
|
2291
|
+
console.error("[order-notifications] failed to load trigger catalog", {
|
|
2292
|
+
triggerKey,
|
|
2293
|
+
err
|
|
2294
|
+
});
|
|
2295
|
+
return false;
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
chunkUSNT2KNT_cjs.__name(isTriggerCatalogEnabled, "isTriggerCatalogEnabled");
|
|
2299
|
+
async function loadBoundTemplate(triggerKey, channel, audienceType = "customers", dataSource, entityMap) {
|
|
2300
|
+
if (!entityMap.order_notification_bindings || !entityMap.message_templates) {
|
|
2301
|
+
console.warn("[order-notifications] order_notification_bindings or message_templates missing from entityMap");
|
|
2302
|
+
return null;
|
|
2303
|
+
}
|
|
2304
|
+
const bindingRepo = dataSource.getRepository(entityMap.order_notification_bindings);
|
|
2305
|
+
let binding = await bindingRepo.findOne({
|
|
2306
|
+
where: {
|
|
2307
|
+
triggerKey,
|
|
2308
|
+
channel,
|
|
2309
|
+
type: audienceType,
|
|
2310
|
+
enabled: true
|
|
2311
|
+
}
|
|
2312
|
+
});
|
|
2313
|
+
if (!binding && audienceType === "customers") {
|
|
2314
|
+
binding = await bindingRepo.findOne({
|
|
2315
|
+
where: {
|
|
2316
|
+
triggerKey,
|
|
2317
|
+
channel,
|
|
2318
|
+
enabled: true
|
|
2319
|
+
}
|
|
2320
|
+
});
|
|
2321
|
+
}
|
|
2322
|
+
if (!binding) {
|
|
2323
|
+
console.warn("[order-notifications] no order_notification_bindings row", {
|
|
2324
|
+
triggerKey,
|
|
2325
|
+
channel,
|
|
2326
|
+
audienceType
|
|
2327
|
+
});
|
|
2328
|
+
return null;
|
|
2329
|
+
}
|
|
2330
|
+
const templateId = binding.messageTemplateId;
|
|
2331
|
+
if (templateId == null) {
|
|
2332
|
+
console.warn("[order-notifications] binding exists but no template selected", {
|
|
2333
|
+
triggerKey,
|
|
2334
|
+
channel,
|
|
2335
|
+
audienceType
|
|
2336
|
+
});
|
|
2337
|
+
return null;
|
|
2338
|
+
}
|
|
2339
|
+
const template = await dataSource.getRepository(entityMap.message_templates).findOne({
|
|
2340
|
+
where: {
|
|
2341
|
+
id: templateId,
|
|
2342
|
+
channel,
|
|
2343
|
+
deleted: false,
|
|
2344
|
+
enabled: true
|
|
2345
|
+
}
|
|
2346
|
+
});
|
|
2347
|
+
if (!template) {
|
|
2348
|
+
console.warn("[order-notifications] bound template not found or disabled", {
|
|
2349
|
+
triggerKey,
|
|
2350
|
+
channel,
|
|
2351
|
+
audienceType,
|
|
2352
|
+
templateId
|
|
2353
|
+
});
|
|
2354
|
+
return null;
|
|
2355
|
+
}
|
|
2356
|
+
return template;
|
|
2357
|
+
}
|
|
2358
|
+
chunkUSNT2KNT_cjs.__name(loadBoundTemplate, "loadBoundTemplate");
|
|
2359
|
+
function mergeTriggerVariables(payload, rich) {
|
|
2360
|
+
const basic = {
|
|
2361
|
+
orderId: String(payload.orderId ?? ""),
|
|
2362
|
+
orderNumber: payload.orderNumber,
|
|
2363
|
+
customerName: payload.customerName,
|
|
2364
|
+
customerPhone: payload.customerPhone ?? "",
|
|
2365
|
+
total: String(payload.total),
|
|
2366
|
+
orderTotal: String(payload.total),
|
|
2367
|
+
currency: payload.currency,
|
|
2368
|
+
vendorId: String(payload.vendorId ?? ""),
|
|
2369
|
+
...payload.refundAmount != null ? {
|
|
2370
|
+
refundAmount: String(payload.refundAmount)
|
|
2371
|
+
} : {}
|
|
2372
|
+
};
|
|
2373
|
+
return {
|
|
2374
|
+
...basic,
|
|
2375
|
+
...rich ?? {}
|
|
2376
|
+
};
|
|
2377
|
+
}
|
|
2378
|
+
chunkUSNT2KNT_cjs.__name(mergeTriggerVariables, "mergeTriggerVariables");
|
|
2379
|
+
async function isAudienceEmailEnabled(dataSource, entityMap, audience) {
|
|
2380
|
+
const settings = await getSettingsGroup(dataSource, entityMap, "event_notifications");
|
|
2381
|
+
if (settings.enabled === "false" || settings.enabled === "0") return false;
|
|
2382
|
+
if (settings.email_enabled === "false" || settings.email_enabled === "0") return false;
|
|
2383
|
+
const key = `email_${audience}_enabled`;
|
|
2384
|
+
return settings[key] !== "false" && settings[key] !== "0";
|
|
2385
|
+
}
|
|
2386
|
+
chunkUSNT2KNT_cjs.__name(isAudienceEmailEnabled, "isAudienceEmailEnabled");
|
|
2387
|
+
async function isPushNotificationsEnabled(dataSource, entityMap) {
|
|
2388
|
+
const settings = await getSettingsGroup(dataSource, entityMap, "event_notifications");
|
|
2389
|
+
if (settings.enabled === "false" || settings.enabled === "0") return false;
|
|
2390
|
+
return settings.push_enabled !== "false" && settings.push_enabled !== "0";
|
|
2391
|
+
}
|
|
2392
|
+
chunkUSNT2KNT_cjs.__name(isPushNotificationsEnabled, "isPushNotificationsEnabled");
|
|
2393
|
+
async function isAudiencePushEnabled(dataSource, entityMap, audience) {
|
|
2394
|
+
const settings = await getSettingsGroup(dataSource, entityMap, "event_notifications");
|
|
2395
|
+
if (settings.enabled === "false" || settings.enabled === "0") return false;
|
|
2396
|
+
if (settings.push_enabled === "false" || settings.push_enabled === "0") return false;
|
|
2397
|
+
const key = `push_${audience}_enabled`;
|
|
2398
|
+
return settings[key] !== "false" && settings[key] !== "0";
|
|
2399
|
+
}
|
|
2400
|
+
chunkUSNT2KNT_cjs.__name(isAudiencePushEnabled, "isAudiencePushEnabled");
|
|
2401
|
+
async function sendPushNotificationForTrigger(triggerKey, orderId, audienceType, targetId, variables, dataSource, entityMap) {
|
|
2402
|
+
let template = await loadBoundTemplate(triggerKey, "mobile", audienceType, dataSource, entityMap);
|
|
2403
|
+
if (!template) {
|
|
2404
|
+
template = await loadBoundTemplate(triggerKey, "push", audienceType, dataSource, entityMap);
|
|
2405
|
+
}
|
|
2406
|
+
if (!template || !template.body) return;
|
|
2407
|
+
const rawTitle = template.subject?.trim() || "Order Notification";
|
|
2408
|
+
const title = chunkOOTLNH63_cjs.applyTemplateVars(rawTitle, variables);
|
|
2409
|
+
const body = chunkOOTLNH63_cjs.applyTemplateVars(template.body, variables);
|
|
2410
|
+
if (!entityMap.user_device_tokens) return;
|
|
2411
|
+
const tokenRepo = dataSource.getRepository(entityMap.user_device_tokens);
|
|
2412
|
+
let targetTokens = [];
|
|
2413
|
+
try {
|
|
2414
|
+
if (audienceType === "customers") {
|
|
2415
|
+
targetTokens = await tokenRepo.find({
|
|
2416
|
+
where: {
|
|
2417
|
+
userId: targetId,
|
|
2418
|
+
isActive: true
|
|
2419
|
+
},
|
|
2420
|
+
select: [
|
|
2421
|
+
"token"
|
|
2422
|
+
]
|
|
2423
|
+
});
|
|
2424
|
+
} else if (audienceType === "vendors") {
|
|
2425
|
+
targetTokens = await tokenRepo.find({
|
|
2426
|
+
where: {
|
|
2427
|
+
vendorId: targetId,
|
|
2428
|
+
isActive: true
|
|
2429
|
+
},
|
|
2430
|
+
select: [
|
|
2431
|
+
"token"
|
|
2432
|
+
]
|
|
2433
|
+
});
|
|
2434
|
+
} else if (audienceType === "admin") {
|
|
2435
|
+
targetTokens = await tokenRepo.find({
|
|
2436
|
+
where: {
|
|
2437
|
+
isActive: true
|
|
2438
|
+
},
|
|
2439
|
+
select: [
|
|
2440
|
+
"token"
|
|
2441
|
+
]
|
|
2442
|
+
});
|
|
2443
|
+
}
|
|
2444
|
+
} catch (err) {
|
|
2445
|
+
console.error("[order-notifications] Error querying device tokens for push", err);
|
|
2446
|
+
return;
|
|
2447
|
+
}
|
|
2448
|
+
for (const t of targetTokens) {
|
|
2449
|
+
if (t.token?.trim()) {
|
|
2450
|
+
await sendFcmPushNotification(dataSource, entityMap, {
|
|
2451
|
+
token: t.token.trim(),
|
|
2452
|
+
title,
|
|
2453
|
+
body,
|
|
2454
|
+
data: {
|
|
2455
|
+
orderId: String(orderId),
|
|
2456
|
+
triggerKey
|
|
2457
|
+
}
|
|
2458
|
+
});
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
chunkUSNT2KNT_cjs.__name(sendPushNotificationForTrigger, "sendPushNotificationForTrigger");
|
|
2463
|
+
async function getOrderVendorEmails(orderId, orderVendorId, dataSource, entityMap) {
|
|
2464
|
+
const emails = /* @__PURE__ */ new Set();
|
|
2465
|
+
if (!entityMap.vendors) return [];
|
|
2466
|
+
const vendorRepo = dataSource.getRepository(entityMap.vendors);
|
|
2467
|
+
if (orderVendorId) {
|
|
2468
|
+
try {
|
|
2469
|
+
const v = await vendorRepo.findOne({
|
|
2470
|
+
where: {
|
|
2471
|
+
id: orderVendorId
|
|
2472
|
+
}
|
|
2473
|
+
});
|
|
2474
|
+
if (v) {
|
|
2475
|
+
const row = v;
|
|
2476
|
+
if (row.email?.trim()) emails.add(row.email.trim());
|
|
2477
|
+
if (row.supportEmail?.trim()) emails.add(row.supportEmail.trim());
|
|
2478
|
+
}
|
|
2479
|
+
} catch {
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
if (entityMap.order_items) {
|
|
2483
|
+
try {
|
|
2484
|
+
const items = await dataSource.getRepository(entityMap.order_items).find({
|
|
2485
|
+
where: {
|
|
2486
|
+
orderId
|
|
2487
|
+
},
|
|
2488
|
+
relations: [
|
|
2489
|
+
"product"
|
|
2490
|
+
]
|
|
2491
|
+
});
|
|
2492
|
+
for (const item of items) {
|
|
2493
|
+
const row = item;
|
|
2494
|
+
const vId = row.product?.vendorId;
|
|
2495
|
+
if (vId && vId !== orderVendorId) {
|
|
2496
|
+
const v = await vendorRepo.findOne({
|
|
2497
|
+
where: {
|
|
2498
|
+
id: vId
|
|
2499
|
+
}
|
|
2500
|
+
});
|
|
2501
|
+
if (v) {
|
|
2502
|
+
const vRow = v;
|
|
2503
|
+
if (vRow.email?.trim()) emails.add(vRow.email.trim());
|
|
2504
|
+
if (vRow.supportEmail?.trim()) emails.add(vRow.supportEmail.trim());
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
} catch {
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
return Array.from(emails);
|
|
2512
|
+
}
|
|
2513
|
+
chunkUSNT2KNT_cjs.__name(getOrderVendorEmails, "getOrderVendorEmails");
|
|
2514
|
+
async function getAdminRecipientEmails(dataSource, entityMap) {
|
|
2515
|
+
const emails = /* @__PURE__ */ new Set();
|
|
2516
|
+
const emailSettings = await getSettingsGroup(dataSource, entityMap, "email");
|
|
2517
|
+
const rawRecipients = [
|
|
2518
|
+
emailSettings.salesTeamEmails,
|
|
2519
|
+
emailSettings.salesTeamEmail,
|
|
2520
|
+
emailSettings.fulfilmentTeamEmails,
|
|
2521
|
+
emailSettings.fulfilmentTeamEmail,
|
|
2522
|
+
emailSettings.adminEmail
|
|
2523
|
+
];
|
|
2524
|
+
for (const raw of rawRecipients) {
|
|
2525
|
+
if (!raw) continue;
|
|
2526
|
+
const parts = String(raw).split(/[,;]/);
|
|
2527
|
+
for (const p of parts) {
|
|
2528
|
+
const trimmed = p.trim();
|
|
2529
|
+
if (trimmed && trimmed.includes("@")) emails.add(trimmed.toLowerCase());
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
if (entityMap.users) {
|
|
2533
|
+
try {
|
|
2534
|
+
const userRepo = dataSource.getRepository(entityMap.users);
|
|
2535
|
+
const adminUsers = await userRepo.find({
|
|
2536
|
+
where: [
|
|
2537
|
+
{
|
|
2538
|
+
groupId: 1,
|
|
2539
|
+
blocked: false
|
|
2540
|
+
},
|
|
2541
|
+
{
|
|
2542
|
+
adminAccess: true,
|
|
2543
|
+
blocked: false
|
|
2544
|
+
}
|
|
2545
|
+
]
|
|
2546
|
+
});
|
|
2547
|
+
for (const u of adminUsers) {
|
|
2548
|
+
const em = u.email?.trim().toLowerCase();
|
|
2549
|
+
if (em && em.includes("@")) {
|
|
2550
|
+
emails.add(em);
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
} catch {
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
try {
|
|
2557
|
+
const rows = await dataSource.query(`SELECT DISTINCT LOWER(TRIM(email)) AS email
|
|
2558
|
+
FROM users
|
|
2559
|
+
WHERE ("groupId" = 1 OR "adminAccess" = true)
|
|
2560
|
+
AND COALESCE(blocked, false) = false
|
|
2561
|
+
AND COALESCE(deleted, false) = false
|
|
2562
|
+
AND "deletedAt" IS NULL`);
|
|
2563
|
+
for (const r of rows) {
|
|
2564
|
+
const em = r.email?.trim().toLowerCase();
|
|
2565
|
+
if (em && em.includes("@")) {
|
|
2566
|
+
emails.add(em);
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
} catch {
|
|
2570
|
+
}
|
|
2571
|
+
return Array.from(emails);
|
|
2572
|
+
}
|
|
2573
|
+
chunkUSNT2KNT_cjs.__name(getAdminRecipientEmails, "getAdminRecipientEmails");
|
|
2574
|
+
function formatBodyForEmailHtml(body) {
|
|
2575
|
+
if (!body) return "";
|
|
2576
|
+
if (/^\s*<(html|body|table|section)[\s>]/i.test(body.trim())) {
|
|
2577
|
+
return body;
|
|
2578
|
+
}
|
|
2579
|
+
const withBreaks = body.replace(/\r\n/g, "\n").replace(/\n/g, "<br />");
|
|
2580
|
+
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>`;
|
|
2581
|
+
}
|
|
2582
|
+
chunkUSNT2KNT_cjs.__name(formatBodyForEmailHtml, "formatBodyForEmailHtml");
|
|
2583
|
+
async function sendEmailForTrigger(triggerKey, orderId, audienceType, variables, contactEmail, dataSource, entityMap, getCms) {
|
|
2584
|
+
const template = await loadBoundTemplate(triggerKey, "email", audienceType, dataSource, entityMap);
|
|
2585
|
+
if (!template) return;
|
|
2586
|
+
const [branding, emailSettings] = await Promise.all([
|
|
2587
|
+
getSettingsGroup(dataSource, entityMap, "branding"),
|
|
2588
|
+
getSettingsGroup(dataSource, entityMap, "email")
|
|
2589
|
+
]);
|
|
2590
|
+
const companyDetails = chunkBXYZDMTZ_cjs.mergeEmailLayoutCompanyDetails(branding, emailSettings);
|
|
2591
|
+
const subject = chunkOOTLNH63_cjs.applyTemplateVars(template.subject ?? "Order update", variables);
|
|
2592
|
+
const rawBody = chunkOOTLNH63_cjs.applyTemplateVars(template.body, variables);
|
|
2593
|
+
const bodyHtml = formatBodyForEmailHtml(rawBody);
|
|
2594
|
+
const html = renderLayout({
|
|
2595
|
+
bodyHtml,
|
|
2596
|
+
companyDetails
|
|
2597
|
+
});
|
|
2598
|
+
const cms = await getCms();
|
|
2599
|
+
const isCustomer = audienceType === "customers";
|
|
2600
|
+
await chunkV25RDUOU_cjs.queueEmail(cms, {
|
|
2601
|
+
to: contactEmail,
|
|
2602
|
+
subject,
|
|
2603
|
+
html,
|
|
2604
|
+
text: rawBody.replace(/<[^>]+>/g, "").trim(),
|
|
2605
|
+
...isCustomer ? {
|
|
2606
|
+
orderId,
|
|
2607
|
+
attachInvoice: true
|
|
2608
|
+
} : {}
|
|
2609
|
+
});
|
|
2610
|
+
console.log("[order-notifications] email queued", {
|
|
2611
|
+
triggerKey,
|
|
2612
|
+
audienceType,
|
|
2613
|
+
orderId,
|
|
2614
|
+
to: contactEmail,
|
|
2615
|
+
attachInvoice: isCustomer
|
|
2616
|
+
});
|
|
2617
|
+
}
|
|
2618
|
+
chunkUSNT2KNT_cjs.__name(sendEmailForTrigger, "sendEmailForTrigger");
|
|
2619
|
+
async function sendWhatsAppForTrigger(triggerKey, orderId, phone, variables, dataSource, entityMap, getCms) {
|
|
2620
|
+
const template = await loadBoundTemplate(triggerKey, "whatsapp", "customers", dataSource, entityMap);
|
|
2621
|
+
if (!template) return;
|
|
2622
|
+
const paramOrder = template.providerMeta?.paramOrder ?? [];
|
|
2623
|
+
if (paramOrder.length === 0) {
|
|
2624
|
+
console.warn("[order-notifications] WhatsApp template has no paramOrder set \u2014 variables may send blank/wrong", {
|
|
2625
|
+
triggerKey
|
|
2626
|
+
});
|
|
2627
|
+
}
|
|
2628
|
+
const cms = await getCms();
|
|
2629
|
+
const metaTemplateName = resolveMetaWhatsAppTemplateName({
|
|
2630
|
+
externalTemplateRef: template.externalTemplateRef,
|
|
2631
|
+
name: template.name
|
|
2632
|
+
});
|
|
2633
|
+
if (!metaTemplateName) {
|
|
2634
|
+
console.warn("[order-notifications] bound WhatsApp template has no Meta template name", {
|
|
2635
|
+
triggerKey,
|
|
2636
|
+
templateId: template.id
|
|
2637
|
+
});
|
|
2638
|
+
return;
|
|
2639
|
+
}
|
|
2640
|
+
const sent = await queueWhatsApp(cms, {
|
|
2641
|
+
to: phone.trim(),
|
|
2642
|
+
templateKey: triggerKey,
|
|
2643
|
+
externalTemplateRef: metaTemplateName,
|
|
2644
|
+
templateLanguage: template.providerMeta?.language ?? void 0,
|
|
2645
|
+
body: template.body,
|
|
2646
|
+
variables,
|
|
2647
|
+
templateParamOrder: paramOrder
|
|
2648
|
+
});
|
|
2649
|
+
if (!sent) {
|
|
2650
|
+
console.warn("[order-notifications] WhatsApp send failed", {
|
|
2651
|
+
triggerKey,
|
|
2652
|
+
orderId,
|
|
2653
|
+
hint: "Register whatsappPlugin() + queuePlugin() in getCms(), or set WHATSAPP_ACCESS_TOKEN and WHATSAPP_PHONE_NUMBER_ID"
|
|
2654
|
+
});
|
|
2655
|
+
} else {
|
|
2656
|
+
console.log("[order-notifications] WhatsApp queued/sent", {
|
|
2657
|
+
triggerKey,
|
|
2658
|
+
orderId,
|
|
2659
|
+
phone: maskPhone3(phone)
|
|
2660
|
+
});
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
chunkUSNT2KNT_cjs.__name(sendWhatsAppForTrigger, "sendWhatsAppForTrigger");
|
|
2664
|
+
async function isOrderPaymentConfirmed(orderId, dataSource, entityMap) {
|
|
2665
|
+
if (!entityMap.orders) return true;
|
|
2666
|
+
try {
|
|
2667
|
+
const orderRepo = dataSource.getRepository(entityMap.orders);
|
|
2668
|
+
const order = await orderRepo.findOne({
|
|
2669
|
+
where: {
|
|
2670
|
+
id: orderId
|
|
2671
|
+
}
|
|
2672
|
+
});
|
|
2673
|
+
if (!order) return false;
|
|
2674
|
+
const o = order;
|
|
2675
|
+
const status = String(o.status ?? "").toLowerCase().trim();
|
|
2676
|
+
const total = Number(o.total ?? 0);
|
|
2677
|
+
if (total <= 0) return true;
|
|
2678
|
+
const confirmedStatuses = [
|
|
2679
|
+
"confirmed",
|
|
2680
|
+
"completed",
|
|
2681
|
+
"processing",
|
|
2682
|
+
"paid",
|
|
2683
|
+
"fulfilled",
|
|
2684
|
+
"shipped"
|
|
2685
|
+
];
|
|
2686
|
+
if (confirmedStatuses.includes(status)) {
|
|
2687
|
+
return true;
|
|
2688
|
+
}
|
|
2689
|
+
if (entityMap.payments) {
|
|
2690
|
+
const paymentRepo = dataSource.getRepository(entityMap.payments);
|
|
2691
|
+
const payment = await paymentRepo.findOne({
|
|
2692
|
+
where: {
|
|
2693
|
+
orderId
|
|
2694
|
+
}
|
|
2695
|
+
});
|
|
2696
|
+
if (payment) {
|
|
2697
|
+
const pStatus = String(payment.status ?? "").toLowerCase().trim();
|
|
2698
|
+
if (pStatus === "paid" || pStatus === "captured" || pStatus === "completed") {
|
|
2699
|
+
return true;
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
return false;
|
|
2704
|
+
} catch (err) {
|
|
2705
|
+
console.error("[order-notifications] failed to check order payment confirmation", err);
|
|
2706
|
+
return false;
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
chunkUSNT2KNT_cjs.__name(isOrderPaymentConfirmed, "isOrderPaymentConfirmed");
|
|
2710
|
+
async function handleTrigger(triggerKey, payload, dataSource, entityMap, getCms) {
|
|
2711
|
+
console.log("[order-notifications] received", {
|
|
2712
|
+
triggerKey,
|
|
2713
|
+
orderId: payload.orderId,
|
|
2714
|
+
phone: maskPhone3(payload.customerPhone)
|
|
2715
|
+
});
|
|
2716
|
+
if (triggerKey === "order_placed") {
|
|
2717
|
+
const isConfirmed = await isOrderPaymentConfirmed(payload.orderId, dataSource, entityMap);
|
|
2718
|
+
if (!isConfirmed) {
|
|
2719
|
+
console.log("[order-notifications] skip order_placed: order payment not confirmed yet", {
|
|
2720
|
+
orderId: payload.orderId
|
|
2721
|
+
});
|
|
2722
|
+
return;
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
const [emailChannelEnabled, whatsappChannelEnabled, pushChannelEnabled] = await Promise.all([
|
|
2726
|
+
isEmailNotificationsEnabled(dataSource, entityMap),
|
|
2727
|
+
isWhatsappNotificationsEnabled(dataSource, entityMap),
|
|
2728
|
+
isPushNotificationsEnabled(dataSource, entityMap)
|
|
2729
|
+
]);
|
|
2730
|
+
if (!emailChannelEnabled && !whatsappChannelEnabled && !pushChannelEnabled) {
|
|
2731
|
+
console.warn("[order-notifications] skip: Email, WhatsApp, and Mobile Push notifications are disabled");
|
|
2732
|
+
return;
|
|
2733
|
+
}
|
|
2734
|
+
const triggerEnabled = await isTriggerCatalogEnabled(triggerKey, dataSource, entityMap);
|
|
2735
|
+
if (!triggerEnabled) return;
|
|
2736
|
+
const context = await loadOrderNotificationContexts(payload.orderId, {
|
|
2737
|
+
dataSource,
|
|
2738
|
+
entityMap
|
|
2739
|
+
});
|
|
2740
|
+
const variables = mergeTriggerVariables(payload, context?.variables);
|
|
2741
|
+
const contactEmail = context?.contact.email?.trim() || "";
|
|
2742
|
+
const contactPhone = context?.contact.phone?.trim() || payload.customerPhone?.trim() || "";
|
|
2743
|
+
if (emailChannelEnabled) {
|
|
2744
|
+
const [customerEnabled, vendorEnabled, adminEnabled] = await Promise.all([
|
|
2745
|
+
isAudienceEmailEnabled(dataSource, entityMap, "customer"),
|
|
2746
|
+
isAudienceEmailEnabled(dataSource, entityMap, "vendor"),
|
|
2747
|
+
isAudienceEmailEnabled(dataSource, entityMap, "admin")
|
|
2748
|
+
]);
|
|
2749
|
+
if (customerEnabled && contactEmail) {
|
|
2750
|
+
await sendEmailForTrigger(triggerKey, payload.orderId, "customers", variables, contactEmail, dataSource, entityMap, getCms);
|
|
2751
|
+
}
|
|
2752
|
+
if (vendorEnabled) {
|
|
2753
|
+
const multiVendorSettings = await getSettingsGroup(dataSource, entityMap, "multi_vendor");
|
|
2754
|
+
if (multiVendorSettings.enabled !== "false" && multiVendorSettings.enabled !== "0") {
|
|
2755
|
+
const vendorEmails = await getOrderVendorEmails(payload.orderId, payload.vendorId, dataSource, entityMap);
|
|
2756
|
+
for (const vEmail of vendorEmails) {
|
|
2757
|
+
await sendEmailForTrigger(triggerKey, payload.orderId, "vendors", variables, vEmail, dataSource, entityMap, getCms);
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
if (adminEnabled) {
|
|
2762
|
+
const adminEmails = await getAdminRecipientEmails(dataSource, entityMap);
|
|
2763
|
+
for (const aEmail of adminEmails) {
|
|
2764
|
+
await sendEmailForTrigger(triggerKey, payload.orderId, "admin", variables, aEmail, dataSource, entityMap, getCms);
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
} else {
|
|
2768
|
+
console.log("[order-notifications] skip email: Email notifications channel is disabled");
|
|
2769
|
+
}
|
|
2770
|
+
if (pushChannelEnabled) {
|
|
2771
|
+
const [pushCustomerEnabled, pushVendorEnabled, pushAdminEnabled] = await Promise.all([
|
|
2772
|
+
isAudiencePushEnabled(dataSource, entityMap, "customer"),
|
|
2773
|
+
isAudiencePushEnabled(dataSource, entityMap, "vendor"),
|
|
2774
|
+
isAudiencePushEnabled(dataSource, entityMap, "admin")
|
|
2775
|
+
]);
|
|
2776
|
+
const targetCustomerId = context?.order?.customerContactId || context?.order?.contactId || context?.order?.userId || context?.order?.id;
|
|
2777
|
+
if (pushCustomerEnabled && targetCustomerId) {
|
|
2778
|
+
await sendPushNotificationForTrigger(triggerKey, payload.orderId, "customers", String(targetCustomerId), variables, dataSource, entityMap);
|
|
2779
|
+
}
|
|
2780
|
+
if (pushVendorEnabled && payload.vendorId) {
|
|
2781
|
+
await sendPushNotificationForTrigger(triggerKey, payload.orderId, "vendors", String(payload.vendorId), variables, dataSource, entityMap);
|
|
2782
|
+
}
|
|
2783
|
+
if (pushAdminEnabled) {
|
|
2784
|
+
await sendPushNotificationForTrigger(triggerKey, payload.orderId, "admin", "admin", variables, dataSource, entityMap);
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
if (whatsappChannelEnabled) {
|
|
2788
|
+
if (contactPhone) {
|
|
2789
|
+
await sendWhatsAppForTrigger(triggerKey, payload.orderId, contactPhone, variables, dataSource, entityMap, getCms);
|
|
2790
|
+
} else {
|
|
2791
|
+
console.warn("[order-notifications] skip WhatsApp: no customer phone", {
|
|
2792
|
+
triggerKey,
|
|
2793
|
+
orderId: payload.orderId
|
|
2794
|
+
});
|
|
2795
|
+
}
|
|
2796
|
+
} else {
|
|
2797
|
+
console.log("[order-notifications] skip WhatsApp: WhatsApp notifications channel is disabled");
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
chunkUSNT2KNT_cjs.__name(handleTrigger, "handleTrigger");
|
|
2801
|
+
function initWhatsappTriggerDispatcher(deps) {
|
|
2802
|
+
if (initialized) return;
|
|
2803
|
+
initialized = true;
|
|
2804
|
+
const { dataSource, entityMap, getCms } = deps;
|
|
2805
|
+
chunkUCKN4BBY_cjs.notificationTriggerEmitter.onAnyTrigger((triggerKey, payload) => {
|
|
2806
|
+
void handleTrigger(triggerKey, payload, dataSource, entityMap, getCms).catch((err) => {
|
|
2807
|
+
console.error("[order-notifications]", triggerKey, err);
|
|
2808
|
+
});
|
|
2809
|
+
});
|
|
2810
|
+
console.log("[order-notifications] dispatcher initialized (email + WhatsApp, unified via order_notification_bindings)");
|
|
2811
|
+
}
|
|
2812
|
+
chunkUSNT2KNT_cjs.__name(initWhatsappTriggerDispatcher, "initWhatsappTriggerDispatcher");
|
|
2813
|
+
async function resendOrderNotification(triggerKey, orderId, deps) {
|
|
2814
|
+
if (!deps.entityMap.orders) return {
|
|
2815
|
+
ok: false,
|
|
2816
|
+
error: "orders entity missing"
|
|
2817
|
+
};
|
|
2818
|
+
const [emailEnabled, whatsappEnabled] = await Promise.all([
|
|
2819
|
+
isEmailNotificationsEnabled(deps.dataSource, deps.entityMap),
|
|
2820
|
+
isWhatsappNotificationsEnabled(deps.dataSource, deps.entityMap)
|
|
2821
|
+
]);
|
|
2822
|
+
if (!emailEnabled && !whatsappEnabled) {
|
|
2823
|
+
return {
|
|
2824
|
+
ok: false,
|
|
2825
|
+
error: "Both Email and WhatsApp notifications are disabled"
|
|
2826
|
+
};
|
|
2827
|
+
}
|
|
2828
|
+
const triggerEnabled = await isTriggerCatalogEnabled(triggerKey, deps.dataSource, deps.entityMap);
|
|
2829
|
+
if (!triggerEnabled) {
|
|
2830
|
+
return {
|
|
2831
|
+
ok: false,
|
|
2832
|
+
error: `Trigger "${triggerKey}" is disabled or migration 1781230000000 not applied`
|
|
2833
|
+
};
|
|
2834
|
+
}
|
|
2835
|
+
const order = await deps.dataSource.getRepository(deps.entityMap.orders).findOne({
|
|
2836
|
+
where: {
|
|
2837
|
+
id: orderId,
|
|
2838
|
+
deleted: false
|
|
2839
|
+
},
|
|
2840
|
+
relations: [
|
|
2841
|
+
"contact"
|
|
2842
|
+
]
|
|
2843
|
+
});
|
|
2844
|
+
if (!order) return {
|
|
2845
|
+
ok: false,
|
|
2846
|
+
error: "Order not found"
|
|
2847
|
+
};
|
|
2848
|
+
const o = order;
|
|
2849
|
+
await handleTrigger(triggerKey, {
|
|
2850
|
+
orderId,
|
|
2851
|
+
orderNumber: String(o.orderNumber ?? ""),
|
|
2852
|
+
customerName: o.contact?.name ?? "",
|
|
2853
|
+
customerPhone: o.contact?.phone ?? null,
|
|
2854
|
+
total: o.total ?? 0,
|
|
2855
|
+
currency: String(o.currency ?? "INR"),
|
|
2856
|
+
vendorId: o.vendorId ?? null
|
|
2857
|
+
}, deps.dataSource, deps.entityMap, deps.getCms);
|
|
2858
|
+
return {
|
|
2859
|
+
ok: true
|
|
2860
|
+
};
|
|
2861
|
+
}
|
|
2862
|
+
chunkUSNT2KNT_cjs.__name(resendOrderNotification, "resendOrderNotification");
|
|
1427
2863
|
|
|
1428
2864
|
exports.CHAT_EMAIL_LOG = CHAT_EMAIL_LOG;
|
|
1429
2865
|
exports.EmailService = EmailService;
|
|
2866
|
+
exports.WhatsAppService = WhatsAppService;
|
|
1430
2867
|
exports.buildChatbotSystemPromptWithEmailTool = buildChatbotSystemPromptWithEmailTool;
|
|
2868
|
+
exports.buildEventOrderTemplateVariables = buildEventOrderTemplateVariables;
|
|
1431
2869
|
exports.buildTranscriptForLeadEmail = buildTranscriptForLeadEmail;
|
|
1432
2870
|
exports.detectChatLeadIntent = detectChatLeadIntent;
|
|
1433
2871
|
exports.emailPlugin = emailPlugin;
|
|
1434
2872
|
exports.emailTemplates = emailTemplates;
|
|
2873
|
+
exports.getSettingsGroup = getSettingsGroup;
|
|
2874
|
+
exports.initWhatsappTriggerDispatcher = initWhatsappTriggerDispatcher;
|
|
2875
|
+
exports.isWhatsAppPluginEnabled = isWhatsAppPluginEnabled;
|
|
1435
2876
|
exports.joinRecipientsForSend = joinRecipientsForSend;
|
|
2877
|
+
exports.mergeWhatsAppConfigLayers = mergeWhatsAppConfigLayers;
|
|
1436
2878
|
exports.parseEmailRecipientsFromConfig = parseEmailRecipientsFromConfig;
|
|
1437
2879
|
exports.parseIntentRecipientEmails = parseIntentRecipientEmails;
|
|
2880
|
+
exports.queueWhatsApp = queueWhatsApp;
|
|
2881
|
+
exports.registerWhatsAppQueueProcessor = registerWhatsAppQueueProcessor;
|
|
1438
2882
|
exports.renderEmail = renderEmail;
|
|
1439
2883
|
exports.renderLayout = renderLayout;
|
|
2884
|
+
exports.resendOrderNotification = resendOrderNotification;
|
|
1440
2885
|
exports.resolveChatEmailToolSettings = resolveChatEmailToolSettings;
|
|
1441
2886
|
exports.sendChatLeadEmail = sendChatLeadEmail;
|
|
2887
|
+
exports.sendFcmPushNotification = sendFcmPushNotification;
|
|
1442
2888
|
exports.serializeEmailRecipients = serializeEmailRecipients;
|
|
2889
|
+
exports.validateFcmCredentials = validateFcmCredentials;
|
|
2890
|
+
exports.whatsAppConfigured = whatsAppConfigured;
|