Bankkarte: IBAN pruefen + BIC/Bank offline aus Bundesbank-BLZ ausfuellen
Neuer Button "BIC & Bank aus IBAN abrufen" im Bankkarten-Modal fuellt BIC + Banknamen automatisch und validiert dabei die IBAN-Pruefziffer (mod-97). Leeres IBAN-Feld -> OK-Messagebox statt Anfrage. Datenschutzfreundlich/offline: kein Dritt-Dienst. Nachschlag im eigenen Backend ueber die Bundesbank-Bankleitzahlendatei (bankdata-germany) + ibantools fuer die Pruefziffer. Die IBAN verlaesst nie den Server; zurueck kommen nur oeffentliche Bankverzeichnis-Daten. Endpoint: POST /api/bank-cards/iban-lookup (nur eingeloggt). Wartung: bankdata-germany/ibantools ~quartalsweise per npm update ziehen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2692,12 +2692,54 @@ function BankCardModal({
|
||||
});
|
||||
|
||||
const [formData, setFormData] = useState(getInitialFormData);
|
||||
const [ibanLookupLoading, setIbanLookupLoading] = useState(false);
|
||||
const [showIbanRequired, setShowIbanRequired] = useState(false);
|
||||
|
||||
// Reset form when bankCard changes
|
||||
useState(() => {
|
||||
setFormData(getInitialFormData());
|
||||
});
|
||||
|
||||
// BIC + Bankname aus der IBAN ableiten (offline über Bundesbank-BLZ im
|
||||
// Backend). Ist das IBAN-Feld leer, kommt eine OK-Messagebox statt einer
|
||||
// Anfrage. Gleichzeitig prüft der Server die IBAN-Prüfziffer (Tippfehler).
|
||||
const handleIbanLookup = async () => {
|
||||
const iban = formData.iban.replace(/\s+/g, '').trim();
|
||||
if (!iban) {
|
||||
setShowIbanRequired(true);
|
||||
return;
|
||||
}
|
||||
setIbanLookupLoading(true);
|
||||
try {
|
||||
const res = await bankCardApi.ibanLookup(iban);
|
||||
const d = res.data;
|
||||
if (!d) {
|
||||
toast.error('IBAN-Prüfung fehlgeschlagen.');
|
||||
return;
|
||||
}
|
||||
if (!d.valid) {
|
||||
toast.error('Die IBAN ist ungültig – bitte Eingabe/Prüfziffer kontrollieren.');
|
||||
return;
|
||||
}
|
||||
if (d.bic || d.bankName) {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
bic: d.bic || prev.bic,
|
||||
bankName: d.bankName || prev.bankName,
|
||||
}));
|
||||
toast.success('IBAN gültig – BIC und Bank übernommen.');
|
||||
} else {
|
||||
toast('IBAN ist gültig, aber es wurden keine Bankdaten gefunden (nur deutsche IBANs werden aufgelöst).', {
|
||||
icon: 'ℹ️',
|
||||
});
|
||||
}
|
||||
} catch (err: any) {
|
||||
toast.error(err?.response?.data?.error || 'Fehler beim IBAN-Nachschlagen.');
|
||||
} finally {
|
||||
setIbanLookupLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: (data: any) => bankCardApi.create(customerId, data),
|
||||
onSuccess: () => {
|
||||
@@ -2738,6 +2780,7 @@ function BankCardModal({
|
||||
}, [bankCard?.id]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal isOpen={isOpen} onClose={onClose} title={isEditing ? 'Bankkarte bearbeiten' : 'Bankkarte hinzufügen'}>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<Input
|
||||
@@ -2747,12 +2790,30 @@ function BankCardModal({
|
||||
required
|
||||
/>
|
||||
|
||||
<Input
|
||||
label="IBAN"
|
||||
value={formData.iban}
|
||||
onChange={(e) => setFormData({ ...formData, iban: e.target.value })}
|
||||
required
|
||||
/>
|
||||
<div>
|
||||
<Input
|
||||
label="IBAN"
|
||||
value={formData.iban}
|
||||
onChange={(e) => setFormData({ ...formData, iban: e.target.value })}
|
||||
required
|
||||
/>
|
||||
<div className="flex justify-end mt-1.5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleIbanLookup}
|
||||
disabled={ibanLookupLoading}
|
||||
className="inline-flex items-center gap-1.5 text-sm font-medium text-blue-600 hover:text-blue-800 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
title="Prüft die IBAN und füllt BIC + Bank aus (offline, Bundesbank-Bankleitzahlen)"
|
||||
>
|
||||
{ibanLookupLoading ? (
|
||||
<RefreshCw className="w-4 h-4 animate-spin" />
|
||||
) : (
|
||||
<Search className="w-4 h-4" />
|
||||
)}
|
||||
BIC & Bank aus IBAN abrufen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
label="BIC"
|
||||
@@ -2813,6 +2874,20 @@ function BankCardModal({
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
|
||||
{showIbanRequired && (
|
||||
<Modal isOpen onClose={() => setShowIbanRequired(false)} title="IBAN erforderlich" size="sm">
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-gray-700">
|
||||
Bitte trage zuerst eine IBAN im Feld „IBAN“ ein. BIC und Bank werden daraus ermittelt.
|
||||
</p>
|
||||
<div className="flex justify-end">
|
||||
<Button onClick={() => setShowIbanRequired(false)}>OK</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -377,8 +377,23 @@ export const bankCardApi = {
|
||||
const res = await api.delete<ApiResponse<void>>(`/bank-cards/${id}`);
|
||||
return res.data;
|
||||
},
|
||||
// IBAN-Prüfung + BIC/Bank-Nachschlag (offline, Bundesbank-BLZ) – die IBAN
|
||||
// verlässt nur unser eigenes Backend, kein Dritt-Dienst.
|
||||
ibanLookup: async (iban: string) => {
|
||||
const res = await api.post<ApiResponse<IbanLookupResult>>(`/bank-cards/iban-lookup`, { iban });
|
||||
return res.data;
|
||||
},
|
||||
};
|
||||
|
||||
export interface IbanLookupResult {
|
||||
valid: boolean;
|
||||
iban: string;
|
||||
country?: string;
|
||||
bic?: string | null;
|
||||
bankName?: string | null;
|
||||
blz?: string | null;
|
||||
}
|
||||
|
||||
// Identity Documents
|
||||
export const documentApi = {
|
||||
getByCustomer: async (customerId: number, showInactive = false) => {
|
||||
|
||||
Reference in New Issue
Block a user