rikudou/iban
Validate, parse, and format International Bank Account Numbers in PHP with rikudou/iban. Country-aware checks, compact and electronic IBAN formatting, and simple integration for banking, payment, and Laravel validation workflows.
composer require rikudou/iban
This is a plain library, not a Laravel package — there's no service provider, config file, or facade to publish. You get two things:
Rikudou\Iban\IBAN\Iban — a value object that parses an IBAN and exposes its parts (country code, BBAN, bank identifier, account number) plus a formatted rendering.Rikudou\Iban\Validator\* — IbanValidator for the ISO 13616 structure + mod-97 check, ChainValidator to stack several checks, and country-specific validators (Czech/Slovak bank-code and account-number verification are the notable ones).Read first, in this order: README.md (it's short), then src/IBAN/IbanInterface.php (the accessor surface you'll actually call), then src/Validator/ValidatorInterface.php (signature and error reporting). Everything else is implementation detail.
First use case: validate and normalize an IBAN captured on a billing or payout form.
use Rikudou\Iban\IBAN\Iban;
use Rikudou\Iban\Validator\IbanValidator;
$raw = "CZ65 0800 0000 1920 0014 5399";
$normalized = strtoupper(preg_replace('/[\s\p{Z}]+/u', '', $raw));
try {
$iban = new Iban($normalized); // throws on malformed input
} catch (\Throwable $e) {
return false; // length / charset / country problem
}
$valid = (new IbanValidator())->isValid($iban); // mod-97 + country rules
Note the two failure modes: the constructor throws on bad input, the validator returns a bool. Any wrapper you build has to handle both.
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Rikudou\Iban\IBAN\Iban;
use Rikudou\Iban\Validator\IbanValidator;
final class ValidIban implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$normalized = strtoupper(preg_replace('/[\s\p{Z}]+/u', '', (string) $value));
try {
$iban = new Iban($normalized);
} catch (\Throwable) {
$fail('The :attribute is not a well-formed IBAN.');
return;
}
if (! (new IbanValidator())->isValid($iban)) {
$fail('The :attribute failed IBAN checksum validation.');
}
}
}
Use it in a FormRequest as 'iban' => ['required', 'string', 'max:42', new ValidIban()]. The max:42 accommodates the spaced form; the canonical form is at most 34 characters.
Store the canonical ungrouped uppercase form — never the spaced one. An attribute cast keeps it consistent across controllers, jobs and imports:
protected function iban(): Attribute
{
return Attribute::make(
set: fn (?string $v) => $v === null
? null
: strtoupper(preg_replace('/[\s\p{Z}]+/u', '', $v)),
);
}
The concrete IBAN classes implement __toString() for the canonical value, and expose the grouped 4-character form for display (getFormattedIban() — confirm the name against IbanInterface in your version). Expose both in API resources / Blade:
'iban' => (string) $account->iban,
'iban_formatted' => $account->iban->getFormattedIban(),
If you'd rather not couple views to the library, trim(chunk_split($iban, 4, ' ')) produces the same output.
Wrap it — don't sprinkle new IbanValidator() through the app:
// app/Providers/BankingServiceProvider.php
$this->app->singleton(ValidatorInterface::class, function () {
return new ChainValidator([
new IbanValidator(),
// new CzechIbanValidator(), new SlovakIbanValidator(), ...
]);
});
ChainValidator is the main extension point: the base IbanValidator only verifies structure and checksum, so "is this a real Czech account?" requires stacking the country-specific validator.
For CZ/SK the package can compose an IBAN from bank code + account number (CzechIban, SlovakIban under src/IBAN/), which beats hard-coded strings in factories:
// database/factories/AccountFactory.php
'iban' => (string) new CzechIban('0800', '0000192000145399'),
For other countries, keep a small fixture helper class with published example IBANs and reuse it across tests and seeders.
Validate in the FormRequest → persist the canonical form → re-validate when the payout job runs, because CSV imports and manual DB edits bypass your rule. Route both validations through a single IbanService so the failure path (log, notify, block payout) is identical.
IbanValidatorContract / value object so swapping to Symfony's IBAN constraint, another IBAN port, or a 30-line mod-97 check is a one-file change.InvalidIbanException and friends); isValid() returns false. Catch \Throwable at the boundary — a stray library exception escaping a FormRequest becomes a 500.IbanInterface in isValid(); older tags accepted a raw string. If behaviour changes after composer update, read src/Validator/ValidatorInterface.php before assuming your data is wrong.varchar(34) for canonical, 42 if you also persist the grouped form./[\s\p{Z}]+/u, not /\s/, or a non-breaking space pasted from a PDF will fail validation.Iban is a generic class name. Alias on import (use Rikudou\Iban\IBAN\Iban as IbanValue;) to avoid colliding with your own model or DTO names.How can I help you explore Laravel packages today?