Spaces:
Running
Running
File size: 7,234 Bytes
6e13570 4c6b1e8 6e13570 4c6b1e8 6e13570 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | const nodemailer = require("nodemailer");
const { env } = require("../config/env");
const logger = require("./logger");
let transporter = null;
function getTransporter() {
if (transporter) return transporter;
transporter = nodemailer.createTransport({
host: env.smtp.host,
port: env.smtp.port,
secure: env.smtp.secure, // true for 465, false for other ports (STARTTLS)
auth: env.smtp.user ? { user: env.smtp.user, pass: env.smtp.pass } : undefined,
requireTLS: !env.smtp.secure, // enforce STARTTLS when not already on a TLS port
});
return transporter;
}
async function sendMail({ to, subject, html, text }) {
try {
const info = await getTransporter().sendMail({
from: env.emailFrom,
to,
subject,
html,
text,
});
return info;
} catch (err) {
// Email failures must never crash a request or leak SMTP credentials.
logger.error("Failed to send email", { subject, error: err.message });
return null;
}
}
function baseLayout(bodyHtml) {
return `
<div style="font-family:Arial,Helvetica,sans-serif;max-width:600px;margin:0 auto;color:#1a1a1a;">
<div style="padding:24px 0;border-bottom:2px solid #eee;">
<h2 style="margin:0;color:#111;">Aynwerd Academy</h2>
</div>
<div style="padding:24px 0;">${bodyHtml}</div>
<div style="padding:16px 0;border-top:1px solid #eee;font-size:12px;color:#777;">
This is an automated message regarding your order. Never share your password or
full payment reference by email. If you didn't make this request, contact
support immediately.
</div>
</div>`;
}
async function sendOrderPlacedEmail({ to, name, order, courseTitle, bankDetails, payoneerDetails }) {
const methodBlock =
order.paymentMethod === "bank_wire"
? `<h3>Bank wire transfer instructions</h3>
<table style="border-collapse:collapse;width:100%;font-size:14px;">
<tr><td style="padding:4px 0;color:#555;">Bank name</td><td><strong>${bankDetails.name}</strong></td></tr>
<tr><td style="padding:4px 0;color:#555;">Account name</td><td><strong>${bankDetails.accountName}</strong></td></tr>
<tr><td style="padding:4px 0;color:#555;">Account number</td><td><strong>${bankDetails.accountNumber}</strong></td></tr>
<tr><td style="padding:4px 0;color:#555;">Routing number</td><td><strong>${bankDetails.routingNumber}</strong></td></tr>
<tr><td style="padding:4px 0;color:#555;">SWIFT/BIC</td><td><strong>${bankDetails.swiftBic}</strong></td></tr>
<tr><td style="padding:4px 0;color:#555;">Bank address</td><td><strong>${bankDetails.address}</strong></td></tr>
</table>
<p>Please use order number <strong>${order.orderNumber}</strong> as your payment reference.</p>`
: `<h3>Payoneer payment instructions</h3>
<p>Send payment to Payoneer account <strong>${payoneerDetails.accountEmail}</strong>
or use your Payoneer app's "Request a Payment" link: <a href="${payoneerDetails.paymentUrl}">${payoneerDetails.paymentUrl}</a>.</p>
<p>Please use order number <strong>${order.orderNumber}</strong> as your payment note.</p>`;
const html = baseLayout(`
<p>Hi ${name},</p>
<p>Thanks for enrolling in <strong>${courseTitle}</strong>. Your order has been created and is
awaiting payment${order.status === "awaiting_confirmation" ? " confirmation" : ""}.</p>
<p><strong>Order number:</strong> ${order.orderNumber}<br/>
<strong>Amount due:</strong> ${order.currency} ${order.amount.toFixed(2)}<br/>
<strong>Payment method:</strong> ${order.paymentMethod === "bank_wire" ? "Bank wire transfer" : "Payoneer"}</p>
${methodBlock}
<p>Once our team confirms your payment, you'll receive a separate email and full course access
will be unlocked automatically on your account.</p>
`);
return sendMail({
to,
subject: `Your Aynwerd order ${order.orderNumber} β payment instructions`,
html,
text: `Order ${order.orderNumber} created for ${courseTitle}. Amount due: ${order.currency} ${order.amount.toFixed(2)}.`,
});
}
async function sendPaymentConfirmedEmail({ to, name, order, courseTitle }) {
const html = baseLayout(`
<p>Hi ${name},</p>
<p>Great news β we've confirmed your payment for <strong>${courseTitle}</strong>.</p>
<p><strong>Order number:</strong> ${order.orderNumber}<br/>
<strong>Amount paid:</strong> ${order.currency} ${order.amount.toFixed(2)}<br/>
<strong>Confirmed on:</strong> ${new Date(order.confirmedAt).toUTCString()}</p>
<p>Full access to the course has been unlocked on your account. Log in any time to get started.</p>
`);
return sendMail({
to,
subject: `Payment confirmed β order ${order.orderNumber}`,
html,
text: `Payment confirmed for order ${order.orderNumber}. Course access unlocked.`,
});
}
async function sendVoucherRedeemedEmail({ to, name, order, courseTitle, voucherCode }) {
const html = baseLayout(`
<p>Hi ${name},</p>
<p>Your voucher <strong>${voucherCode}</strong> has been redeemed for <strong>${courseTitle}</strong> β
no payment was required.</p>
<p><strong>Order number:</strong> ${order.orderNumber}</p>
<p>Full access to the course has been unlocked on your account. Log in any time to get started.</p>
`);
return sendMail({
to,
subject: `Voucher redeemed β ${courseTitle} unlocked`,
html,
text: `Voucher ${voucherCode} redeemed for ${courseTitle}. Course access unlocked. Order ${order.orderNumber}.`,
});
}
async function sendPaymentRejectedEmail({ to, name, order, courseTitle, reason }) {
const html = baseLayout(`
<p>Hi ${name},</p>
<p>We were unable to confirm your payment for <strong>${courseTitle}</strong> (order
<strong>${order.orderNumber}</strong>).</p>
${reason ? `<p><strong>Reason:</strong> ${reason}</p>` : ""}
<p>Please reply to this email or contact support so we can help resolve this.</p>
`);
return sendMail({
to,
subject: `Action needed on order ${order.orderNumber}`,
html,
text: `We could not confirm payment for order ${order.orderNumber}.${reason ? ` Reason: ${reason}` : ""}`,
});
}
async function sendAdminNewOrderAlert({ order, courseTitle, customerEmail }) {
if (!env.adminNotifyEmail) return null;
const html = baseLayout(`
<p>New order awaiting confirmation.</p>
<p><strong>Order:</strong> ${order.orderNumber}<br/>
<strong>Customer:</strong> ${customerEmail}<br/>
<strong>Course:</strong> ${courseTitle}<br/>
<strong>Amount:</strong> ${order.currency} ${order.amount.toFixed(2)}<br/>
<strong>Method:</strong> ${order.paymentMethod}</p>
<p>Review and confirm it from the admin dashboard.</p>
`);
return sendMail({
to: env.adminNotifyEmail,
subject: `New order ${order.orderNumber} awaiting confirmation`,
html,
});
}
module.exports = {
sendOrderPlacedEmail,
sendPaymentConfirmedEmail,
sendVoucherRedeemedEmail,
sendPaymentRejectedEmail,
sendAdminNewOrderAlert,
};
|