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; } }