67 lines
2.3 KiB
PHP
67 lines
2.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Support;
|
|
|
|
/**
|
|
* Zustandslose Helfer für die SEPA-Gläubiger-Identifikationsnummer (Creditor Identifier).
|
|
*
|
|
* Aufbau nach EPC262-08: Ländercode, zwei Prüfziffern, drei Zeichen Geschäftsbereichskennung, dann die
|
|
* nationale Kennung. In Deutschland vergibt die Bundesbank sie mit 18 Zeichen (`DE98ZZZ09999999999`).
|
|
*
|
|
* Die Prüfziffer ist der Teil, auf den es ankommt: Eine Lastschriftdatei mit falscher Gläubiger-ID lehnt
|
|
* die Bank als Ganzes ab -- und zwar erst beim Hochladen, wenn die Vorabankündigungen schon verschickt
|
|
* sind.
|
|
*/
|
|
final class CreditorId
|
|
{
|
|
/** Länge der Gläubiger-ID je Ländercode, soweit sie national festgelegt ist. */
|
|
private const array LENGTHS = [
|
|
'DE' => 18,
|
|
];
|
|
|
|
/** Leerzeichen raus, Großbuchstaben -- die kanonische Form, die gespeichert wird. */
|
|
public static function normalize(string $creditorId): string
|
|
{
|
|
return strtoupper(preg_replace('/\s+/', '', $creditorId) ?? '');
|
|
}
|
|
|
|
public static function isValid(string $creditorId): bool
|
|
{
|
|
$creditorId = self::normalize($creditorId);
|
|
|
|
if (preg_match('/^[A-Z]{2}[0-9]{2}[A-Z0-9]{3}[A-Z0-9]{1,28}$/', $creditorId) !== 1) {
|
|
return false;
|
|
}
|
|
|
|
$expected = self::LENGTHS[substr($creditorId, 0, 2)] ?? null;
|
|
if ($expected !== null && strlen($creditorId) !== $expected) {
|
|
return false;
|
|
}
|
|
|
|
return self::checksum($creditorId) === 1;
|
|
}
|
|
|
|
/**
|
|
* Mod 97-10 wie bei der IBAN, aber ohne die Geschäftsbereichskennung (Stellen 5 bis 7): Sie darf
|
|
* der Gläubiger frei wählen, ohne dass sich die Prüfziffer ändert. Geprüft wird deshalb die nationale
|
|
* Kennung, gefolgt von Ländercode und Prüfziffern; Buchstaben zählen als Position + 9 (A = 10 … Z = 35).
|
|
*/
|
|
private static function checksum(string $creditorId): int
|
|
{
|
|
$rearranged = substr($creditorId, 7) . substr($creditorId, 0, 4);
|
|
|
|
$remainder = 0;
|
|
foreach (str_split($rearranged) as $char) {
|
|
$value = ctype_digit($char) ? $char : (string) (ord($char) - 55);
|
|
|
|
foreach (str_split($value) as $digit) {
|
|
$remainder = ($remainder * 10 + (int) $digit) % 97;
|
|
}
|
|
}
|
|
|
|
return $remainder;
|
|
}
|
|
}
|