IBAN und BIC Validierung (2)

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.

Schreibe einen Kommentar