@infuro/cms-core 1.0.60 → 1.0.62
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/dist/admin.cjs +4179 -2665
- package/dist/admin.js +2990 -1476
- package/dist/api.cjs +41 -37
- package/dist/api.js +9 -5
- package/dist/{chunk-URN7GS23.js → chunk-6LV36PCL.js} +199 -1059
- package/dist/{chunk-O2642WVS.js → chunk-6XAVZE4I.js} +17 -3
- package/dist/{chunk-BXYZDMTZ.cjs → chunk-AXMP2U43.cjs} +3 -94
- package/dist/{chunk-T3HPSF7K.js → chunk-AYWDQFJZ.js} +3 -0
- package/dist/chunk-CTIEAHQL.cjs +96 -0
- package/dist/chunk-GWDI752Q.js +83 -0
- package/dist/chunk-GZGFNVTK.js +886 -0
- package/dist/chunk-H3IVLIPR.cjs +86 -0
- package/dist/{chunk-2KUCQVAQ.js → chunk-HMLVPIJR.js} +2 -91
- package/dist/{chunk-V25RDUOU.cjs → chunk-LRD4CGOG.cjs} +38 -4
- package/dist/{chunk-73WVHINE.cjs → chunk-LVQSG25Y.cjs} +18 -4
- package/dist/chunk-NMYGQWHV.js +93 -0
- package/dist/chunk-OVTOP42W.js +199 -0
- package/dist/{chunk-BSLNXKV2.cjs → chunk-QA5C477U.cjs} +927 -351
- package/dist/{chunk-JAEUYYGA.js → chunk-R6Q67HAS.js} +876 -291
- package/dist/{chunk-YSYX4GUD.cjs → chunk-TWTISTZB.cjs} +210 -1080
- package/dist/{chunk-77I3KS7V.cjs → chunk-UEE4PTTB.cjs} +3 -0
- package/dist/chunk-WFMVD567.cjs +209 -0
- package/dist/{chunk-L3525VXI.js → chunk-YW7EF7K7.js} +36 -2
- package/dist/chunk-ZPAPCGCR.cjs +899 -0
- package/dist/{email-queue-OI2HSCGE.js → email-queue-65PQAG4C.js} +3 -2
- package/dist/{email-queue-6NJY6HNM.cjs → email-queue-J7DXYNCE.cjs} +7 -6
- package/dist/email-service-F7HA4X63.js +3 -0
- package/dist/email-service-RDOTY3CO.cjs +28 -0
- package/dist/{emit-order-notification-trigger-TNEZOYEE.cjs → emit-order-notification-trigger-K7URAZHJ.cjs} +4 -4
- package/dist/{emit-order-notification-trigger-CZU3V2WE.js → emit-order-notification-trigger-MD7EBODC.js} +2 -2
- package/dist/{erp-order-invoice-EYYNR526.cjs → erp-order-invoice-C5GZCOBM.cjs} +6 -5
- package/dist/{erp-order-invoice-3GXDE443.js → erp-order-invoice-MYFCXOCY.js} +2 -1
- package/dist/index.cjs +638 -346
- package/dist/index.d.cts +53 -19
- package/dist/index.d.ts +53 -19
- package/dist/index.js +350 -58
- package/dist/migrations/1783000000000-AddOrderPendingProcessingCompletedTriggers.ts +56 -0
- package/dist/migrations/1783100000000-AddSmsToOrderNotificationBindingsChannelEnum.ts +17 -0
- package/dist/migrations/1783200000000-AddOrderAssigneeColumns.ts +30 -0
- package/dist/order-assignees-handlers-LKMAGLG6.js +150 -0
- package/dist/order-assignees-handlers-NPKD64P6.cjs +152 -0
- package/dist/order-completion-otp-handlers-3NTOGEA5.js +361 -0
- package/dist/order-completion-otp-handlers-4EOLYLAP.cjs +363 -0
- package/dist/order-notification-dispatcher-IYMOS4VY.js +10 -0
- package/dist/order-notification-dispatcher-X2XM5VUY.cjs +35 -0
- package/dist/sms-notification-templates-handlers-SJLO7JGZ.js +47 -0
- package/dist/sms-notification-templates-handlers-YSB3L2PK.cjs +49 -0
- package/package.json +1 -1
- package/dist/order-notification-dispatcher-KJVGLK5E.cjs +0 -24
- package/dist/order-notification-dispatcher-ZSFKEYMP.js +0 -7
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { queueWhatsApp } from './chunk-GWDI752Q.js';
|
|
2
|
+
import { notificationTriggerEmitter } from './chunk-AYWDQFJZ.js';
|
|
3
|
+
import { sesRegionSupportsSmtp, sesSmtpHost, EmailService, renderLayout } from './chunk-GZGFNVTK.js';
|
|
4
|
+
import { queueEmail } from './chunk-YW7EF7K7.js';
|
|
5
|
+
import { mergeEmailLayoutCompanyDetails } from './chunk-NMYGQWHV.js';
|
|
4
6
|
import { applyTemplateVars } from './chunk-CRFV5WJK.js';
|
|
5
7
|
import { __name } from './chunk-SHUYVCID.js';
|
|
6
|
-
import { SESClient, SendRawEmailCommand, SendEmailCommand } from '@aws-sdk/client-ses';
|
|
7
|
-
import { createRequire } from 'module';
|
|
8
|
-
import nodemailer from 'nodemailer';
|
|
9
8
|
import * as crypto from 'crypto';
|
|
10
9
|
|
|
11
10
|
// src/lib/event-order-template-context.ts
|
|
@@ -306,86 +305,6 @@ async function loadOrderNotificationContexts(orderId, deps) {
|
|
|
306
305
|
}
|
|
307
306
|
__name(loadOrderNotificationContexts, "loadOrderNotificationContexts");
|
|
308
307
|
|
|
309
|
-
// src/plugins/whatsapp/whatsapp-queue.ts
|
|
310
|
-
var WHATSAPP_QUEUE_NAME = "whatsapp";
|
|
311
|
-
function maskPhone(raw) {
|
|
312
|
-
const digits = raw.replace(/\D/g, "");
|
|
313
|
-
if (digits.length <= 4) return "****";
|
|
314
|
-
return `${"*".repeat(Math.max(0, digits.length - 4))}${digits.slice(-4)}`;
|
|
315
|
-
}
|
|
316
|
-
__name(maskPhone, "maskPhone");
|
|
317
|
-
function registerWhatsAppQueueProcessor(cms) {
|
|
318
|
-
const queue = cms.getPlugin("queue");
|
|
319
|
-
const whatsapp = cms.getPlugin("whatsapp");
|
|
320
|
-
if (!queue) {
|
|
321
|
-
console.warn("[whatsapp] queue processor not registered: queue plugin missing");
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
if (!whatsapp || typeof whatsapp.send !== "function") {
|
|
325
|
-
console.warn("[whatsapp] queue processor not registered: whatsapp plugin missing or not configured");
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
queue.registerProcessor(WHATSAPP_QUEUE_NAME, async (data) => {
|
|
329
|
-
const payload = data;
|
|
330
|
-
if (!payload.to) {
|
|
331
|
-
console.warn("[whatsapp] queue job skipped: missing recipient");
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
console.log("[whatsapp] queue job processing", {
|
|
335
|
-
to: maskPhone(payload.to),
|
|
336
|
-
templateKey: payload.templateKey ?? null,
|
|
337
|
-
externalTemplateRef: payload.externalTemplateRef ?? null
|
|
338
|
-
});
|
|
339
|
-
try {
|
|
340
|
-
const ok = await whatsapp.send(payload);
|
|
341
|
-
if (ok) {
|
|
342
|
-
console.log("[whatsapp] queue job completed", {
|
|
343
|
-
to: maskPhone(payload.to),
|
|
344
|
-
sent: true
|
|
345
|
-
});
|
|
346
|
-
} else {
|
|
347
|
-
console.warn("[whatsapp] queue job completed without delivery", {
|
|
348
|
-
to: maskPhone(payload.to),
|
|
349
|
-
sent: false
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
} catch (err) {
|
|
353
|
-
console.error("[whatsapp] queue job failed", {
|
|
354
|
-
to: maskPhone(payload.to),
|
|
355
|
-
error: err instanceof Error ? err.message : String(err)
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
});
|
|
359
|
-
console.log("[whatsapp] queue processor registered", {
|
|
360
|
-
queue: WHATSAPP_QUEUE_NAME
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
__name(registerWhatsAppQueueProcessor, "registerWhatsAppQueueProcessor");
|
|
364
|
-
async function queueWhatsApp(cms, payload) {
|
|
365
|
-
const queue = cms.getPlugin("queue");
|
|
366
|
-
if (queue) {
|
|
367
|
-
console.log("[whatsapp] enqueue", {
|
|
368
|
-
to: maskPhone(payload.to),
|
|
369
|
-
templateKey: payload.templateKey ?? null,
|
|
370
|
-
externalTemplateRef: payload.externalTemplateRef ?? null
|
|
371
|
-
});
|
|
372
|
-
await queue.add(WHATSAPP_QUEUE_NAME, payload);
|
|
373
|
-
return true;
|
|
374
|
-
}
|
|
375
|
-
const whatsapp = cms.getPlugin("whatsapp");
|
|
376
|
-
if (whatsapp && typeof whatsapp.send === "function") {
|
|
377
|
-
console.log("[whatsapp] no queue plugin \u2014 sending immediately", {
|
|
378
|
-
to: maskPhone(payload.to)
|
|
379
|
-
});
|
|
380
|
-
return whatsapp.send(payload);
|
|
381
|
-
}
|
|
382
|
-
console.warn("[whatsapp] send skipped: neither queue nor whatsapp plugin available", {
|
|
383
|
-
to: maskPhone(payload.to)
|
|
384
|
-
});
|
|
385
|
-
return false;
|
|
386
|
-
}
|
|
387
|
-
__name(queueWhatsApp, "queueWhatsApp");
|
|
388
|
-
|
|
389
308
|
// src/plugins/whatsapp/whatsapp-service.ts
|
|
390
309
|
function norm(v) {
|
|
391
310
|
const t = v?.trim();
|
|
@@ -435,11 +354,11 @@ function resolveMetaWhatsAppTemplateName(opts) {
|
|
|
435
354
|
return ref || void 0;
|
|
436
355
|
}
|
|
437
356
|
__name(resolveMetaWhatsAppTemplateName, "resolveMetaWhatsAppTemplateName");
|
|
438
|
-
function
|
|
357
|
+
function maskPhone(digits) {
|
|
439
358
|
if (digits.length <= 4) return "****";
|
|
440
359
|
return `${"*".repeat(Math.max(0, digits.length - 4))}${digits.slice(-4)}`;
|
|
441
360
|
}
|
|
442
|
-
__name(
|
|
361
|
+
__name(maskPhone, "maskPhone");
|
|
443
362
|
function logWhatsApp(level, message, extra) {
|
|
444
363
|
const line = extra ? `${message} ${JSON.stringify(extra)}` : message;
|
|
445
364
|
if (level === "info") console.log(`[whatsapp] ${line}`);
|
|
@@ -481,7 +400,7 @@ var WhatsAppService = class {
|
|
|
481
400
|
return false;
|
|
482
401
|
}
|
|
483
402
|
logWhatsApp("info", "send started", {
|
|
484
|
-
to:
|
|
403
|
+
to: maskPhone(to),
|
|
485
404
|
templateKey: opts.templateKey ?? null,
|
|
486
405
|
externalTemplateRef: opts.externalTemplateRef ?? null,
|
|
487
406
|
phoneNumberId: cfg.phoneNumberId
|
|
@@ -524,7 +443,7 @@ var WhatsAppService = class {
|
|
|
524
443
|
body
|
|
525
444
|
] : [];
|
|
526
445
|
logWhatsApp("info", "sending template message", {
|
|
527
|
-
to:
|
|
446
|
+
to: maskPhone(to),
|
|
528
447
|
templateName,
|
|
529
448
|
paramCount: paramValues.length,
|
|
530
449
|
paramOrder: opts.templateParamOrder ?? null
|
|
@@ -533,12 +452,12 @@ var WhatsAppService = class {
|
|
|
533
452
|
}
|
|
534
453
|
if (!body) {
|
|
535
454
|
logWhatsApp("warn", "send skipped: no template name and empty body", {
|
|
536
|
-
to:
|
|
455
|
+
to: maskPhone(to)
|
|
537
456
|
});
|
|
538
457
|
return false;
|
|
539
458
|
}
|
|
540
459
|
logWhatsApp("info", "sending text message", {
|
|
541
|
-
to:
|
|
460
|
+
to: maskPhone(to),
|
|
542
461
|
bodyLength: body.length
|
|
543
462
|
});
|
|
544
463
|
return this.sendTextMessage(cfg, to, body);
|
|
@@ -564,14 +483,14 @@ var WhatsAppService = class {
|
|
|
564
483
|
const errText = await res.text().catch(() => "");
|
|
565
484
|
logWhatsApp("error", "text send failed", {
|
|
566
485
|
status: res.status,
|
|
567
|
-
to:
|
|
486
|
+
to: maskPhone(to),
|
|
568
487
|
error: errText
|
|
569
488
|
});
|
|
570
489
|
return false;
|
|
571
490
|
}
|
|
572
491
|
const data = await res.json().catch(() => ({}));
|
|
573
492
|
logWhatsApp("info", "text send succeeded", {
|
|
574
|
-
to:
|
|
493
|
+
to: maskPhone(to),
|
|
575
494
|
messageId: data.messages?.[0]?.id ?? null
|
|
576
495
|
});
|
|
577
496
|
return true;
|
|
@@ -613,7 +532,7 @@ var WhatsAppService = class {
|
|
|
613
532
|
const errText = await res.text().catch(() => "");
|
|
614
533
|
logWhatsApp("error", "template send failed", {
|
|
615
534
|
status: res.status,
|
|
616
|
-
to:
|
|
535
|
+
to: maskPhone(to),
|
|
617
536
|
templateName,
|
|
618
537
|
paramCount: paramValues.length,
|
|
619
538
|
error: errText
|
|
@@ -622,7 +541,7 @@ var WhatsAppService = class {
|
|
|
622
541
|
}
|
|
623
542
|
const data = await res.json().catch(() => ({}));
|
|
624
543
|
logWhatsApp("info", "template send succeeded", {
|
|
625
|
-
to:
|
|
544
|
+
to: maskPhone(to),
|
|
626
545
|
templateName,
|
|
627
546
|
messageId: data.messages?.[0]?.id ?? null
|
|
628
547
|
});
|
|
@@ -630,886 +549,6 @@ var WhatsAppService = class {
|
|
|
630
549
|
}
|
|
631
550
|
};
|
|
632
551
|
|
|
633
|
-
// src/plugins/email/templates/layout.ts
|
|
634
|
-
function escapeHtml(s) {
|
|
635
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
636
|
-
}
|
|
637
|
-
__name(escapeHtml, "escapeHtml");
|
|
638
|
-
function socialLinkInnerHtml(s) {
|
|
639
|
-
if (s.iconUrl?.trim()) {
|
|
640
|
-
return `<img src="${escapeHtml(s.iconUrl.trim())}" alt="" width="20" height="20" style="display:inline-block;vertical-align:middle;border:0;" />`;
|
|
641
|
-
}
|
|
642
|
-
const label = s.icon && s.icon.trim() || "\u{1F517}";
|
|
643
|
-
return escapeHtml(label);
|
|
644
|
-
}
|
|
645
|
-
__name(socialLinkInnerHtml, "socialLinkInnerHtml");
|
|
646
|
-
function supportLinesHtml(supportEmail, supportPhone, opts) {
|
|
647
|
-
const parts = [];
|
|
648
|
-
const top = opts?.tightTop ? "margin-top:0;" : "margin-top:4px;";
|
|
649
|
-
if (supportEmail) {
|
|
650
|
-
parts.push(`<div style="font-size:13px;color:#444;${top}">📧 ${escapeHtml(supportEmail)}</div>`);
|
|
651
|
-
}
|
|
652
|
-
if (supportPhone) {
|
|
653
|
-
parts.push(`<div style="font-size:13px;color:#444;margin-top:2px;">📞 ${escapeHtml(supportPhone)}</div>`);
|
|
654
|
-
}
|
|
655
|
-
return parts.join("");
|
|
656
|
-
}
|
|
657
|
-
__name(supportLinesHtml, "supportLinesHtml");
|
|
658
|
-
function renderLayout(options) {
|
|
659
|
-
const { bodyHtml, companyDetails } = options;
|
|
660
|
-
const { logoUrl, companyName, supportEmail, supportPhone, socialLinks, footerDisclaimer, followUsTitle = "Follow Us" } = companyDetails;
|
|
661
|
-
const supportFooterHtml = supportLinesHtml(supportEmail, supportPhone, {
|
|
662
|
-
tightTop: true
|
|
663
|
-
});
|
|
664
|
-
const hasSocial = Boolean(socialLinks?.length);
|
|
665
|
-
const brandFooterInner = [];
|
|
666
|
-
if (logoUrl) {
|
|
667
|
-
brandFooterInner.push(`<img src="${escapeHtml(logoUrl)}" alt="" style="max-height:28px;max-width:160px;height:auto;width:auto;display:inline-block;vertical-align:middle;margin-right:10px;border:0;" />`);
|
|
668
|
-
}
|
|
669
|
-
if (companyName) {
|
|
670
|
-
brandFooterInner.push(`<span style="font-size:14px;font-weight:600;color:#111;vertical-align:middle;">${escapeHtml(companyName)}</span>`);
|
|
671
|
-
}
|
|
672
|
-
const brandFooterHtml = brandFooterInner.length ? `<div style="line-height:1.4;">${brandFooterInner.join("")}</div>` : "";
|
|
673
|
-
const followTitleHtml = hasSocial && followUsTitle ? `<div class="footer-follow-title" style="font-size:13px;font-weight:600;color:#555;margin:0;">${escapeHtml(followUsTitle)}</div>` : "";
|
|
674
|
-
const socialRowHtml = hasSocial && socialLinks ? `<div class="footer-social-icons" style="margin-top:6px;text-align:left;font-size:0;line-height:0;">${socialLinks.map((s) => `<a href="${escapeHtml(s.url)}" style="display:inline-block;margin-right:12px;text-decoration:none;color:#333;font-size:18px;line-height:1;vertical-align:middle;">${socialLinkInnerHtml(s)}</a>`).join("")}</div>` : "";
|
|
675
|
-
const rowSupport = supportFooterHtml ? `<tr><td class="footer-support-cell" colspan="2" valign="top" style="padding:0 0 10px 0;">${supportFooterHtml}</td></tr>` : "";
|
|
676
|
-
let footerBlock = "";
|
|
677
|
-
if (brandFooterHtml || followTitleHtml || socialRowHtml || footerDisclaimer || rowSupport) {
|
|
678
|
-
const rowBrand = brandFooterHtml ? `<tr><td class="footer-brand-cell" colspan="2" valign="top" style="padding:0 0 10px 0;">${brandFooterHtml}</td></tr>` : "";
|
|
679
|
-
const rowFollowTitle = followTitleHtml ? `<tr><td class="footer-follow-cell" colspan="2" valign="top" style="padding:14px 0 0 0;text-align:left;">${followTitleHtml}</td></tr>` : "";
|
|
680
|
-
const rowSocial = socialRowHtml ? `<tr><td class="footer-social-cell" colspan="2" style="padding:0;text-align:left;">${socialRowHtml}</td></tr>` : "";
|
|
681
|
-
footerBlock = `<table role="presentation" class="footer-main" width="100%" cellpadding="0" cellspacing="0" style="margin-top:28px;padding-top:16px;">${rowBrand}${rowSupport}${rowFollowTitle}${rowSocial}</table>`;
|
|
682
|
-
}
|
|
683
|
-
const disclaimerBlock = footerDisclaimer ? `<div style="margin-top:20px;padding-top:12px;font-size:11px;line-height:1.5;color:#888;">${escapeHtml(footerDisclaimer).replace(/\n/g, "<br/>")}</div>` : "";
|
|
684
|
-
const responsiveCss = `
|
|
685
|
-
@media only screen and (max-width: 600px) {
|
|
686
|
-
.email-wrap { padding-left: 12px !important; padding-right: 12px !important; }
|
|
687
|
-
.footer-follow-cell { padding-top: 12px !important; }
|
|
688
|
-
}
|
|
689
|
-
`;
|
|
690
|
-
return `<!DOCTYPE html>
|
|
691
|
-
<html>
|
|
692
|
-
<head>
|
|
693
|
-
<meta charset="utf-8">
|
|
694
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
695
|
-
<style type="text/css">${responsiveCss}</style>
|
|
696
|
-
</head>
|
|
697
|
-
<body style="margin:0;padding:0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;font-size:14px;line-height:1.5;color:#333;background:#fff;">
|
|
698
|
-
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background:#fff;">
|
|
699
|
-
<tr><td class="email-wrap" style="padding:16px;">
|
|
700
|
-
<div>
|
|
701
|
-
${bodyHtml}
|
|
702
|
-
</div>
|
|
703
|
-
${footerBlock}
|
|
704
|
-
${disclaimerBlock}
|
|
705
|
-
</td></tr>
|
|
706
|
-
</table>
|
|
707
|
-
</body>
|
|
708
|
-
</html>`;
|
|
709
|
-
}
|
|
710
|
-
__name(renderLayout, "renderLayout");
|
|
711
|
-
|
|
712
|
-
// src/plugins/email/templates/inline-cta.ts
|
|
713
|
-
function escapeHtml2(s) {
|
|
714
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
715
|
-
}
|
|
716
|
-
__name(escapeHtml2, "escapeHtml");
|
|
717
|
-
function escapeAttr(s) {
|
|
718
|
-
return escapeHtml2(s);
|
|
719
|
-
}
|
|
720
|
-
__name(escapeAttr, "escapeAttr");
|
|
721
|
-
function primaryCtaButton(href, label) {
|
|
722
|
-
return `<table role="presentation" cellpadding="0" cellspacing="0" style="margin:20px 0;">
|
|
723
|
-
<tr><td style="border-radius:6px;background:#1a1a1a;">
|
|
724
|
-
<a href="${escapeAttr(href)}" style="display:inline-block;padding:12px 22px;font-size:14px;font-weight:600;color:#ffffff;text-decoration:none;">${escapeHtml2(label)}</a>
|
|
725
|
-
</td></tr>
|
|
726
|
-
</table>`;
|
|
727
|
-
}
|
|
728
|
-
__name(primaryCtaButton, "primaryCtaButton");
|
|
729
|
-
|
|
730
|
-
// src/plugins/email/templates/signup.ts
|
|
731
|
-
function render(ctx) {
|
|
732
|
-
const { name, loginUrl, verifyEmailUrl, companyDetails } = ctx;
|
|
733
|
-
if (verifyEmailUrl) {
|
|
734
|
-
const subject2 = "Confirm your email";
|
|
735
|
-
const greeting = name && name.trim() ? `Hi ${escapeHtml2(name.trim())},` : "Hi,";
|
|
736
|
-
const bodyHtml2 = `<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">${greeting}</p>
|
|
737
|
-
<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">Thanks for signing up. Please confirm your email address to activate your account. Until you confirm, you won\u2019t be able to sign in.</p>
|
|
738
|
-
${primaryCtaButton(verifyEmailUrl, "Confirm email address")}
|
|
739
|
-
<p style="margin:16px 0 0 0;font-size:12px;line-height:1.5;color:#888;">This link expires in a few days. If you didn\u2019t create an account, you can ignore this message.<br/><span style="word-break:break-all;">${escapeHtml2(verifyEmailUrl)}</span></p>`;
|
|
740
|
-
const text2 = [
|
|
741
|
-
greeting,
|
|
742
|
-
"",
|
|
743
|
-
"Confirm your email to activate your account:",
|
|
744
|
-
verifyEmailUrl,
|
|
745
|
-
"",
|
|
746
|
-
"You cannot sign in until your email is confirmed."
|
|
747
|
-
].join("\n");
|
|
748
|
-
const html2 = renderLayout({
|
|
749
|
-
bodyHtml: bodyHtml2,
|
|
750
|
-
companyDetails
|
|
751
|
-
});
|
|
752
|
-
return {
|
|
753
|
-
subject: subject2,
|
|
754
|
-
html: html2,
|
|
755
|
-
text: text2
|
|
756
|
-
};
|
|
757
|
-
}
|
|
758
|
-
const subject = "Welcome";
|
|
759
|
-
const bodyHtml = `<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">Welcome${name ? `, ${escapeHtml2(name)}` : ""}.</p><p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">Your account has been created.</p>${loginUrl ? `${primaryCtaButton(loginUrl, "Sign in")}` : ""}`;
|
|
760
|
-
const text = `Welcome${name ? `, ${name}` : ""}. Your account has been created.${loginUrl ? ` Sign in: ${loginUrl}` : ""}`;
|
|
761
|
-
const html = renderLayout({
|
|
762
|
-
bodyHtml,
|
|
763
|
-
companyDetails
|
|
764
|
-
});
|
|
765
|
-
return {
|
|
766
|
-
subject,
|
|
767
|
-
html,
|
|
768
|
-
text
|
|
769
|
-
};
|
|
770
|
-
}
|
|
771
|
-
__name(render, "render");
|
|
772
|
-
|
|
773
|
-
// src/plugins/email/templates/passwordReset.ts
|
|
774
|
-
function render2(ctx) {
|
|
775
|
-
const { resetLink, companyDetails } = ctx;
|
|
776
|
-
const subject = "Reset your password";
|
|
777
|
-
const bodyHtml = `<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">Hello,</p>
|
|
778
|
-
<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">We received a request to reset the password for your account. If you made this request, use the button below to choose a new password.</p>
|
|
779
|
-
<p style="margin:0 0 8px 0;font-size:14px;line-height:1.5;color:#555;">For your security, this link will stop working after a short time. If it expires, request a new reset from the sign-in page.</p>
|
|
780
|
-
${primaryCtaButton(resetLink, "Reset password")}
|
|
781
|
-
<p style="margin:16px 0 0 0;font-size:12px;line-height:1.5;color:#888;">If you did not request a password reset, you can safely ignore this email\u2014your password will stay the same.</p>
|
|
782
|
-
<p style="margin:12px 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(resetLink)}</span></p>`;
|
|
783
|
-
const text = [
|
|
784
|
-
"Hello,",
|
|
785
|
-
"",
|
|
786
|
-
"We received a request to reset your password. Open the link below to set a new password:",
|
|
787
|
-
resetLink,
|
|
788
|
-
"",
|
|
789
|
-
"This link expires after a limited time.",
|
|
790
|
-
"",
|
|
791
|
-
"If you did not request this, you can ignore this email."
|
|
792
|
-
].join("\n");
|
|
793
|
-
const html = renderLayout({
|
|
794
|
-
bodyHtml,
|
|
795
|
-
companyDetails
|
|
796
|
-
});
|
|
797
|
-
return {
|
|
798
|
-
subject,
|
|
799
|
-
html,
|
|
800
|
-
text
|
|
801
|
-
};
|
|
802
|
-
}
|
|
803
|
-
__name(render2, "render");
|
|
804
|
-
|
|
805
|
-
// src/plugins/email/templates/passwordChange.ts
|
|
806
|
-
function render3(ctx) {
|
|
807
|
-
const { name, companyDetails } = ctx;
|
|
808
|
-
const subject = "Password changed";
|
|
809
|
-
const bodyHtml = `<h2>Password changed</h2><p>Your password has been updated successfully${name ? `, ${escapeHtml3(name)}` : ""}.</p>`;
|
|
810
|
-
const text = `Your password has been updated successfully${name ? `, ${name}` : ""}.`;
|
|
811
|
-
const html = renderLayout({
|
|
812
|
-
bodyHtml,
|
|
813
|
-
companyDetails
|
|
814
|
-
});
|
|
815
|
-
return {
|
|
816
|
-
subject,
|
|
817
|
-
html,
|
|
818
|
-
text
|
|
819
|
-
};
|
|
820
|
-
}
|
|
821
|
-
__name(render3, "render");
|
|
822
|
-
function escapeHtml3(s) {
|
|
823
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
824
|
-
}
|
|
825
|
-
__name(escapeHtml3, "escapeHtml");
|
|
826
|
-
|
|
827
|
-
// src/plugins/email/templates/orderPlaced.ts
|
|
828
|
-
function formatMoney(amount, currency) {
|
|
829
|
-
const v = typeof amount === "string" ? parseFloat(amount) : amount;
|
|
830
|
-
const num = Number.isFinite(v) ? Number(v).toFixed(2) : String(amount);
|
|
831
|
-
return currency ? `${num} ${currency}` : num;
|
|
832
|
-
}
|
|
833
|
-
__name(formatMoney, "formatMoney");
|
|
834
|
-
function renderLineItemsHtml(items, currency) {
|
|
835
|
-
if (!items.length) {
|
|
836
|
-
return '<p style="margin:12px 0 0 0;font-size:14px;color:#666;">No line items.</p>';
|
|
837
|
-
}
|
|
838
|
-
const rows = items.map((it) => {
|
|
839
|
-
const name = escapeHtml2(it.productName);
|
|
840
|
-
const sku = it.sku && String(it.sku).trim() ? `<span style="font-size:12px;color:#888;"> (${escapeHtml2(String(it.sku).trim())})</span>` : "";
|
|
841
|
-
const hsn = it.hsn && String(it.hsn).trim() ? `<br/><span style="font-size:11px;color:#888;">HSN: ${escapeHtml2(String(it.hsn).trim())}</span>` : "";
|
|
842
|
-
const taxNote = it.tax != null && String(it.tax).trim() && Number(it.tax) !== 0 ? `<br/><span style="font-size:11px;color:#888;">Tax: ${escapeHtml2(formatMoney(it.tax, currency))}</span>` : "";
|
|
843
|
-
return `<tr>
|
|
844
|
-
<td style="padding:10px 8px 10px 0;border-bottom:1px solid #eee;vertical-align:top;font-size:14px;color:#333;">${name}${sku}${hsn}${taxNote}</td>
|
|
845
|
-
<td align="right" style="padding:10px 8px;border-bottom:1px solid #eee;vertical-align:top;font-size:14px;color:#333;white-space:nowrap;">${escapeHtml2(String(it.quantity))}</td>
|
|
846
|
-
<td align="right" style="padding:10px 8px;border-bottom:1px solid #eee;vertical-align:top;font-size:14px;color:#333;white-space:nowrap;">${escapeHtml2(formatMoney(it.unitPrice, currency))}</td>
|
|
847
|
-
<td align="right" style="padding:10px 0 10px 8px;border-bottom:1px solid #eee;vertical-align:top;font-size:14px;color:#333;white-space:nowrap;">${escapeHtml2(formatMoney(it.lineTotal, currency))}</td>
|
|
848
|
-
</tr>`;
|
|
849
|
-
}).join("");
|
|
850
|
-
return `<p style="margin:16px 0 8px 0;font-size:14px;font-weight:600;color:#111;">Order items</p>
|
|
851
|
-
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="border-collapse:collapse;">
|
|
852
|
-
<tr>
|
|
853
|
-
<td style="padding:0 8px 8px 0;font-size:12px;font-weight:600;color:#555;text-transform:uppercase;letter-spacing:0.02em;">Item</td>
|
|
854
|
-
<td align="right" style="padding:0 8px 8px 8px;font-size:12px;font-weight:600;color:#555;text-transform:uppercase;">Qty</td>
|
|
855
|
-
<td align="right" style="padding:0 8px 8px 8px;font-size:12px;font-weight:600;color:#555;text-transform:uppercase;">Price</td>
|
|
856
|
-
<td align="right" style="padding:0 0 8px 8px;font-size:12px;font-weight:600;color:#555;text-transform:uppercase;">Total</td>
|
|
857
|
-
</tr>
|
|
858
|
-
${rows}
|
|
859
|
-
</table>`;
|
|
860
|
-
}
|
|
861
|
-
__name(renderLineItemsHtml, "renderLineItemsHtml");
|
|
862
|
-
function renderLineItemsText(items, currency) {
|
|
863
|
-
if (!items.length) return "";
|
|
864
|
-
const lines = items.map((it) => {
|
|
865
|
-
let s = `- ${it.productName} \xD7 ${it.quantity} @ ${formatMoney(it.unitPrice, currency)} = ${formatMoney(it.lineTotal, currency)}`;
|
|
866
|
-
if (it.sku) s += ` [${it.sku}]`;
|
|
867
|
-
if (it.hsn && String(it.hsn).trim()) s += ` HSN:${it.hsn}`;
|
|
868
|
-
if (it.tax != null && Number(it.tax) !== 0) s += ` tax:${formatMoney(it.tax, currency)}`;
|
|
869
|
-
return s;
|
|
870
|
-
});
|
|
871
|
-
return [
|
|
872
|
-
"Items:",
|
|
873
|
-
...lines
|
|
874
|
-
].join("\n");
|
|
875
|
-
}
|
|
876
|
-
__name(renderLineItemsText, "renderLineItemsText");
|
|
877
|
-
function formatAddressLines(addr) {
|
|
878
|
-
if (!addr || typeof addr !== "object") return [];
|
|
879
|
-
const parts = [
|
|
880
|
-
addr.line1,
|
|
881
|
-
addr.line2,
|
|
882
|
-
[
|
|
883
|
-
addr.city,
|
|
884
|
-
addr.state
|
|
885
|
-
].filter(Boolean).join(", "),
|
|
886
|
-
addr.postalCode,
|
|
887
|
-
addr.country
|
|
888
|
-
].filter((x) => x != null && String(x).trim() !== "");
|
|
889
|
-
return parts.map((p) => String(p).trim()).filter(Boolean);
|
|
890
|
-
}
|
|
891
|
-
__name(formatAddressLines, "formatAddressLines");
|
|
892
|
-
function renderAddressesHtml(billing, shipping) {
|
|
893
|
-
const bLines = formatAddressLines(billing);
|
|
894
|
-
const sLines = formatAddressLines(shipping);
|
|
895
|
-
if (!bLines.length && !sLines.length) return "";
|
|
896
|
-
let html = '<p style="margin:16px 0 8px 0;font-size:14px;font-weight:600;color:#111;">Addresses</p>';
|
|
897
|
-
if (bLines.length) {
|
|
898
|
-
html += `<p style="margin:0 0 4px 0;font-size:12px;font-weight:600;color:#555;">Billing</p><p style="margin:0 0 12px 0;font-size:14px;line-height:1.5;color:#333;">${bLines.map((l) => escapeHtml2(l)).join("<br/>")}</p>`;
|
|
899
|
-
}
|
|
900
|
-
if (sLines.length) {
|
|
901
|
-
html += `<p style="margin:0 0 4px 0;font-size:12px;font-weight:600;color:#555;">Shipping</p><p style="margin:0 0 0 0;font-size:14px;line-height:1.5;color:#333;">${sLines.map((l) => escapeHtml2(l)).join("<br/>")}</p>`;
|
|
902
|
-
}
|
|
903
|
-
return html;
|
|
904
|
-
}
|
|
905
|
-
__name(renderAddressesHtml, "renderAddressesHtml");
|
|
906
|
-
function renderAddressesText(billing, shipping) {
|
|
907
|
-
const bLines = formatAddressLines(billing);
|
|
908
|
-
const sLines = formatAddressLines(shipping);
|
|
909
|
-
const out = [];
|
|
910
|
-
if (bLines.length) out.push("Billing:", ...bLines.map((l) => ` ${l}`));
|
|
911
|
-
if (sLines.length) out.push("Shipping:", ...sLines.map((l) => ` ${l}`));
|
|
912
|
-
return out.length ? out.join("\n") : "";
|
|
913
|
-
}
|
|
914
|
-
__name(renderAddressesText, "renderAddressesText");
|
|
915
|
-
function render4(ctx) {
|
|
916
|
-
const { orderNumber, total, subtotal, tax, currency, customerName, companyDetails, audience = "customer", internalCustomerEmail, lineItems = [], billingAddress, shippingAddress, invoiceAttached = false } = ctx;
|
|
917
|
-
const itemsHtml = renderLineItemsHtml(lineItems, currency);
|
|
918
|
-
const itemsText = renderLineItemsText(lineItems, currency);
|
|
919
|
-
const addrHtml = renderAddressesHtml(billingAddress, shippingAddress);
|
|
920
|
-
const addrText = renderAddressesText(billingAddress, shippingAddress);
|
|
921
|
-
const subtotalLine = subtotal != null && String(subtotal).trim() !== "" ? `<p style="margin:8px 0 0 0;font-size:14px;line-height:1.5;color:#333;"><strong>Subtotal:</strong> ${escapeHtml2(String(subtotal))}${currency ? ` ${escapeHtml2(currency)}` : ""}</p>` : "";
|
|
922
|
-
const taxLine = tax != null && String(tax).trim() !== "" && Number(tax) !== 0 ? `<p style="margin:4px 0 0 0;font-size:14px;line-height:1.5;color:#333;"><strong>Tax:</strong> ${escapeHtml2(String(tax))}${currency ? ` ${escapeHtml2(currency)}` : ""}</p>` : "";
|
|
923
|
-
const totalLine = total != null && String(total).trim() !== "" ? `<p style="margin:12px 0 0 0;font-size:15px;line-height:1.5;color:#333;"><strong>Order total:</strong> ${escapeHtml2(String(total))}${currency ? ` ${escapeHtml2(currency)}` : ""}</p>` : "";
|
|
924
|
-
let subject;
|
|
925
|
-
let bodyHtml;
|
|
926
|
-
let text;
|
|
927
|
-
if (audience === "sales") {
|
|
928
|
-
subject = `New order #${orderNumber}`;
|
|
929
|
-
const who = customerName || internalCustomerEmail ? `<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;"><strong>Customer:</strong> ${escapeHtml2(customerName || "\u2014")}${internalCustomerEmail ? ` <span style="color:#555;">(${escapeHtml2(internalCustomerEmail)})</span>` : ""}</p>` : "";
|
|
930
|
-
bodyHtml = `<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">A new order has been placed and payment completed.</p>
|
|
931
|
-
<p style="margin:0 0 8px 0;font-size:15px;line-height:1.5;color:#333;"><strong>Order number:</strong> ${escapeHtml2(orderNumber)}</p>
|
|
932
|
-
${who}
|
|
933
|
-
${addrHtml}
|
|
934
|
-
${itemsHtml}
|
|
935
|
-
${subtotalLine}${taxLine}${totalLine}`;
|
|
936
|
-
text = [
|
|
937
|
-
`New order #${orderNumber}`,
|
|
938
|
-
customerName ? `Customer: ${customerName}` : "",
|
|
939
|
-
internalCustomerEmail ? `Email: ${internalCustomerEmail}` : "",
|
|
940
|
-
addrText,
|
|
941
|
-
itemsText,
|
|
942
|
-
subtotal != null ? `Subtotal: ${subtotal}${currency ? ` ${currency}` : ""}` : "",
|
|
943
|
-
tax != null && Number(tax) !== 0 ? `Tax: ${tax}${currency ? ` ${currency}` : ""}` : "",
|
|
944
|
-
total != null ? `Order total: ${total}${currency ? ` ${currency}` : ""}` : ""
|
|
945
|
-
].filter(Boolean).join("\n\n");
|
|
946
|
-
} else {
|
|
947
|
-
subject = `Order confirmed #${orderNumber}`;
|
|
948
|
-
const thanksPlain = customerName && customerName.trim() ? `Thank you, ${customerName.trim()}.` : "Thank you for your order.";
|
|
949
|
-
const thanksHtml = `${escapeHtml2(thanksPlain)} We\u2019ve received your order and will process it shortly.`;
|
|
950
|
-
bodyHtml = `<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">${thanksHtml}</p>
|
|
951
|
-
<p style="margin:0 0 8px 0;font-size:15px;line-height:1.5;color:#333;"><strong>Order number:</strong> ${escapeHtml2(orderNumber)}</p>
|
|
952
|
-
${addrHtml}
|
|
953
|
-
${itemsHtml}
|
|
954
|
-
${subtotalLine}${taxLine}${totalLine}
|
|
955
|
-
${invoiceAttached ? `<p style="margin:16px 0 0 0;font-size:14px;line-height:1.5;color:#333;">Your order invoice is attached as a PDF.</p>` : ""}
|
|
956
|
-
<p style="margin:16px 0 0 0;font-size:13px;line-height:1.5;color:#666;">If you have questions, reply to this email or contact us using the details below.</p>`;
|
|
957
|
-
text = [
|
|
958
|
-
`Order confirmed #${orderNumber}`,
|
|
959
|
-
`${thanksPlain} We\u2019ve received your order and will process it shortly.`,
|
|
960
|
-
"",
|
|
961
|
-
addrText,
|
|
962
|
-
itemsText,
|
|
963
|
-
subtotal != null ? `Subtotal: ${subtotal}${currency ? ` ${currency}` : ""}` : "",
|
|
964
|
-
tax != null && Number(tax) !== 0 ? `Tax: ${tax}${currency ? ` ${currency}` : ""}` : "",
|
|
965
|
-
total != null ? `Order total: ${total}${currency ? ` ${currency}` : ""}` : "",
|
|
966
|
-
invoiceAttached ? "Your order invoice is attached as a PDF." : "",
|
|
967
|
-
"",
|
|
968
|
-
"We will process your order shortly."
|
|
969
|
-
].filter((line, i, arr) => !(line === "" && arr[i - 1] === "")).join("\n");
|
|
970
|
-
}
|
|
971
|
-
const html = renderLayout({
|
|
972
|
-
bodyHtml,
|
|
973
|
-
companyDetails
|
|
974
|
-
});
|
|
975
|
-
return {
|
|
976
|
-
subject,
|
|
977
|
-
html,
|
|
978
|
-
text
|
|
979
|
-
};
|
|
980
|
-
}
|
|
981
|
-
__name(render4, "render");
|
|
982
|
-
|
|
983
|
-
// src/plugins/email/templates/returnInitiated.ts
|
|
984
|
-
function render5(ctx) {
|
|
985
|
-
const { returnId, orderNumber, companyDetails } = ctx;
|
|
986
|
-
const subject = "Return initiated";
|
|
987
|
-
const bodyHtml = `<h2>Return initiated</h2><p>Your return request has been received.</p>${orderNumber ? `<p><strong>Order:</strong> ${escapeHtml4(orderNumber)}</p>` : ""}${returnId ? `<p><strong>Return ID:</strong> ${escapeHtml4(returnId)}</p>` : ""}`;
|
|
988
|
-
const text = `Return initiated.${orderNumber ? ` Order: ${orderNumber}` : ""}${returnId ? ` Return ID: ${returnId}` : ""}`;
|
|
989
|
-
const html = renderLayout({
|
|
990
|
-
bodyHtml,
|
|
991
|
-
companyDetails
|
|
992
|
-
});
|
|
993
|
-
return {
|
|
994
|
-
subject,
|
|
995
|
-
html,
|
|
996
|
-
text
|
|
997
|
-
};
|
|
998
|
-
}
|
|
999
|
-
__name(render5, "render");
|
|
1000
|
-
function escapeHtml4(s) {
|
|
1001
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1002
|
-
}
|
|
1003
|
-
__name(escapeHtml4, "escapeHtml");
|
|
1004
|
-
|
|
1005
|
-
// src/plugins/email/templates/shippingUpdate.ts
|
|
1006
|
-
function render6(ctx) {
|
|
1007
|
-
const { orderNumber, status, trackingUrl, companyDetails } = ctx;
|
|
1008
|
-
const subject = status ? `Shipping update: ${status}` : "Shipping update";
|
|
1009
|
-
const bodyHtml = `<h2>Shipping update</h2>${status ? `<p><strong>Status:</strong> ${escapeHtml5(status)}</p>` : ""}${orderNumber ? `<p><strong>Order:</strong> ${escapeHtml5(orderNumber)}</p>` : ""}${trackingUrl ? `<p><a href="${escapeHtml5(trackingUrl)}">Track your order</a></p>` : ""}`;
|
|
1010
|
-
const text = `Shipping update.${status ? ` Status: ${status}` : ""}${orderNumber ? ` Order: ${orderNumber}` : ""}${trackingUrl ? ` Track: ${trackingUrl}` : ""}`;
|
|
1011
|
-
const html = renderLayout({
|
|
1012
|
-
bodyHtml,
|
|
1013
|
-
companyDetails
|
|
1014
|
-
});
|
|
1015
|
-
return {
|
|
1016
|
-
subject,
|
|
1017
|
-
html,
|
|
1018
|
-
text
|
|
1019
|
-
};
|
|
1020
|
-
}
|
|
1021
|
-
__name(render6, "render");
|
|
1022
|
-
function escapeHtml5(s) {
|
|
1023
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1024
|
-
}
|
|
1025
|
-
__name(escapeHtml5, "escapeHtml");
|
|
1026
|
-
|
|
1027
|
-
// src/plugins/email/templates/invite.ts
|
|
1028
|
-
function render7(ctx) {
|
|
1029
|
-
const { inviteLink, email, inviteeName, companyDetails } = ctx;
|
|
1030
|
-
const subject = "You're invited";
|
|
1031
|
-
const greeting = inviteeName && inviteeName.trim() ? `Hello ${escapeHtml2(inviteeName.trim())},` : "Hello,";
|
|
1032
|
-
const bodyHtml = `<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">${greeting}</p>
|
|
1033
|
-
<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">You have been invited to create your account for <strong>${escapeHtml2(email)}</strong>. Use the secure link below to choose your password and activate access.</p>
|
|
1034
|
-
<p style="margin:0 0 8px 0;font-size:14px;line-height:1.5;color:#555;">This link is personal to you. If you did not expect this invitation, you can ignore this message.</p>
|
|
1035
|
-
${primaryCtaButton(inviteLink, "Accept invitation & set password")}
|
|
1036
|
-
<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>`;
|
|
1037
|
-
const text = [
|
|
1038
|
-
inviteeName?.trim() ? `Hello ${inviteeName.trim()},` : "Hello,",
|
|
1039
|
-
"",
|
|
1040
|
-
`You have been invited to create your account (${email}).`,
|
|
1041
|
-
"Open this link to set your password:",
|
|
1042
|
-
inviteLink,
|
|
1043
|
-
"",
|
|
1044
|
-
"If you did not expect this invitation, you can ignore this email."
|
|
1045
|
-
].join("\n");
|
|
1046
|
-
const html = renderLayout({
|
|
1047
|
-
bodyHtml,
|
|
1048
|
-
companyDetails
|
|
1049
|
-
});
|
|
1050
|
-
return {
|
|
1051
|
-
subject,
|
|
1052
|
-
html,
|
|
1053
|
-
text
|
|
1054
|
-
};
|
|
1055
|
-
}
|
|
1056
|
-
__name(render7, "render");
|
|
1057
|
-
|
|
1058
|
-
// src/plugins/email/templates/formSubmission.ts
|
|
1059
|
-
function render8(ctx) {
|
|
1060
|
-
const { formName, contactName, contactEmail, formData, formFieldRows, companyDetails } = ctx;
|
|
1061
|
-
const subject = `New Form Submission: ${formName}`;
|
|
1062
|
-
const fieldsBlock = formFieldRows && formFieldRows.length > 0 ? formFieldRows.map((r) => `<p style="margin:10px 0 0 0;line-height:1.5;"><strong>${escapeHtml6(r.label)}</strong><br/>${escapeHtml6(r.value)}</p>`).join("") : `<p style="margin:10px 0 0 0;font-size:13px;white-space:pre-wrap;">${escapeHtml6(JSON.stringify(formData, null, 2))}</p>`;
|
|
1063
|
-
const bodyHtml = `<p style="margin:0 0 6px 0;font-size:18px;font-weight:600;color:#111;">New Form Submission</p>
|
|
1064
|
-
<p style="margin:0 0 4px 0;"><strong>Form:</strong> ${escapeHtml6(formName)}</p>
|
|
1065
|
-
<p style="margin:0 0 8px 0;"><strong>Contact:</strong> ${escapeHtml6(contactName)}${contactEmail ? ` (${escapeHtml6(contactEmail)})` : ""}</p>
|
|
1066
|
-
${fieldsBlock}`;
|
|
1067
|
-
const textLines = [
|
|
1068
|
-
"New Form Submission",
|
|
1069
|
-
`Form: ${formName}`,
|
|
1070
|
-
`Contact: ${contactName}${contactEmail ? ` (${contactEmail})` : ""}`,
|
|
1071
|
-
"",
|
|
1072
|
-
...formFieldRows?.length ? formFieldRows.map((r) => `${r.label}: ${r.value}`) : [
|
|
1073
|
-
JSON.stringify(formData, null, 2)
|
|
1074
|
-
]
|
|
1075
|
-
];
|
|
1076
|
-
const text = textLines.join("\n");
|
|
1077
|
-
const html = renderLayout({
|
|
1078
|
-
bodyHtml,
|
|
1079
|
-
companyDetails
|
|
1080
|
-
});
|
|
1081
|
-
return {
|
|
1082
|
-
subject,
|
|
1083
|
-
html,
|
|
1084
|
-
text
|
|
1085
|
-
};
|
|
1086
|
-
}
|
|
1087
|
-
__name(render8, "render");
|
|
1088
|
-
function escapeHtml6(s) {
|
|
1089
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1090
|
-
}
|
|
1091
|
-
__name(escapeHtml6, "escapeHtml");
|
|
1092
|
-
|
|
1093
|
-
// src/plugins/email/templates/otp.ts
|
|
1094
|
-
function render9(ctx) {
|
|
1095
|
-
const { code, companyDetails } = ctx;
|
|
1096
|
-
const name = companyDetails.companyName?.trim() || "Our store";
|
|
1097
|
-
const bodyHtml = `
|
|
1098
|
-
<p>Your verification code is:</p>
|
|
1099
|
-
<p style="font-size:24px;font-weight:bold;letter-spacing:4px;">${escapeHtml7(code)}</p>
|
|
1100
|
-
<p style="color:#666;font-size:14px;">This code expires in 10 minutes. If you did not request it, you can ignore this email.</p>
|
|
1101
|
-
`;
|
|
1102
|
-
const html = renderLayout({
|
|
1103
|
-
bodyHtml,
|
|
1104
|
-
companyDetails
|
|
1105
|
-
});
|
|
1106
|
-
return {
|
|
1107
|
-
subject: `${name}: your sign-in code`,
|
|
1108
|
-
html,
|
|
1109
|
-
text: `Your code is ${code}. It expires in 10 minutes.`
|
|
1110
|
-
};
|
|
1111
|
-
}
|
|
1112
|
-
__name(render9, "render");
|
|
1113
|
-
function escapeHtml7(s) {
|
|
1114
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1115
|
-
}
|
|
1116
|
-
__name(escapeHtml7, "escapeHtml");
|
|
1117
|
-
|
|
1118
|
-
// src/plugins/email/templates/chatLead.ts
|
|
1119
|
-
function render10(ctx) {
|
|
1120
|
-
const { contactName, contactEmail, contactPhone, conversationId, latestMessage, intentCode, intentReason, transcript, companyDetails } = ctx;
|
|
1121
|
-
const intentDisplay = intentCode?.trim() ? `${intentCode.trim()}${intentReason?.trim() ? ` \u2014 ${intentReason.trim()}` : ""}` : intentReason;
|
|
1122
|
-
const subject = `Chat lead${intentCode?.trim() ? ` [${intentCode.trim()}]` : ""}: ${contactName || contactEmail || "Visitor"}`;
|
|
1123
|
-
const transcriptBlock = transcript?.trim() ? `<div style="margin-top:16px;padding:12px;background:#f9fafb;border-radius:6px;border:1px solid #e5e7eb;">
|
|
1124
|
-
<p style="margin:0 0 8px 0;font-size:13px;font-weight:600;color:#374151;">Recent conversation</p>
|
|
1125
|
-
<pre style="margin:0;font-size:12px;white-space:pre-wrap;font-family:inherit;color:#4b5563;">${escapeHtml8(transcript.trim())}</pre>
|
|
1126
|
-
</div>` : "";
|
|
1127
|
-
const bodyHtml = `<p style="margin:0 0 6px 0;font-size:18px;font-weight:600;color:#111;">New chat lead</p>
|
|
1128
|
-
<p style="margin:0 0 12px 0;font-size:14px;color:#374151;">A visitor showed interest based on your intent rules.</p>
|
|
1129
|
-
<table style="width:100%;border-collapse:collapse;font-size:14px;">
|
|
1130
|
-
<tr><td style="padding:4px 8px 4px 0;color:#6b7280;vertical-align:top;">Name</td><td>${escapeHtml8(contactName || "\u2014")}</td></tr>
|
|
1131
|
-
<tr><td style="padding:4px 8px 4px 0;color:#6b7280;vertical-align:top;">Email</td><td><a href="mailto:${escapeHtml8(contactEmail)}">${escapeHtml8(contactEmail)}</a></td></tr>
|
|
1132
|
-
<tr><td style="padding:4px 8px 4px 0;color:#6b7280;vertical-align:top;">Phone</td><td>${escapeHtml8(contactPhone?.trim() || "\u2014")}</td></tr>
|
|
1133
|
-
<tr><td style="padding:4px 8px 4px 0;color:#6b7280;vertical-align:top;">Conversation</td><td>#${conversationId}</td></tr>
|
|
1134
|
-
<tr><td style="padding:4px 8px 4px 0;color:#6b7280;vertical-align:top;">Intent</td><td>${escapeHtml8(intentReason)}</td></tr>
|
|
1135
|
-
</table>
|
|
1136
|
-
<p style="margin:16px 0 6px 0;font-size:13px;font-weight:600;color:#374151;">Latest message</p>
|
|
1137
|
-
<p style="margin:0;font-size:14px;white-space:pre-wrap;">${escapeHtml8(latestMessage)}</p>
|
|
1138
|
-
${transcriptBlock}`;
|
|
1139
|
-
const text = [
|
|
1140
|
-
"New chat lead",
|
|
1141
|
-
`Name: ${contactName || "\u2014"}`,
|
|
1142
|
-
`Email: ${contactEmail}`,
|
|
1143
|
-
`Phone: ${contactPhone?.trim() || "\u2014"}`,
|
|
1144
|
-
`Conversation: #${conversationId}`,
|
|
1145
|
-
`Intent: ${intentDisplay}`,
|
|
1146
|
-
"",
|
|
1147
|
-
"Latest message:",
|
|
1148
|
-
latestMessage,
|
|
1149
|
-
transcript?.trim() ? `
|
|
1150
|
-
Recent conversation:
|
|
1151
|
-
${transcript.trim()}` : ""
|
|
1152
|
-
].join("\n");
|
|
1153
|
-
const html = renderLayout({
|
|
1154
|
-
bodyHtml,
|
|
1155
|
-
companyDetails
|
|
1156
|
-
});
|
|
1157
|
-
return {
|
|
1158
|
-
subject,
|
|
1159
|
-
html,
|
|
1160
|
-
text
|
|
1161
|
-
};
|
|
1162
|
-
}
|
|
1163
|
-
__name(render10, "render");
|
|
1164
|
-
function escapeHtml8(s) {
|
|
1165
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1166
|
-
}
|
|
1167
|
-
__name(escapeHtml8, "escapeHtml");
|
|
1168
|
-
|
|
1169
|
-
// src/plugins/email/templates/vendorOnboarded.ts
|
|
1170
|
-
function render11(ctx) {
|
|
1171
|
-
const { audience, activation, kind = "vendor_onboard", vendorName, vendorSlug, ownerName, ownerEmail, inviteLink, signInLink, companyDetails } = ctx;
|
|
1172
|
-
const storeLabel = vendorName.trim() || "your store";
|
|
1173
|
-
const greeting = ownerName && ownerName.trim() ? `Hello ${escapeHtml2(ownerName.trim())},` : "Hello,";
|
|
1174
|
-
const slugNote = vendorSlug && vendorSlug.trim() ? `<p style="margin:0 0 8px 0;font-size:14px;line-height:1.5;color:#555;"><strong>Store URL slug:</strong> ${escapeHtml2(vendorSlug.trim())}</p>` : "";
|
|
1175
|
-
let subject;
|
|
1176
|
-
let bodyHtml;
|
|
1177
|
-
let text;
|
|
1178
|
-
if (audience === "internal") {
|
|
1179
|
-
const action = kind === "team_invite" ? "A team member was invited to a vendor store." : activation === "invite" ? "A new vendor store was created and the owner was invited." : "A new vendor store was created with an active owner account.";
|
|
1180
|
-
subject = kind === "team_invite" ? `Team invite: ${storeLabel}` : `New vendor: ${storeLabel}`;
|
|
1181
|
-
bodyHtml = `<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">${action}</p>
|
|
1182
|
-
<p style="margin:0 0 8px 0;font-size:15px;line-height:1.5;color:#333;"><strong>Store:</strong> ${escapeHtml2(storeLabel)}</p>
|
|
1183
|
-
${slugNote}
|
|
1184
|
-
<p style="margin:0 0 8px 0;font-size:15px;line-height:1.5;color:#333;"><strong>Owner / invitee:</strong> ${escapeHtml2(ownerName?.trim() || "\u2014")} <span style="color:#555;">(${escapeHtml2(ownerEmail)})</span></p>
|
|
1185
|
-
<p style="margin:0 0 0 0;font-size:14px;line-height:1.5;color:#555;"><strong>Activation:</strong> ${escapeHtml2(activation === "invite" ? "Invitation" : "Password set")}</p>`;
|
|
1186
|
-
text = [
|
|
1187
|
-
subject,
|
|
1188
|
-
"",
|
|
1189
|
-
action,
|
|
1190
|
-
`Store: ${storeLabel}`,
|
|
1191
|
-
vendorSlug ? `Slug: ${vendorSlug}` : "",
|
|
1192
|
-
`Owner / invitee: ${ownerName?.trim() || "\u2014"} (${ownerEmail})`,
|
|
1193
|
-
`Activation: ${activation === "invite" ? "Invitation" : "Password set"}`
|
|
1194
|
-
].filter(Boolean).join("\n");
|
|
1195
|
-
} else if (kind === "team_invite" && activation === "membership" && inviteLink) {
|
|
1196
|
-
subject = `You're invited to ${storeLabel}`;
|
|
1197
|
-
bodyHtml = `<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">${greeting}</p>
|
|
1198
|
-
<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>
|
|
1199
|
-
${primaryCtaButton(inviteLink, "Accept invitation")}
|
|
1200
|
-
<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>`;
|
|
1201
|
-
text = [
|
|
1202
|
-
ownerName?.trim() ? `Hello ${ownerName.trim()},` : "Hello,",
|
|
1203
|
-
"",
|
|
1204
|
-
`You have been invited to join ${storeLabel}.`,
|
|
1205
|
-
"Open this link to accept (no new password required):",
|
|
1206
|
-
inviteLink
|
|
1207
|
-
].join("\n");
|
|
1208
|
-
} else if (kind === "team_invite" && activation === "invite" && inviteLink) {
|
|
1209
|
-
subject = `You're invited to ${storeLabel}`;
|
|
1210
|
-
bodyHtml = `<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">${greeting}</p>
|
|
1211
|
-
<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>. Use the secure link below to set your password and activate your account.</p>
|
|
1212
|
-
${primaryCtaButton(inviteLink, "Accept invitation & set password")}
|
|
1213
|
-
<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>`;
|
|
1214
|
-
text = [
|
|
1215
|
-
ownerName?.trim() ? `Hello ${ownerName.trim()},` : "Hello,",
|
|
1216
|
-
"",
|
|
1217
|
-
`You have been invited to join ${storeLabel}.`,
|
|
1218
|
-
"Open this link to set your password:",
|
|
1219
|
-
inviteLink
|
|
1220
|
-
].join("\n");
|
|
1221
|
-
} else if (activation === "invite" && inviteLink) {
|
|
1222
|
-
subject = `Your vendor store "${storeLabel}" is ready`;
|
|
1223
|
-
bodyHtml = `<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">${greeting}</p>
|
|
1224
|
-
<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">Your vendor store <strong>${escapeHtml2(storeLabel)}</strong> has been created. Accept the invitation below to set your password and access the admin dashboard.</p>
|
|
1225
|
-
${slugNote}
|
|
1226
|
-
${primaryCtaButton(inviteLink, "Accept invitation & set password")}
|
|
1227
|
-
<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>`;
|
|
1228
|
-
text = [
|
|
1229
|
-
ownerName?.trim() ? `Hello ${ownerName.trim()},` : "Hello,",
|
|
1230
|
-
"",
|
|
1231
|
-
`Your vendor store "${storeLabel}" has been created.`,
|
|
1232
|
-
"Open this link to set your password:",
|
|
1233
|
-
inviteLink
|
|
1234
|
-
].join("\n");
|
|
1235
|
-
} else {
|
|
1236
|
-
subject = `Welcome to ${storeLabel}`;
|
|
1237
|
-
const loginUrl = signInLink?.trim() || inviteLink?.trim() || "";
|
|
1238
|
-
bodyHtml = `<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">${greeting}</p>
|
|
1239
|
-
<p style="margin:0 0 12px 0;font-size:15px;line-height:1.5;color:#333;">Your vendor store <strong>${escapeHtml2(storeLabel)}</strong> has been created and your owner account is ready. Sign in to the admin dashboard to manage your store.</p>
|
|
1240
|
-
${slugNote}
|
|
1241
|
-
${loginUrl ? primaryCtaButton(loginUrl, "Sign in to admin") : ""}
|
|
1242
|
-
${loginUrl ? `<p style="margin:16px 0 0 0;font-size:12px;line-height:1.5;color:#888;">Sign-in URL:<br/><span style="word-break:break-all;">${escapeHtml2(loginUrl)}</span></p>` : ""}`;
|
|
1243
|
-
text = [
|
|
1244
|
-
ownerName?.trim() ? `Hello ${ownerName.trim()},` : "Hello,",
|
|
1245
|
-
"",
|
|
1246
|
-
`Your vendor store "${storeLabel}" has been created.`,
|
|
1247
|
-
loginUrl ? `Sign in: ${loginUrl}` : ""
|
|
1248
|
-
].filter(Boolean).join("\n");
|
|
1249
|
-
}
|
|
1250
|
-
const html = renderLayout({
|
|
1251
|
-
bodyHtml,
|
|
1252
|
-
companyDetails
|
|
1253
|
-
});
|
|
1254
|
-
return {
|
|
1255
|
-
subject,
|
|
1256
|
-
html,
|
|
1257
|
-
text
|
|
1258
|
-
};
|
|
1259
|
-
}
|
|
1260
|
-
__name(render11, "render");
|
|
1261
|
-
|
|
1262
|
-
// src/plugins/email/templates/index.ts
|
|
1263
|
-
var templateRenderMap = {
|
|
1264
|
-
signup: render,
|
|
1265
|
-
passwordReset: render2,
|
|
1266
|
-
passwordChange: render3,
|
|
1267
|
-
orderPlaced: render4,
|
|
1268
|
-
returnInitiated: render5,
|
|
1269
|
-
shippingUpdate: render6,
|
|
1270
|
-
invite: render7,
|
|
1271
|
-
formSubmission: render8,
|
|
1272
|
-
otp: render9,
|
|
1273
|
-
chatLead: render10,
|
|
1274
|
-
vendorOnboarded: render11
|
|
1275
|
-
};
|
|
1276
|
-
function getTemplateRenderer(name) {
|
|
1277
|
-
return templateRenderMap[name];
|
|
1278
|
-
}
|
|
1279
|
-
__name(getTemplateRenderer, "getTemplateRenderer");
|
|
1280
|
-
|
|
1281
|
-
// src/plugins/email/renderer.ts
|
|
1282
|
-
function renderEmail(templateName, ctx, options) {
|
|
1283
|
-
const layoutFn = options?.renderLayout ?? renderLayout;
|
|
1284
|
-
const getBody = options?.getBody;
|
|
1285
|
-
if (getBody) {
|
|
1286
|
-
const custom = getBody(templateName, ctx);
|
|
1287
|
-
if (custom != null) {
|
|
1288
|
-
const html = layoutFn({
|
|
1289
|
-
bodyHtml: custom.bodyHtml,
|
|
1290
|
-
companyDetails: ctx.companyDetails
|
|
1291
|
-
});
|
|
1292
|
-
return {
|
|
1293
|
-
subject: custom.subject,
|
|
1294
|
-
html,
|
|
1295
|
-
text: custom.text
|
|
1296
|
-
};
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
const render12 = getTemplateRenderer(templateName);
|
|
1300
|
-
if (!render12) {
|
|
1301
|
-
throw new Error(`Unknown email template: ${templateName}`);
|
|
1302
|
-
}
|
|
1303
|
-
return render12(ctx);
|
|
1304
|
-
}
|
|
1305
|
-
__name(renderEmail, "renderEmail");
|
|
1306
|
-
|
|
1307
|
-
// src/plugins/email/email-service.ts
|
|
1308
|
-
var require2 = createRequire(import.meta.url);
|
|
1309
|
-
var MailComposer = require2("nodemailer/lib/mail-composer");
|
|
1310
|
-
var SES_REGIONS_WITHOUT_SMTP = /* @__PURE__ */ new Set([
|
|
1311
|
-
"af-south-1",
|
|
1312
|
-
"ap-south-2",
|
|
1313
|
-
"ap-southeast-3",
|
|
1314
|
-
"ap-southeast-5",
|
|
1315
|
-
"ca-west-1",
|
|
1316
|
-
"eu-central-2",
|
|
1317
|
-
"eu-south-1",
|
|
1318
|
-
"il-central-1",
|
|
1319
|
-
"me-central-1",
|
|
1320
|
-
"me-south-1"
|
|
1321
|
-
]);
|
|
1322
|
-
function sesSmtpHost(region) {
|
|
1323
|
-
return `email-smtp.${region}.amazonaws.com`;
|
|
1324
|
-
}
|
|
1325
|
-
__name(sesSmtpHost, "sesSmtpHost");
|
|
1326
|
-
function sesRegionSupportsSmtp(region) {
|
|
1327
|
-
return !SES_REGIONS_WITHOUT_SMTP.has(region.trim());
|
|
1328
|
-
}
|
|
1329
|
-
__name(sesRegionSupportsSmtp, "sesRegionSupportsSmtp");
|
|
1330
|
-
function toBuffer(content) {
|
|
1331
|
-
return Buffer.isBuffer(content) ? content : Buffer.from(content);
|
|
1332
|
-
}
|
|
1333
|
-
__name(toBuffer, "toBuffer");
|
|
1334
|
-
var EmailService = class {
|
|
1335
|
-
static {
|
|
1336
|
-
__name(this, "EmailService");
|
|
1337
|
-
}
|
|
1338
|
-
config;
|
|
1339
|
-
templateOptions;
|
|
1340
|
-
sesClient;
|
|
1341
|
-
transporter;
|
|
1342
|
-
/** AWS via SMTP (nodemailer) vs SES API SDK. */
|
|
1343
|
-
awsViaSmtp = false;
|
|
1344
|
-
constructor(config) {
|
|
1345
|
-
this.config = config;
|
|
1346
|
-
this.templateOptions = config.templateOptions;
|
|
1347
|
-
if (config.type === "AWS") {
|
|
1348
|
-
if (!config.region) {
|
|
1349
|
-
throw new Error("AWS SES configuration incomplete: AWS_REGION is required");
|
|
1350
|
-
}
|
|
1351
|
-
if (config.user && config.password) {
|
|
1352
|
-
this.awsViaSmtp = true;
|
|
1353
|
-
const host = config.host || sesSmtpHost(config.region);
|
|
1354
|
-
const port = config.port ?? 587;
|
|
1355
|
-
const secure = config.secure ?? false;
|
|
1356
|
-
this.transporter = nodemailer.createTransport({
|
|
1357
|
-
host,
|
|
1358
|
-
port,
|
|
1359
|
-
secure,
|
|
1360
|
-
auth: {
|
|
1361
|
-
user: config.user,
|
|
1362
|
-
pass: config.password
|
|
1363
|
-
}
|
|
1364
|
-
});
|
|
1365
|
-
} else if (config.accessKeyId && config.secretAccessKey) {
|
|
1366
|
-
this.sesClient = new SESClient({
|
|
1367
|
-
region: config.region,
|
|
1368
|
-
credentials: {
|
|
1369
|
-
accessKeyId: config.accessKeyId,
|
|
1370
|
-
secretAccessKey: config.secretAccessKey
|
|
1371
|
-
}
|
|
1372
|
-
});
|
|
1373
|
-
} else {
|
|
1374
|
-
throw new Error("AWS SES configuration incomplete: set SMTP_USER and SMTP_PASSWORD (SES SMTP) or AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY (SES API)");
|
|
1375
|
-
}
|
|
1376
|
-
} else if (config.type === "SMTP" || config.type === "GMAIL") {
|
|
1377
|
-
if (!config.user || !config.password) throw new Error("SMTP configuration incomplete");
|
|
1378
|
-
const host = config.type === "GMAIL" ? "smtp.gmail.com" : config.host;
|
|
1379
|
-
if (!host) {
|
|
1380
|
-
throw new Error("SMTP configuration incomplete: SMTP_HOST is required when SMTP_TYPE=SMTP (nodemailer otherwise connects to 127.0.0.1)");
|
|
1381
|
-
}
|
|
1382
|
-
const port = config.port ?? 587;
|
|
1383
|
-
const secure = config.secure ?? false;
|
|
1384
|
-
this.transporter = nodemailer.createTransport({
|
|
1385
|
-
host,
|
|
1386
|
-
port,
|
|
1387
|
-
secure,
|
|
1388
|
-
auth: {
|
|
1389
|
-
user: config.user,
|
|
1390
|
-
pass: config.password
|
|
1391
|
-
}
|
|
1392
|
-
});
|
|
1393
|
-
} else {
|
|
1394
|
-
throw new Error(`Unsupported email type: ${config.type}`);
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
nodemailerAttachments(attachments) {
|
|
1398
|
-
if (!attachments?.length) return void 0;
|
|
1399
|
-
return attachments.map((a) => ({
|
|
1400
|
-
filename: a.filename,
|
|
1401
|
-
content: toBuffer(a.content),
|
|
1402
|
-
contentType: a.contentType || "application/octet-stream"
|
|
1403
|
-
}));
|
|
1404
|
-
}
|
|
1405
|
-
async send(emailData) {
|
|
1406
|
-
try {
|
|
1407
|
-
const from = emailData.from || this.config.from;
|
|
1408
|
-
const to = emailData.to || this.config.to;
|
|
1409
|
-
const attachments = this.nodemailerAttachments(emailData.attachments);
|
|
1410
|
-
if (this.config.type === "AWS" && this.sesClient) {
|
|
1411
|
-
if (attachments?.length) {
|
|
1412
|
-
const composer = new MailComposer({
|
|
1413
|
-
from,
|
|
1414
|
-
to,
|
|
1415
|
-
subject: emailData.subject,
|
|
1416
|
-
html: emailData.html,
|
|
1417
|
-
text: emailData.text,
|
|
1418
|
-
attachments
|
|
1419
|
-
});
|
|
1420
|
-
const raw = await new Promise((resolve, reject) => {
|
|
1421
|
-
composer.compile().build((err, message) => {
|
|
1422
|
-
if (err) reject(err);
|
|
1423
|
-
else resolve(message);
|
|
1424
|
-
});
|
|
1425
|
-
});
|
|
1426
|
-
await this.sesClient.send(new SendRawEmailCommand({
|
|
1427
|
-
RawMessage: {
|
|
1428
|
-
Data: raw
|
|
1429
|
-
}
|
|
1430
|
-
}));
|
|
1431
|
-
return true;
|
|
1432
|
-
}
|
|
1433
|
-
await this.sesClient.send(new SendEmailCommand({
|
|
1434
|
-
Source: from,
|
|
1435
|
-
Destination: {
|
|
1436
|
-
ToAddresses: [
|
|
1437
|
-
to
|
|
1438
|
-
]
|
|
1439
|
-
},
|
|
1440
|
-
Message: {
|
|
1441
|
-
Subject: {
|
|
1442
|
-
Data: emailData.subject,
|
|
1443
|
-
Charset: "UTF-8"
|
|
1444
|
-
},
|
|
1445
|
-
Body: {
|
|
1446
|
-
Html: {
|
|
1447
|
-
Data: emailData.html,
|
|
1448
|
-
Charset: "UTF-8"
|
|
1449
|
-
},
|
|
1450
|
-
...emailData.text && {
|
|
1451
|
-
Text: {
|
|
1452
|
-
Data: emailData.text,
|
|
1453
|
-
Charset: "UTF-8"
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
}));
|
|
1459
|
-
return true;
|
|
1460
|
-
}
|
|
1461
|
-
if (this.transporter && (this.config.type === "SMTP" || this.config.type === "GMAIL" || this.config.type === "AWS" && this.awsViaSmtp)) {
|
|
1462
|
-
await this.transporter.sendMail({
|
|
1463
|
-
from,
|
|
1464
|
-
to,
|
|
1465
|
-
subject: emailData.subject,
|
|
1466
|
-
html: emailData.html,
|
|
1467
|
-
text: emailData.text,
|
|
1468
|
-
attachments
|
|
1469
|
-
});
|
|
1470
|
-
return true;
|
|
1471
|
-
}
|
|
1472
|
-
return false;
|
|
1473
|
-
} catch (error) {
|
|
1474
|
-
console.error("Email sending failed:", error);
|
|
1475
|
-
return false;
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1478
|
-
renderTemplate(templateName, ctx) {
|
|
1479
|
-
return renderEmail(templateName, ctx, this.templateOptions);
|
|
1480
|
-
}
|
|
1481
|
-
/** Default notification recipient from plugin config (SMTP_TO / plugin `to`). */
|
|
1482
|
-
getDefaultTo() {
|
|
1483
|
-
return this.config.to;
|
|
1484
|
-
}
|
|
1485
|
-
};
|
|
1486
|
-
var emailTemplates = {
|
|
1487
|
-
formSubmission: /* @__PURE__ */ __name((data) => ({
|
|
1488
|
-
subject: `New Form Submission: ${data.formName}`,
|
|
1489
|
-
html: `<h2>New Form Submission</h2><p><strong>Form:</strong> ${data.formName}</p><p><strong>Contact:</strong> ${data.contactName} (${data.contactEmail})</p><pre>${JSON.stringify(data.formData, null, 2)}</pre>`,
|
|
1490
|
-
text: `New Form Submission
|
|
1491
|
-
Form: ${data.formName}
|
|
1492
|
-
Contact: ${data.contactName} (${data.contactEmail})
|
|
1493
|
-
${JSON.stringify(data.formData, null, 2)}`
|
|
1494
|
-
}), "formSubmission"),
|
|
1495
|
-
contactSubmission: /* @__PURE__ */ __name((data) => ({
|
|
1496
|
-
subject: `New Contact Form Submission from ${data.name}`,
|
|
1497
|
-
html: `<h2>New Contact Form Submission</h2><p><strong>Name:</strong> ${data.name}</p><p><strong>Email:</strong> ${data.email}</p>${data.phone ? `<p><strong>Phone:</strong> ${data.phone}</p>` : ""}${data.message ? `<p><strong>Message:</strong></p><p>${data.message}</p>` : ""}`,
|
|
1498
|
-
text: `New Contact Form Submission
|
|
1499
|
-
Name: ${data.name}
|
|
1500
|
-
Email: ${data.email}
|
|
1501
|
-
${data.phone ? `Phone: ${data.phone}
|
|
1502
|
-
` : ""}${data.message ? `Message: ${data.message}` : ""}`
|
|
1503
|
-
}), "contactSubmission"),
|
|
1504
|
-
passwordReset: /* @__PURE__ */ __name((data) => ({
|
|
1505
|
-
subject: "Reset your password",
|
|
1506
|
-
html: `<h2>Reset your password</h2><p>Click the link below to set a new password. This link expires in 1 hour.</p><p><a href="${data.resetLink}">${data.resetLink}</a></p>`,
|
|
1507
|
-
text: `Reset your password: ${data.resetLink}
|
|
1508
|
-
|
|
1509
|
-
This link expires in 1 hour.`
|
|
1510
|
-
}), "passwordReset")
|
|
1511
|
-
};
|
|
1512
|
-
|
|
1513
552
|
// src/lib/email-recipients.ts
|
|
1514
553
|
function parseEmailRecipientsFromConfig(raw) {
|
|
1515
554
|
if (raw == null || raw === "") return [];
|
|
@@ -2211,14 +1250,108 @@ async function validateFcmCredentials(dataSource, entityMap) {
|
|
|
2211
1250
|
}
|
|
2212
1251
|
__name(validateFcmCredentials, "validateFcmCredentials");
|
|
2213
1252
|
|
|
1253
|
+
// src/lib/order-notification-defaults.ts
|
|
1254
|
+
var ORDER_NOTIFICATION_DEFAULTS = {
|
|
1255
|
+
email: {
|
|
1256
|
+
pending: {
|
|
1257
|
+
subject: "Your Order #{{orderNumber}} is Pending",
|
|
1258
|
+
body: "<p>Thank you for your order! It is currently pending.</p>"
|
|
1259
|
+
},
|
|
1260
|
+
order_pending: {
|
|
1261
|
+
subject: "Your Order #{{orderNumber}} is Pending",
|
|
1262
|
+
body: "<p>Thank you for your order! It is currently pending.</p>"
|
|
1263
|
+
},
|
|
1264
|
+
confirmed: {
|
|
1265
|
+
subject: "Your Order #{{orderNumber}} is Confirmed",
|
|
1266
|
+
body: "<p>Your order has been confirmed!</p>"
|
|
1267
|
+
},
|
|
1268
|
+
order_confirmed: {
|
|
1269
|
+
subject: "Your Order #{{orderNumber}} is Confirmed",
|
|
1270
|
+
body: "<p>Your order has been confirmed!</p>"
|
|
1271
|
+
},
|
|
1272
|
+
order_placed: {
|
|
1273
|
+
subject: "Your Order #{{orderNumber}} is Placed",
|
|
1274
|
+
body: "<p>Thank you for your order! Your order #{{orderNumber}} has been placed.</p>"
|
|
1275
|
+
},
|
|
1276
|
+
processing: {
|
|
1277
|
+
subject: "Your Order #{{orderNumber}} is Processing",
|
|
1278
|
+
body: "<p>Your order is now being processed by our team.</p>"
|
|
1279
|
+
},
|
|
1280
|
+
order_processing: {
|
|
1281
|
+
subject: "Your Order #{{orderNumber}} is Processing",
|
|
1282
|
+
body: "<p>Your order is now being processed by our team.</p>"
|
|
1283
|
+
},
|
|
1284
|
+
completed: {
|
|
1285
|
+
subject: "Your Order #{{orderNumber}} is Completed",
|
|
1286
|
+
body: "<p>Your order has been completed. Thank you!</p>"
|
|
1287
|
+
},
|
|
1288
|
+
order_completed: {
|
|
1289
|
+
subject: "Your Order #{{orderNumber}} is Completed",
|
|
1290
|
+
body: "<p>Your order has been completed. Thank you!</p>"
|
|
1291
|
+
},
|
|
1292
|
+
cancelled: {
|
|
1293
|
+
subject: "Your Order #{{orderNumber}} is Cancelled",
|
|
1294
|
+
body: "<p>Your order has been cancelled.</p>"
|
|
1295
|
+
},
|
|
1296
|
+
order_cancelled: {
|
|
1297
|
+
subject: "Your Order #{{orderNumber}} is Cancelled",
|
|
1298
|
+
body: "<p>Your order has been cancelled.</p>"
|
|
1299
|
+
},
|
|
1300
|
+
payment_success: {
|
|
1301
|
+
subject: "Payment Received for Order #{{orderNumber}}",
|
|
1302
|
+
body: "<p>Payment of {{currency}} {{total}} has been successfully received for order #{{orderNumber}}.</p>"
|
|
1303
|
+
},
|
|
1304
|
+
payment_failed: {
|
|
1305
|
+
subject: "Payment Failed for Order #{{orderNumber}}",
|
|
1306
|
+
body: "<p>Payment for order #{{orderNumber}} could not be completed.</p>"
|
|
1307
|
+
},
|
|
1308
|
+
payment_refund_initiated: {
|
|
1309
|
+
subject: "Refund Initiated for Order #{{orderNumber}}",
|
|
1310
|
+
body: "<p>A refund of {{currency}} {{refundAmount}} has been initiated for order #{{orderNumber}}.</p>"
|
|
1311
|
+
}
|
|
1312
|
+
},
|
|
1313
|
+
sms: {
|
|
1314
|
+
pending: "Your Order #{{orderNumber}} is pending. Total: {{total}}.",
|
|
1315
|
+
order_pending: "Your Order #{{orderNumber}} is pending. Total: {{total}}.",
|
|
1316
|
+
confirmed: "Your Order #{{orderNumber}} is confirmed. Total: {{total}}.",
|
|
1317
|
+
order_confirmed: "Your Order #{{orderNumber}} is confirmed. Total: {{total}}.",
|
|
1318
|
+
order_placed: "Your Order #{{orderNumber}} is placed. Total: {{total}}.",
|
|
1319
|
+
processing: "Your Order #{{orderNumber}} is now processing.",
|
|
1320
|
+
order_processing: "Your Order #{{orderNumber}} is now processing.",
|
|
1321
|
+
completed: "Your Order #{{orderNumber}} has been completed. Thank you!",
|
|
1322
|
+
order_completed: "Your Order #{{orderNumber}} has been completed. Thank you!",
|
|
1323
|
+
cancelled: "Your Order #{{orderNumber}} is cancelled.",
|
|
1324
|
+
order_cancelled: "Your Order #{{orderNumber}} is cancelled.",
|
|
1325
|
+
payment_success: "Payment received for Order #{{orderNumber}}. Amount: {{currency}} {{total}}.",
|
|
1326
|
+
payment_failed: "Payment failed for Order #{{orderNumber}}.",
|
|
1327
|
+
payment_refund_initiated: "Refund of {{currency}} {{refundAmount}} initiated for Order #{{orderNumber}}."
|
|
1328
|
+
},
|
|
1329
|
+
whatsapp: {
|
|
1330
|
+
pending: "Hi {{customerName}}, your Order #{{orderNumber}} is pending.",
|
|
1331
|
+
order_pending: "Hi {{customerName}}, your Order #{{orderNumber}} is pending.",
|
|
1332
|
+
confirmed: "Hi {{customerName}}, your Order #{{orderNumber}} is confirmed.",
|
|
1333
|
+
order_confirmed: "Hi {{customerName}}, your Order #{{orderNumber}} is confirmed.",
|
|
1334
|
+
order_placed: "Hi {{customerName}}, your Order #{{orderNumber}} is placed.",
|
|
1335
|
+
processing: "Hi {{customerName}}, your Order #{{orderNumber}} is now being processed.",
|
|
1336
|
+
order_processing: "Hi {{customerName}}, your Order #{{orderNumber}} is now being processed.",
|
|
1337
|
+
completed: "Hi {{customerName}}, your Order #{{orderNumber}} is completed.",
|
|
1338
|
+
order_completed: "Hi {{customerName}}, your Order #{{orderNumber}} is completed.",
|
|
1339
|
+
cancelled: "Hi {{customerName}}, your Order #{{orderNumber}} is cancelled.",
|
|
1340
|
+
order_cancelled: "Hi {{customerName}}, your Order #{{orderNumber}} is cancelled.",
|
|
1341
|
+
payment_success: "Hi {{customerName}}, payment for Order #{{orderNumber}} was successful.",
|
|
1342
|
+
payment_failed: "Hi {{customerName}}, payment for Order #{{orderNumber}} has failed.",
|
|
1343
|
+
payment_refund_initiated: "Hi {{customerName}}, a refund has been initiated for Order #{{orderNumber}}."
|
|
1344
|
+
}
|
|
1345
|
+
};
|
|
1346
|
+
|
|
2214
1347
|
// src/lib/order-notification-dispatcher.ts
|
|
2215
1348
|
var initialized = false;
|
|
2216
|
-
function
|
|
1349
|
+
function maskPhone2(raw) {
|
|
2217
1350
|
const digits = String(raw ?? "").replace(/\D/g, "");
|
|
2218
1351
|
if (digits.length <= 4) return "(none)";
|
|
2219
1352
|
return `***${digits.slice(-4)}`;
|
|
2220
1353
|
}
|
|
2221
|
-
__name(
|
|
1354
|
+
__name(maskPhone2, "maskPhone");
|
|
2222
1355
|
async function getSettingsGroup(dataSource, entityMap, group) {
|
|
2223
1356
|
if (!entityMap.configs) return {};
|
|
2224
1357
|
const rows = await dataSource.getRepository(entityMap.configs).find({
|
|
@@ -2247,28 +1380,20 @@ async function isWhatsappNotificationsEnabled(dataSource, entityMap) {
|
|
|
2247
1380
|
__name(isWhatsappNotificationsEnabled, "isWhatsappNotificationsEnabled");
|
|
2248
1381
|
async function isTriggerCatalogEnabled(triggerKey, dataSource, entityMap) {
|
|
2249
1382
|
if (!entityMap.order_notification_triggers) {
|
|
2250
|
-
|
|
2251
|
-
return false;
|
|
1383
|
+
return true;
|
|
2252
1384
|
}
|
|
2253
1385
|
try {
|
|
2254
1386
|
const row = await dataSource.getRepository(entityMap.order_notification_triggers).findOne({
|
|
2255
1387
|
where: {
|
|
2256
|
-
triggerKey
|
|
2257
|
-
enabled: true
|
|
1388
|
+
triggerKey
|
|
2258
1389
|
}
|
|
2259
1390
|
});
|
|
2260
1391
|
if (!row) {
|
|
2261
|
-
|
|
2262
|
-
triggerKey
|
|
2263
|
-
});
|
|
1392
|
+
return true;
|
|
2264
1393
|
}
|
|
2265
|
-
return
|
|
1394
|
+
return row.enabled !== false;
|
|
2266
1395
|
} catch (err) {
|
|
2267
|
-
|
|
2268
|
-
triggerKey,
|
|
2269
|
-
err
|
|
2270
|
-
});
|
|
2271
|
-
return false;
|
|
1396
|
+
return true;
|
|
2272
1397
|
}
|
|
2273
1398
|
}
|
|
2274
1399
|
__name(isTriggerCatalogEnabled, "isTriggerCatalogEnabled");
|
|
@@ -2296,8 +1421,24 @@ async function loadBoundTemplate(triggerKey, channel, audienceType = "customers"
|
|
|
2296
1421
|
}
|
|
2297
1422
|
}).catch(() => null);
|
|
2298
1423
|
}
|
|
2299
|
-
if (!binding) {
|
|
2300
|
-
|
|
1424
|
+
if (!binding || binding.messageTemplateId == null) {
|
|
1425
|
+
const defChannel = ORDER_NOTIFICATION_DEFAULTS[channel];
|
|
1426
|
+
const def = defChannel ? defChannel[triggerKey] : null;
|
|
1427
|
+
if (def) {
|
|
1428
|
+
if (typeof def === "string") {
|
|
1429
|
+
return {
|
|
1430
|
+
body: def,
|
|
1431
|
+
externalTemplateRef: null,
|
|
1432
|
+
subject: "Order Notification"
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
return {
|
|
1436
|
+
body: def.body,
|
|
1437
|
+
subject: def.subject,
|
|
1438
|
+
externalTemplateRef: null
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
console.warn("[order-notifications] no order_notification_bindings row or default template", {
|
|
2301
1442
|
triggerKey,
|
|
2302
1443
|
channel,
|
|
2303
1444
|
audienceType
|
|
@@ -2305,14 +1446,6 @@ async function loadBoundTemplate(triggerKey, channel, audienceType = "customers"
|
|
|
2305
1446
|
return null;
|
|
2306
1447
|
}
|
|
2307
1448
|
const templateId = binding.messageTemplateId;
|
|
2308
|
-
if (templateId == null) {
|
|
2309
|
-
console.warn("[order-notifications] binding exists but no template selected", {
|
|
2310
|
-
triggerKey,
|
|
2311
|
-
channel,
|
|
2312
|
-
audienceType
|
|
2313
|
-
});
|
|
2314
|
-
return null;
|
|
2315
|
-
}
|
|
2316
1449
|
const template = await dataSource.getRepository(entityMap.message_templates).findOne({
|
|
2317
1450
|
where: {
|
|
2318
1451
|
id: templateId,
|
|
@@ -2322,6 +1455,22 @@ async function loadBoundTemplate(triggerKey, channel, audienceType = "customers"
|
|
|
2322
1455
|
}
|
|
2323
1456
|
}).catch(() => null);
|
|
2324
1457
|
if (!template) {
|
|
1458
|
+
const defChannel = ORDER_NOTIFICATION_DEFAULTS[channel];
|
|
1459
|
+
const def = defChannel ? defChannel[triggerKey] : null;
|
|
1460
|
+
if (def) {
|
|
1461
|
+
if (typeof def === "string") {
|
|
1462
|
+
return {
|
|
1463
|
+
body: def,
|
|
1464
|
+
externalTemplateRef: null,
|
|
1465
|
+
subject: "Order Notification"
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
return {
|
|
1469
|
+
body: def.body,
|
|
1470
|
+
subject: def.subject,
|
|
1471
|
+
externalTemplateRef: null
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
2325
1474
|
console.warn("[order-notifications] bound template not found or disabled", {
|
|
2326
1475
|
triggerKey,
|
|
2327
1476
|
channel,
|
|
@@ -2332,6 +1481,22 @@ async function loadBoundTemplate(triggerKey, channel, audienceType = "customers"
|
|
|
2332
1481
|
}
|
|
2333
1482
|
return template;
|
|
2334
1483
|
} catch (err) {
|
|
1484
|
+
const defChannel = ORDER_NOTIFICATION_DEFAULTS[channel];
|
|
1485
|
+
const def = defChannel ? defChannel[triggerKey] : null;
|
|
1486
|
+
if (def) {
|
|
1487
|
+
if (typeof def === "string") {
|
|
1488
|
+
return {
|
|
1489
|
+
body: def,
|
|
1490
|
+
externalTemplateRef: null,
|
|
1491
|
+
subject: "Order Notification"
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
return {
|
|
1495
|
+
body: def.body,
|
|
1496
|
+
subject: def.subject,
|
|
1497
|
+
externalTemplateRef: null
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
2335
1500
|
console.warn("[order-notifications] loadBoundTemplate query failed", {
|
|
2336
1501
|
triggerKey,
|
|
2337
1502
|
channel,
|
|
@@ -2674,72 +1839,17 @@ async function sendWhatsAppForTrigger(triggerKey, orderId, phone, variables, dat
|
|
|
2674
1839
|
console.log("[order-notifications] WhatsApp queued/sent", {
|
|
2675
1840
|
triggerKey,
|
|
2676
1841
|
orderId,
|
|
2677
|
-
phone:
|
|
1842
|
+
phone: maskPhone2(phone)
|
|
2678
1843
|
});
|
|
2679
1844
|
}
|
|
2680
1845
|
}
|
|
2681
1846
|
__name(sendWhatsAppForTrigger, "sendWhatsAppForTrigger");
|
|
2682
|
-
async function isOrderPaymentConfirmed(orderId, dataSource, entityMap) {
|
|
2683
|
-
if (!entityMap.orders) return true;
|
|
2684
|
-
try {
|
|
2685
|
-
const orderRepo = dataSource.getRepository(entityMap.orders);
|
|
2686
|
-
const order = await orderRepo.findOne({
|
|
2687
|
-
where: {
|
|
2688
|
-
id: orderId
|
|
2689
|
-
}
|
|
2690
|
-
});
|
|
2691
|
-
if (!order) return false;
|
|
2692
|
-
const o = order;
|
|
2693
|
-
const status = String(o.status ?? "").toLowerCase().trim();
|
|
2694
|
-
const total = Number(o.total ?? 0);
|
|
2695
|
-
if (total <= 0) return true;
|
|
2696
|
-
const confirmedStatuses = [
|
|
2697
|
-
"confirmed",
|
|
2698
|
-
"completed",
|
|
2699
|
-
"processing",
|
|
2700
|
-
"paid",
|
|
2701
|
-
"fulfilled",
|
|
2702
|
-
"shipped"
|
|
2703
|
-
];
|
|
2704
|
-
if (confirmedStatuses.includes(status)) {
|
|
2705
|
-
return true;
|
|
2706
|
-
}
|
|
2707
|
-
if (entityMap.payments) {
|
|
2708
|
-
const paymentRepo = dataSource.getRepository(entityMap.payments);
|
|
2709
|
-
const payment = await paymentRepo.findOne({
|
|
2710
|
-
where: {
|
|
2711
|
-
orderId
|
|
2712
|
-
}
|
|
2713
|
-
});
|
|
2714
|
-
if (payment) {
|
|
2715
|
-
const pStatus = String(payment.status ?? "").toLowerCase().trim();
|
|
2716
|
-
if (pStatus === "paid" || pStatus === "captured" || pStatus === "completed") {
|
|
2717
|
-
return true;
|
|
2718
|
-
}
|
|
2719
|
-
}
|
|
2720
|
-
}
|
|
2721
|
-
return false;
|
|
2722
|
-
} catch (err) {
|
|
2723
|
-
console.error("[order-notifications] failed to check order payment confirmation", err);
|
|
2724
|
-
return false;
|
|
2725
|
-
}
|
|
2726
|
-
}
|
|
2727
|
-
__name(isOrderPaymentConfirmed, "isOrderPaymentConfirmed");
|
|
2728
1847
|
async function handleTrigger(triggerKey, payload, dataSource, entityMap, getCms) {
|
|
2729
1848
|
console.log("[order-notifications] received", {
|
|
2730
1849
|
triggerKey,
|
|
2731
1850
|
orderId: payload.orderId,
|
|
2732
|
-
phone:
|
|
1851
|
+
phone: maskPhone2(payload.customerPhone)
|
|
2733
1852
|
});
|
|
2734
|
-
if (triggerKey === "order_placed") {
|
|
2735
|
-
const isConfirmed = await isOrderPaymentConfirmed(payload.orderId, dataSource, entityMap);
|
|
2736
|
-
if (!isConfirmed) {
|
|
2737
|
-
console.log("[order-notifications] skip order_placed: order payment not confirmed yet", {
|
|
2738
|
-
orderId: payload.orderId
|
|
2739
|
-
});
|
|
2740
|
-
return;
|
|
2741
|
-
}
|
|
2742
|
-
}
|
|
2743
1853
|
const [emailChannelEnabled, whatsappChannelEnabled, pushChannelEnabled] = await Promise.all([
|
|
2744
1854
|
isEmailNotificationsEnabled(dataSource, entityMap),
|
|
2745
1855
|
isWhatsappNotificationsEnabled(dataSource, entityMap),
|
|
@@ -2816,7 +1926,9 @@ async function handleTrigger(triggerKey, payload, dataSource, entityMap, getCms)
|
|
|
2816
1926
|
}
|
|
2817
1927
|
}
|
|
2818
1928
|
__name(handleTrigger, "handleTrigger");
|
|
1929
|
+
var globalGetCms = null;
|
|
2819
1930
|
function initWhatsappTriggerDispatcher(deps) {
|
|
1931
|
+
if (deps?.getCms) globalGetCms = deps.getCms;
|
|
2820
1932
|
if (initialized) return;
|
|
2821
1933
|
initialized = true;
|
|
2822
1934
|
const { dataSource, entityMap, getCms } = deps;
|
|
@@ -2830,6 +1942,13 @@ function initWhatsappTriggerDispatcher(deps) {
|
|
|
2830
1942
|
console.log("[order-notifications] dispatcher initialized (email + WhatsApp, unified via order_notification_bindings)");
|
|
2831
1943
|
}
|
|
2832
1944
|
__name(initWhatsappTriggerDispatcher, "initWhatsappTriggerDispatcher");
|
|
1945
|
+
async function dispatchOrderNotificationDirectly(triggerKey, payload, dataSource, entityMap, getCms) {
|
|
1946
|
+
const cmsResolver = getCms ?? globalGetCms ?? (async () => ({
|
|
1947
|
+
getPlugin: /* @__PURE__ */ __name(() => null, "getPlugin")
|
|
1948
|
+
}));
|
|
1949
|
+
await handleTrigger(triggerKey, payload, dataSource, entityMap, cmsResolver);
|
|
1950
|
+
}
|
|
1951
|
+
__name(dispatchOrderNotificationDirectly, "dispatchOrderNotificationDirectly");
|
|
2833
1952
|
async function resendOrderNotification(triggerKey, orderId, deps) {
|
|
2834
1953
|
if (!deps.entityMap.orders) return {
|
|
2835
1954
|
ok: false,
|
|
@@ -2880,5 +1999,26 @@ async function resendOrderNotification(triggerKey, orderId, deps) {
|
|
|
2880
1999
|
};
|
|
2881
2000
|
}
|
|
2882
2001
|
__name(resendOrderNotification, "resendOrderNotification");
|
|
2002
|
+
async function emitOrderNotificationTrigger(dataSource, entityMap, triggerKey, payload) {
|
|
2003
|
+
try {
|
|
2004
|
+
const settings = await getSettingsGroup(dataSource, entityMap, "event_notifications");
|
|
2005
|
+
const enabledChannels = [];
|
|
2006
|
+
if (settings.email_enabled !== "false" && settings.email_enabled !== "0" && settings.email_customer_enabled !== "false") enabledChannels.push("email");
|
|
2007
|
+
if (settings.whatsapp_enabled !== "false" && settings.whatsapp_enabled !== "0") enabledChannels.push("whatsapp");
|
|
2008
|
+
if (settings.sms_enabled !== "false" && settings.sms_enabled !== "0") enabledChannels.push("sms");
|
|
2009
|
+
if (settings.push_enabled !== "false" && settings.push_enabled !== "0") enabledChannels.push("push");
|
|
2010
|
+
notificationTriggerEmitter.emitTrigger(triggerKey, payload);
|
|
2011
|
+
return {
|
|
2012
|
+
ok: true,
|
|
2013
|
+
channels: enabledChannels
|
|
2014
|
+
};
|
|
2015
|
+
} catch (err) {
|
|
2016
|
+
return {
|
|
2017
|
+
ok: false,
|
|
2018
|
+
channels: []
|
|
2019
|
+
};
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
__name(emitOrderNotificationTrigger, "emitOrderNotificationTrigger");
|
|
2883
2023
|
|
|
2884
|
-
export { CHAT_EMAIL_LOG,
|
|
2024
|
+
export { CHAT_EMAIL_LOG, WhatsAppService, buildChatbotSystemPromptWithEmailTool, buildEventOrderTemplateVariables, buildTranscriptForLeadEmail, detectChatLeadIntent, dispatchOrderNotificationDirectly, emailPlugin, emitOrderNotificationTrigger, getSettingsGroup, initWhatsappTriggerDispatcher, isWhatsAppPluginEnabled, joinRecipientsForSend, parseEmailRecipientsFromConfig, parseIntentRecipientEmails, resendOrderNotification, resolveChatEmailToolSettings, sendChatLeadEmail, sendFcmPushNotification, serializeEmailRecipients, validateFcmCredentials };
|