IBAN Überprüfung eingefügt
This commit is contained in:
parent
ad5f30920b
commit
c5d08abbd8
@ -1006,3 +1006,24 @@ body {
|
||||
|
||||
/* Hover auf Mitglieder-Tabellenzeilen */
|
||||
.member-row:hover td { background: #f0f0ff !important; }
|
||||
|
||||
/* ---- IBAN Validierung ---- */
|
||||
.iban-message {
|
||||
font-size: 0.8rem;
|
||||
margin-top: 4px;
|
||||
padding-left: 4px;
|
||||
min-height: 18px;
|
||||
}
|
||||
.iban-message.success { color: var(--success); }
|
||||
.iban-message.error { color: var(--error); }
|
||||
|
||||
.input-wrap input.input-valid,
|
||||
.karte-input.input-valid {
|
||||
border-color: var(--success) !important;
|
||||
background: #f0fdf4 !important;
|
||||
}
|
||||
.input-wrap input.input-error,
|
||||
.karte-input.input-error {
|
||||
border-color: var(--error) !important;
|
||||
background: #fff5f5 !important;
|
||||
}
|
||||
|
||||
83
public/js/iban.js
Normal file
83
public/js/iban.js
Normal file
@ -0,0 +1,83 @@
|
||||
/**
|
||||
* PlusFit24 – IBAN Validierung
|
||||
* Prüft: Format, Ländercode, Länge, Prüfziffer (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 formatIBAN(value) {
|
||||
const clean = value.replace(/[^A-Z0-9]/gi, '').toUpperCase();
|
||||
return clean.replace(/(.{4})/g, '$1 ').trim();
|
||||
}
|
||||
|
||||
function validateIBAN(iban) {
|
||||
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 checkDigits = clean.substring(2, 4);
|
||||
if (!/^\d{2}$/.test(checkDigits)) return { valid: false, error: 'Prüfziffern ungültig' };
|
||||
|
||||
const expectedLength = IBAN_LENGTHS[country];
|
||||
if (!expectedLength) return { valid: false, error: 'Ländercode "' + country + '" nicht unterstützt' };
|
||||
if (clean.length !== expectedLength) {
|
||||
return { valid: false, error: country + '-IBAN muss ' + expectedLength + ' Zeichen haben (aktuell: ' + clean.length + ')' };
|
||||
}
|
||||
|
||||
if (!/^[A-Z0-9]+$/.test(clean.substring(4))) {
|
||||
return { valid: false, error: 'IBAN enthält ungültige Zeichen' };
|
||||
}
|
||||
|
||||
// Modulo-97 Prüfung
|
||||
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: 'Prüfziffer falsch – IBAN ungültig' };
|
||||
|
||||
return { valid: true, formatted: formatIBAN(clean) };
|
||||
}
|
||||
|
||||
function attachIBANValidation(inputEl, statusEl, messageEl) {
|
||||
inputEl.addEventListener('input', function () {
|
||||
const rawClean = this.value.replace(/[^A-Z0-9]/gi, '').toUpperCase();
|
||||
this.value = formatIBAN(rawClean);
|
||||
|
||||
if (rawClean.length < 5) {
|
||||
if (statusEl) statusEl.textContent = '';
|
||||
if (messageEl) { messageEl.textContent = ''; messageEl.className = 'iban-message'; }
|
||||
this.classList.remove('input-error', 'input-valid');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = validateIBAN(rawClean);
|
||||
if (result.valid) {
|
||||
if (statusEl) statusEl.textContent = '✅';
|
||||
if (messageEl) { messageEl.textContent = 'IBAN gültig'; messageEl.className = 'iban-message success'; }
|
||||
this.classList.remove('input-error'); this.classList.add('input-valid');
|
||||
} else {
|
||||
if (statusEl) statusEl.textContent = '❌';
|
||||
if (messageEl) { messageEl.textContent = result.error; messageEl.className = 'iban-message error'; }
|
||||
this.classList.remove('input-valid'); this.classList.add('input-error');
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -20,6 +20,38 @@ async function verifyEmailDomain(email) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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;
|
||||
@ -46,6 +78,14 @@ router.post('/submit-membership', async (req, res) => {
|
||||
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.' });
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
</head>
|
||||
<body class="admin-body">
|
||||
<div class="admin-layout">
|
||||
<div class="admin-layout">
|
||||
|
||||
<aside class="admin-sidebar">
|
||||
<div class="logo admin-logo">Plusfit<span>24</span></div>
|
||||
|
||||
@ -225,7 +225,8 @@
|
||||
<div class="karte-row">
|
||||
<div class="karte-field karte-field-full">
|
||||
<label>IBAN</label>
|
||||
<input type="text" name="iban" value="<%= member.iban ? member.iban.replace(/(.{4})/g, '$1 ').trim() : '' %>" disabled class="karte-input karte-iban">
|
||||
<input type="text" name="iban" id="ibanInput" value="<%= member.iban ? member.iban.replace(/(.{4})/g, '$1 ').trim() : '' %>" disabled class="karte-input karte-iban" maxlength="34" autocomplete="off">
|
||||
<div class="iban-message" id="ibanMessage"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="karte-row">
|
||||
@ -291,11 +292,18 @@
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<script src="/js/iban.js"></script>
|
||||
<script>
|
||||
const editableFields = document.querySelectorAll('.karte-input:not(.karte-readonly)');
|
||||
|
||||
function enableEdit() {
|
||||
editableFields.forEach(f => f.removeAttribute('disabled'));
|
||||
// IBAN Validierung aktivieren
|
||||
const ibanInput = document.getElementById('ibanInput');
|
||||
const ibanMessage = document.getElementById('ibanMessage');
|
||||
if (ibanInput) {
|
||||
attachIBANValidation(ibanInput, null, ibanMessage);
|
||||
}
|
||||
document.getElementById('editBtn').classList.add('hidden');
|
||||
document.getElementById('saveBtn').classList.remove('hidden');
|
||||
document.getElementById('cancelBtn').classList.remove('hidden');
|
||||
|
||||
@ -176,8 +176,10 @@
|
||||
<div class="form-group">
|
||||
<div class="input-wrap">
|
||||
<span class="input-icon">💳</span>
|
||||
<input type="text" id="iban" placeholder="IBAN" maxlength="22">
|
||||
<input type="text" id="iban" placeholder="DE00 0000 0000 0000 0000 00" maxlength="34" autocomplete="off">
|
||||
<span class="email-status" id="ibanStatus"></span>
|
||||
</div>
|
||||
<div class="email-message" id="ibanMessage"></div>
|
||||
</div>
|
||||
|
||||
<div class="step-buttons">
|
||||
@ -278,6 +280,7 @@
|
||||
<p>© 2024 PlusFit24 UG · <a href="https://plusfit24.de/datenschutz-2/" target="_blank">Datenschutz</a> · <a href="https://plusfit24.de/wp-content/uploads/2022/11/AG_PlusFit24.pdf" target="_blank">AGB</a></p>
|
||||
</footer>
|
||||
|
||||
<script src="/js/iban.js"></script>
|
||||
<script>
|
||||
const TARIFF_ID = '<%= tariff.id %>';
|
||||
let currentStep = 1;
|
||||
@ -468,6 +471,11 @@
|
||||
btn.textContent = 'Kostenpflichtige Mitgliedschaft abschließen';
|
||||
}
|
||||
}
|
||||
// IBAN Validierung (iban.js wird vor diesem Script geladen)
|
||||
const ibanInput = document.getElementById('iban');
|
||||
if (ibanInput) {
|
||||
attachIBANValidation(ibanInput, document.getElementById('ibanStatus'), document.getElementById('ibanMessage'));
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user