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