Add email delivery for license keys (SMTP)
This commit is contained in:
+107
@@ -0,0 +1,107 @@
|
||||
import crypto from 'crypto';
|
||||
import nodemailer from 'nodemailer';
|
||||
|
||||
const LICENSE_SECRET = process.env.DASHCADDY_LICENSE_SECRET || '';
|
||||
|
||||
const ALPHABET = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // Crockford-ish (no I, O, 0, 1)
|
||||
|
||||
/**
|
||||
* Generate a DashCaddy license code in the format `DC-XXXX-XXXX-XXXX-XXXX`.
|
||||
* Deterministic for the same (licenseKey, durationDays) tuple — if a customer
|
||||
* pays again, regenerate the code with the same key + new duration, and the
|
||||
* code stays the same. That's what "the same key gets more time" means.
|
||||
*/
|
||||
export function generateLicenseCode(licenseKey, durationDays) {
|
||||
if (!LICENSE_SECRET) {
|
||||
throw new Error('DASHCADDY_LICENSE_SECRET not set');
|
||||
}
|
||||
const seed = crypto
|
||||
.createHmac('sha256', LICENSE_SECRET)
|
||||
.update(`${licenseKey}|${durationDays}`)
|
||||
.digest();
|
||||
const segments = [];
|
||||
for (let i = 0; i < seed.length && segments.length < 4; i += 2) {
|
||||
const b1 = seed[i];
|
||||
const b2 = seed[i + 1] || 0;
|
||||
segments.push(
|
||||
ALPHABET[b1 % 32] +
|
||||
ALPHABET[Math.floor(b1 / 32) % 32] +
|
||||
ALPHABET[b2 % 32] +
|
||||
ALPHABET[Math.floor(b2 / 32) % 32]
|
||||
);
|
||||
}
|
||||
return 'DC-' + segments.join('-');
|
||||
}
|
||||
|
||||
let transporter = null;
|
||||
function getTransporter() {
|
||||
if (transporter) return transporter;
|
||||
transporter = nodemailer.createTransport({
|
||||
host: process.env.SMTP_HOST || '127.0.0.1',
|
||||
port: parseInt(process.env.SMTP_PORT || '25', 10),
|
||||
secure: process.env.SMTP_SECURE === 'true',
|
||||
auth: process.env.SMTP_USERNAME ? {
|
||||
user: process.env.SMTP_USERNAME,
|
||||
pass: process.env.SMTP_PASSWORD
|
||||
} : undefined,
|
||||
tls: { rejectUnauthorized: false }
|
||||
});
|
||||
return transporter;
|
||||
}
|
||||
|
||||
function formatDate(iso) {
|
||||
if (!iso) return 'N/A';
|
||||
return new Date(iso).toUTCString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the license key email to the customer. Called after every payment
|
||||
* (initial purchase or renewal extension).
|
||||
*/
|
||||
export async function sendLicenseEmail({ to, code, durationDays, planCode, expiresAt, extended = false }) {
|
||||
const subject = extended
|
||||
? `Your DashCaddy Premium license has been extended (${durationDays} days added)`
|
||||
: `Your DashCaddy Premium license key`;
|
||||
const text = [
|
||||
extended ? 'Your DashCaddy Premium license has been extended.' : 'Thank you for purchasing DashCaddy Premium.',
|
||||
'',
|
||||
`License key: ${code}`,
|
||||
`Plan: ${planCode}`,
|
||||
`License valid until: ${formatDate(expiresAt)}`,
|
||||
'',
|
||||
'To activate: paste this license key into your DashCaddy dashboard at Admin → License.',
|
||||
'',
|
||||
'Need help? Reply to this email or visit https://dashcaddy.net/about'
|
||||
].join('\n');
|
||||
const html = `
|
||||
<div style="font-family: system-ui, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
|
||||
<h2 style="color: #0f172a;">${extended ? 'License extended' : 'Welcome to DashCaddy Premium'}</h2>
|
||||
<p>${extended
|
||||
? 'Your DashCaddy Premium license has been extended. Same key, more time.'
|
||||
: 'Thank you for purchasing DashCaddy Premium. Your license key is below.'}</p>
|
||||
<div style="background: #f1f5f9; padding: 16px; border-radius: 8px; margin: 20px 0; font-family: monospace; font-size: 16px; text-align: center; letter-spacing: 2px;">
|
||||
${code}
|
||||
</div>
|
||||
<p><strong>Plan:</strong> ${planCode}</p>
|
||||
<p><strong>License valid until:</strong> ${formatDate(expiresAt)}</p>
|
||||
<p>To activate: paste this license key into your DashCaddy dashboard at <strong>Admin → License</strong>.</p>
|
||||
<hr style="margin-top: 30px; border: none; border-top: 1px solid #e2e8f0;" />
|
||||
<p style="color: #64748b; font-size: 14px;">Need help? Reply to this email or visit <a href="https://dashcaddy.net/about">dashcaddy.net/about</a></p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
try {
|
||||
const t = getTransporter();
|
||||
await t.sendMail({
|
||||
from: process.env.SMTP_FROM || 'licenses@dashcaddy.net',
|
||||
to,
|
||||
subject,
|
||||
text,
|
||||
html
|
||||
});
|
||||
return { delivered: true, via: 'smtp' };
|
||||
} catch (err) {
|
||||
console.error('Email delivery failed:', err.message);
|
||||
return { delivered: false, error: err.message };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user