240 lines
10 KiB
JavaScript
240 lines
10 KiB
JavaScript
const express = require('express');
|
||
const crypto = require('crypto');
|
||
const router = express.Router();
|
||
const dns = require('dns').promises;
|
||
const db = require('../config/database');
|
||
const mailer = require('../config/mailer');
|
||
|
||
// ============================================
|
||
// E-Mail Validierung via DNS MX-Record
|
||
// ============================================
|
||
async function verifyEmailDomain(email) {
|
||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||
if (!emailRegex.test(email)) return { valid: false, reason: 'Ungültiges E-Mail-Format' };
|
||
const domain = email.split('@')[1];
|
||
try {
|
||
const records = await dns.resolveMx(domain);
|
||
if (records && records.length > 0) return { valid: true };
|
||
return { valid: false, reason: 'Domain hat keine E-Mail-Server (MX-Records fehlen)' };
|
||
} catch (err) {
|
||
return { valid: false, reason: 'E-Mail-Domain konnte nicht verifiziert werden' };
|
||
}
|
||
}
|
||
|
||
// ============================================
|
||
// IBAN Validierung (Modulo-97)
|
||
// ============================================
|
||
const IBAN_LENGTHS = {
|
||
AL:28,AD:24,AT:20,AZ:28,BH:22,BE:16,BA:20,BR:29,BG:22,CR:22,HR:21,
|
||
CY:28,CZ:24,DK:18,DO:28,EE:20,FO:18,FI:18,FR:27,GE:22,DE:22,GI:23,
|
||
GL:18,GT:28,HU:28,IS:26,IE:22,IL:23,IT:27,JO:30,KZ:20,KW:30,LV:21,
|
||
LB:28,LI:21,LT:20,LU:20,MK:19,MT:31,MR:27,MU:30,MC:27,MD:24,ME:22,
|
||
NL:18,NO:15,PK:24,PS:29,PL:28,PT:25,QA:29,RO:24,SM:27,SA:24,RS:22,
|
||
SK:24,SI:19,ES:24,SE:24,CH:21,TN:24,TR:26,AE:23,GB:22,VG:24
|
||
};
|
||
function validateIBANServer(iban) {
|
||
if (!iban) return { valid: true };
|
||
const clean = iban.replace(/\s/g, '').toUpperCase();
|
||
if (clean.length < 5) return { valid: false, error: 'IBAN zu kurz' };
|
||
const country = clean.substring(0, 2);
|
||
if (!/^[A-Z]{2}$/.test(country)) return { valid: false, error: 'Ungültiger Ländercode' };
|
||
const expectedLen = IBAN_LENGTHS[country];
|
||
if (!expectedLen) return { valid: false, error: 'Ländercode nicht unterstützt' };
|
||
if (clean.length !== expectedLen) return { valid: false, error: country + '-IBAN muss ' + expectedLen + ' Zeichen haben' };
|
||
const rearranged = clean.substring(4) + clean.substring(0, 4);
|
||
const numeric = rearranged.split('').map(c => {
|
||
const code = c.charCodeAt(0);
|
||
return code >= 65 ? (code - 55).toString() : c;
|
||
}).join('');
|
||
let remainder = 0;
|
||
for (let i = 0; i < numeric.length; i++) {
|
||
remainder = (remainder * 10 + parseInt(numeric[i])) % 97;
|
||
}
|
||
if (remainder !== 1) return { valid: false, error: 'IBAN Prüfziffer ungültig' };
|
||
return { valid: true };
|
||
}
|
||
|
||
// ============================================
|
||
// Bestätigungs-E-Mail Template
|
||
// ============================================
|
||
function confirmationEmailHtml(member, confirmLink) {
|
||
return `<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head><meta charset="UTF-8"></head>
|
||
<body style="font-family:Outfit,Arial,sans-serif;background:#f8f9ff;margin:0;padding:20px">
|
||
<div style="max-width:600px;margin:0 auto;background:white;border-radius:16px;overflow:hidden;box-shadow:0 2px 16px rgba(0,0,0,0.08)">
|
||
<div style="background:#2d2dcc;padding:32px;text-align:center">
|
||
<h1 style="color:white;margin:0;font-size:1.8rem">Plusfit<span style="color:#a5b4fc">24</span></h1>
|
||
<p style="color:#c7d2fe;margin:8px 0 0">Mitgliedschaft bestätigen</p>
|
||
</div>
|
||
<div style="padding:32px">
|
||
<p>Hallo ${member.first_name} ${member.last_name},</p>
|
||
<p>vielen Dank für deine Anmeldung bei PlusFit24! Um deine Mitgliedschaft zu aktivieren, bestätige bitte deine E-Mail-Adresse:</p>
|
||
|
||
<div style="text-align:center;margin:32px 0">
|
||
<a href="${confirmLink}"
|
||
style="display:inline-block;background:#2d2dcc;color:white;padding:16px 40px;border-radius:12px;text-decoration:none;font-weight:700;font-size:1.05rem">
|
||
✅ Mitgliedschaft jetzt bestätigen
|
||
</a>
|
||
</div>
|
||
|
||
<div style="background:#f8f9ff;border-radius:10px;padding:16px;margin-bottom:20px">
|
||
<p style="margin:0 0 8px;font-weight:700">Deine Vertragsdaten:</p>
|
||
<p style="margin:4px 0;color:#374151">📋 Tarif: ${member.tariff_name}</p>
|
||
<p style="margin:4px 0;color:#374151">💰 Monatsbeitrag: ${Number(member.agreed_price).toFixed(2).replace('.', ',')} €</p>
|
||
<p style="margin:4px 0;color:#374151">📦 Startpaket: ${Number(member.start_package_price).toFixed(2).replace('.', ',')} €</p>
|
||
</div>
|
||
|
||
<p style="color:#6b7280;font-size:0.85rem">
|
||
Dieser Link ist <strong>24 Stunden</strong> gültig. Falls du diese Anmeldung nicht durchgeführt hast, ignoriere diese E-Mail.<br><br>
|
||
<strong>PlusFit24 UG</strong> · Moosleiten 12 · 84089 Aiglsbach
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</body></html>`;
|
||
}
|
||
|
||
// ============================================
|
||
// POST /api/verify-email
|
||
// ============================================
|
||
router.post('/verify-email', async (req, res) => {
|
||
const { email } = req.body;
|
||
if (!email) return res.json({ valid: false, reason: 'Keine E-Mail angegeben' });
|
||
const result = await verifyEmailDomain(email);
|
||
res.json(result);
|
||
});
|
||
|
||
// ============================================
|
||
// POST /api/submit-membership
|
||
// ============================================
|
||
router.post('/submit-membership', async (req, res) => {
|
||
try {
|
||
const {
|
||
tariff_id, salutation, title, first_name, last_name, birth_date,
|
||
email, phone, street, address_addition, zip, city,
|
||
bank_name, account_holder, iban,
|
||
sepa_accepted, agb_accepted, datenschutz_accepted, data_correct,
|
||
guardian_consent
|
||
} = req.body;
|
||
|
||
// Pflichtfelder
|
||
if (!tariff_id || !first_name || !last_name || !birth_date || !email || !street || !zip || !city) {
|
||
return res.json({ success: false, error: 'Bitte alle Pflichtfelder ausfüllen.' });
|
||
}
|
||
if (!agb_accepted || !datenschutz_accepted || !data_correct) {
|
||
return res.json({ success: false, error: 'Bitte alle Einverständniserklärungen bestätigen.' });
|
||
}
|
||
|
||
// E-Mail validieren
|
||
const emailCheck = await verifyEmailDomain(email);
|
||
if (!emailCheck.valid) {
|
||
return res.json({ success: false, error: 'E-Mail-Adresse ist nicht erreichbar: ' + emailCheck.reason });
|
||
}
|
||
|
||
// IBAN prüfen
|
||
if (iban && iban.trim()) {
|
||
const ibanCheck = validateIBANServer(iban.trim());
|
||
if (!ibanCheck.valid) {
|
||
return res.json({ success: false, error: 'IBAN ungültig: ' + ibanCheck.error });
|
||
}
|
||
}
|
||
|
||
// Alter berechnen
|
||
const birthDateObj = new Date(birth_date);
|
||
const today = new Date();
|
||
let age = today.getFullYear() - birthDateObj.getFullYear();
|
||
const mo = today.getMonth() - birthDateObj.getMonth();
|
||
if (mo < 0 || (mo === 0 && today.getDate() < birthDateObj.getDate())) age--;
|
||
const is_minor = age < 18 ? 1 : 0;
|
||
|
||
if (is_minor && !guardian_consent) {
|
||
return res.json({ success: false, error: 'Bei Minderjährigen ist die Einverständniserklärung der Erziehungsberechtigten erforderlich.' });
|
||
}
|
||
if (age < 14) {
|
||
return res.json({ success: false, error: 'Das Mindestalter für eine Mitgliedschaft beträgt 14 Jahre.' });
|
||
}
|
||
|
||
// Tarif laden
|
||
const [tariffs] = await db.query('SELECT * FROM tariffs WHERE id = ? AND active = 1', [tariff_id]);
|
||
if (tariffs.length === 0) {
|
||
return res.json({ success: false, error: 'Ungültiger oder inaktiver Tarif.' });
|
||
}
|
||
const tariff = tariffs[0];
|
||
|
||
// Berechnungen
|
||
const startPackagePrice = parseFloat(tariff.start_package_price) || 35.00;
|
||
const contractStart = new Date(today);
|
||
contractStart.setDate(contractStart.getDate() + 15);
|
||
const daysInMonth = new Date(contractStart.getFullYear(), contractStart.getMonth() + 1, 0).getDate();
|
||
const remainingDays = daysInMonth - contractStart.getDate() + 1;
|
||
const partialMonth = Math.round((parseFloat(tariff.price_monthly) / daysInMonth) * remainingDays * 100) / 100;
|
||
const firstPaymentAmt = Math.round((partialMonth + startPackagePrice) * 100) / 100;
|
||
const contractEnd = new Date(contractStart);
|
||
contractEnd.setMonth(contractEnd.getMonth() + tariff.duration_months);
|
||
contractEnd.setDate(contractEnd.getDate() - 1);
|
||
|
||
// Tokens generieren
|
||
const access_token = crypto.randomBytes(16).toString('hex');
|
||
const confirmation_token = crypto.randomBytes(32).toString('hex');
|
||
|
||
// In DB speichern — Status: pending
|
||
await db.query(`
|
||
INSERT INTO memberships
|
||
(tariff_id, salutation, title, first_name, last_name, birth_date,
|
||
email, phone, street, address_addition, zip, city,
|
||
bank_name, account_holder, iban,
|
||
sepa_accepted, agb_accepted, datenschutz_accepted, data_correct,
|
||
guardian_consent, is_minor,
|
||
access_token, confirmation_token,
|
||
agreed_price, agreed_duration,
|
||
start_package_price,
|
||
signup_date, contract_start, contract_end, effective_end,
|
||
first_payment_date, first_payment_amt,
|
||
status, reviewed)
|
||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||
`, [
|
||
tariff_id, salutation, title || '', first_name, last_name, birth_date,
|
||
email, phone || '', street, address_addition || '', zip, city,
|
||
bank_name || '', account_holder || '', iban || '',
|
||
sepa_accepted ? 1 : 0, agb_accepted ? 1 : 0,
|
||
datenschutz_accepted ? 1 : 0, data_correct ? 1 : 0,
|
||
guardian_consent ? 1 : 0, is_minor,
|
||
access_token, confirmation_token,
|
||
tariff.price_monthly, tariff.duration_months,
|
||
startPackagePrice,
|
||
today.toISOString().split('T')[0],
|
||
contractStart.toISOString().split('T')[0],
|
||
contractEnd.toISOString().split('T')[0],
|
||
contractEnd.toISOString().split('T')[0],
|
||
contractStart.toISOString().split('T')[0],
|
||
firstPaymentAmt,
|
||
'pending', 0
|
||
]);
|
||
|
||
// Bestätigungs-E-Mail senden
|
||
const baseUrl = process.env.APP_URL || 'https://plusfit24.software-joksch.com';
|
||
const confirmLink = `${baseUrl}/confirm/${confirmation_token}`;
|
||
const memberData = { first_name, last_name, agreed_price: tariff.price_monthly, start_package_price: startPackagePrice, tariff_name: tariff.name };
|
||
|
||
try {
|
||
await mailer.sendMail({
|
||
from: process.env.MAIL_FROM,
|
||
to: email,
|
||
subject: 'PlusFit24 – Bitte bestätige deine Mitgliedschaft',
|
||
html: confirmationEmailHtml(memberData, confirmLink)
|
||
});
|
||
} catch (mailErr) {
|
||
console.error('E-Mail Fehler:', mailErr.message);
|
||
// Trotzdem Erfolg zurückgeben — Admin kann manuell bestätigen
|
||
}
|
||
|
||
res.json({ success: true, pending: true });
|
||
|
||
} catch (err) {
|
||
console.error('Submit error:', err);
|
||
res.json({ success: false, error: 'Serverfehler. Bitte versuche es später erneut.' });
|
||
}
|
||
});
|
||
|
||
module.exports = router;
|