Vertragsverwaltung_Plusfit24/routes/api.js
2026-03-27 14:44:03 +00:00

152 lines
5.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const express = require('express');
const crypto = require('crypto');
const router = express.Router();
const dns = require('dns').promises;
const db = require('../config/database');
// Email Validierung via DNS MX-Record Check
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' };
}
}
// Server-seitige IBAN Prüfung (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 }; // Optional kein IBAN = ok
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 };
}
// 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;
// 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 (falls angegeben)
if (iban && iban.trim()) {
const ibanCheck = validateIBANServer(iban.trim());
if (!ibanCheck.valid) {
return res.json({ success: false, error: 'IBAN ungültig: ' + ibanCheck.error });
}
}
// Pflichtfelder prüfen
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.' });
}
// Alter berechnen
const birthDateObj = new Date(birth_date);
const today = new Date();
let age = today.getFullYear() - birthDateObj.getFullYear();
const m = today.getMonth() - birthDateObj.getMonth();
if (m < 0 || (m === 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 prüfen
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.' });
}
// Zugangstoken generieren
const access_token = crypto.randomBytes(16).toString('hex');
// In DB speichern
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,
agreed_price, agreed_duration)
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,
tariffs[0].price_monthly, tariffs[0].duration_months
]);
res.json({ success: true });
} catch (err) {
console.error('Submit error:', err);
res.json({ success: false, error: 'Serverfehler. Bitte versuche es später erneut.' });
}
});
module.exports = router;