„Je mehr Vergnügen du an deiner Arbeit hast, um so besser wird sie bezahlt“
Mark Twain
Im ersten Teil zu der IBAN und BIC Validierung wurden erste Implementierungen vorgestellt um die syntaktische Korrektheit von IBAN und BIC zu prüfen und bei der IBAN zusätzlich die Prüfsumme zu nutzen. In diesem Beitrag sollen zusätzliche landesspezifische Eigenschaften genutzt werden. Insbesondere die Informationen der Deutschen Bundesbank zu den deutschen Geldinstituten.
Der BicValidator
wird um den Aufruf einer länderspezifischen BankService
Instanz erweitert. Die statische Methode byCountry
liefert die BankService
Instanz. Für den Ländercode DE
beispielsweise eine Instanz vom Typ GermanBankServiceImpl
. Über die byBankIdentifierCode
liefert der BankService
alle Bank Einträge mit dem verwendeten BIC. Der BIC ist genau dann valide, wenn die Liste nicht leer ist. Sollte es keinen entsprechenden BankService
geben, dann wird die BIC derzeit als valide angesehen.
@Override public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) { if (value == null) { return true; } Matcher matcher = PATTERN.matcher(value); if (!matcher.matches()) { return false; } String countryCode = matcher.group(2); Optional<BankService> bankService = BankService.byCountry(countryCode); return bankService.isEmpty() || !bankService.get().byBankIdentifierCode(value).isEmpty(); }
Bislang existiert nur eine einzige konkrete Implementierung der BankService
Klasse. Damit dies aber nicht so bleibt, wurde über den ServiceLoader
Mechanismus eine einfache Erweiterungsmöglichkeit bereitgestellt.
private record BankServiceWithCountry(String code, BankService bankService) { } private static final Map<String, BankService> BANK_SERVICES = ServiceLoader.load(BankService.class).stream().map(BankService::convert) .filter(Objects::nonNull).collect(toMap(BankServiceWithCountry::code, BankServiceWithCountry::bankService)); private static BankServiceWithCountry convert(Provider<BankService> bankService) { return Optional.ofNullable(bankService.type().getAnnotation(CountryCode.class)).map(CountryCode::value) .map(x -> new BankServiceWithCountry(x, bankService.get())).orElse(null); }
Bei der Klasseninitialisierung lädt der ServiceLocator
alle gefundenen Implementierungen in eine Map
. Als Schlüssel wird dabei der Wert des CountryCode
verwendet, mit dem die Implementierung annotiert ist.
Eigene Implementierungen benötigen nur eine META_INF/services/de.schegge.bank.BankService
, in der die implementierenden Klassen aufgelistet sind.
Die GermanBankServiceImpl
Implementierung liefert für jeden existierenden BIC eine Bank
Instanz. Möglich ist dies, weil Informationen der Deutschen Bundesbank genutzt werden. Die Informationen finden sich im Download Bereich des Instituts in Form der BLZ.txt
.
Die BLZ.txt
wird in regelmäßigen Abständen aktualisiert und enthält Daten zu jedem deutschen Kreditinstitut, u.a. den für die Validierung interessanten BIC und die Bankleitzahl. Etwas ungewöhnlich im Jahr 2023 ist der Aufbau der Datei. Es handelt sich um eine Fixed Length Format Datei, in der alle Zeilen die selbe Länge haben und keine Trennzeichen zwischen den einzelnen Felder existieren.
100000001Bundesbank 10591Berlin BBk Berlin 20100MARKDEF110009011380U000000000 100100101Postbank Ndl der Deutsche Bank 10559Berlin Postbank Ndl Deutsche Bank 10010PBNKDEFFXXX24000538U000000000 ...
Das Einlesen der benötigten Felder ist trivial und wird hier deshalb nicht weiter erörtert.
Wird die Methode byBankIdentifierCode
mit einem elfstelligen BIC aufgerufen, dann wird die entsprechende Bank
Instanz aus der byBanIdentifierCode
Map
gelesen. Bei einem achtstelligen BIC wird das Suffix XXX
angehängt um einen elfstelligen BIC zu erhalten. Wie schon im ersten Teil erwähnt, handelt es sich bei dem Suffix XXX
um den Hauptsitz.
Die Informationen aus der BLZ.txt
helfen auch bei der Validierung der deutschen IBAN, denn die die Positionen 5-12 der IBAN repräsentieren in Deutschland die Bankleitzahl des entsprechenden Geldinstituts. Die Bankleitzahl ist, eingedenk des Dateinamens, auch in der BLZ.txt
enthalten. Der IbanValidator
erhält auch eine ergänzende Prüfung der Bankleitzahl.
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) { if (value == null) { return true; } Matcher matcher = PATTERN.matcher(value); if (!matcher.matches()) { return false; } String countryCode = matcher.group(1); String basicBankAccountNumber = matcher.group(3); int checkDigit = Integer.parseInt(matcher.group(2)); StringBuilder builder = new StringBuilder(basicBankAccountNumber.length()); basicBankAccountNumber.chars().mapToObj(c -> CHAR_VALUES[c]).map(String::valueOf).forEach(builder::append); countryCode.chars().mapToObj(c -> CHAR_VALUES[c]).map(String::valueOf).forEach(builder::append); if (checkDigit != 98 - new BigInteger(builder.append("00").toString()).mod(VALUE_97).intValue()) { return false; } Optional<BankService> bankService = BankService.byCountry(countryCode); if (bankService.isEmpty()) { return lenient; } return bankService.map(x -> x.byBasicBankAcountNumber(basicBankAccountNumber)).isPresent(); }
Der die IBAN sich von Land zu Land in ihrem internen Aufbau unterscheidet, wird die Bankleitprüfung nicht auf den ersten acht Zeichen durchgeführt, sondern die gesamte Basic Bank Account Number übergeben. Die Klasse GermanBankServiceImpl
verwendet dann die ersten acht Zeichen als Bankleitzahl. Eine isländische Implementierung benötigt aber nur vier.
Auch bei dem IbanValidator
gilt eine IBAN als valide, wenn keine geeignete BankService
Implementierung für den Country Code existiert. Das ist nicht intuitiv, doch sollte bedacht werden, dass nur die Validität einer IBAN geprüft wird. Ob sich hinter dieser IBAN tatsächlich ein echtes Konto verbirgt, kann nur eine Anfrage bei der Bank bestätigen. Denn mit einer korrekten Bankleitzahl und einer zufälligen Kontonummer kann immer eine valide IBAN mit Prüfsumme berechnet werden.
Im abschließenden dritten Teil folgen noch einige kleine Ergänzungen um die Validierung von IBAN und BIC abzurunden.
1 thought on “IBAN und BIC Validierung (2)”