Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Php Iban Laravel Package

globalcitizen/php-iban

Parse, validate and generate IBAN/IIBAN bank details in PHP. Extract country codes, checksums, bank and branch codes, verify legacy national checksums, convert formats, obfuscate and get correction suggestions for mistyped input. Includes example IBANs per country.

View on GitHub
Deep Wiki
Context7

Getting Started

Install with Composer — there is no Laravel service provider, facade, or config file to publish:

composer require globalcitizen/php-iban

The package is plain procedural PHP. Composer autoloads php-iban.php through its files autoload entry, so the iban_* / verify_iban() functions are available globally as soon as vendor/autoload.php is loaded. Nothing needs registering in config/app.php or a service provider.

Where to look first:

  • vendor/globalcitizen/php-iban/php-iban.php — the function list is the real API documentation. Grep for function iban_ and function verify_iban.
  • vendor/globalcitizen/php-iban/registry.txt — the country registry (lengths, BBAN structure, SEPA membership, currency, central bank metadata).
  • docs/COMEDY-OF-ERRORS in the repo — explains deliberate deviations from SWIFT's registry. Read this before assuming a SWIFT spec mismatch is a bug.
  • tests/ — example IBANs per country; useful as your own fixture source.

First use case: validating a bank account IBAN on a form.

$input = 'GB82 WEST 1234 5698 7654 32';

$machine = iban_to_machine_format($input); // GB82WEST12345698765432

if (!verify_iban($machine, true)) {
    // invalid
}

$country = iban_country($machine);                       // GB
$iso     = iban_country_get_iso3166($country);           // GB
$bank    = iban_get_bank_identifier($machine);           // WEST
$branch  = iban_get_branch_identifier($machine);         // 123456
$account = iban_get_account_number($machine);            // 98765432
$pretty  = iban_to_human_format($machine);               // GB82 WEST 1234 5698 7654 32

Do that once in a throwaway route or tinker session, then move straight to a validation rule — that is where 90% of day-to-day use lives.

Implementation Patterns

1. A dedicated validation rule (plus normalization in a FormRequest).

namespace App\Rules;

use Illuminate\Contracts\Validation\Rule;

class Iban implements Rule
{
    private string $normalized = '';

    public function passes($attribute, $value): bool
    {
        if (!is_string($value) || trim($value) === '') {
            return false;
        }

        $this->normalized = iban_to_machine_format(trim($value));

        // true = machine format only, so stray spaces/hyphens fail here.
        return verify_iban($this->normalized, true);
    }

    public function message(): string
    {
        $suggestIONS = $this->normalized
            ? iban_mistranscription_suggestions($this->normalized)
            : [];

        if (!empty($suggestions)) {
            return 'The :attribute is not a valid IBAN. Did you mean '
                . iban_to_human_format($suggestions[0]) . '?';
        }

        return 'The :attribute is not a valid IBAN.';
    }
}

Normalize once, at the edge, in prepareForValidation() so the rest of the app only ever sees machine format:

protected function prepareForValidation(): void
{
    if ($this->filled('iban')) {
        $this->merge([
            'iban' => iban_to_machine_format(trim((string) $this->input('iban'))),
        ]);
    }
}

2. Store machine format, present human format. Machine format is canonical for comparison, uniqueness, and payment files. Human format is for UI, PDFs, and emails. Add a separate accessor rather than mutating the stored attribute — an accessor that reformats will surprise API consumers and where() clauses.

protected function ibanPretty(): Attribute
{
    return Attribute::make(
        get: fn () => $this->iban ? iban_to_human_format($this->iban) : null,
    );
}

Column: string('iban', 42)->nullable()->index() is a safe default; the 34-char official limit leaves room for IIBAN and future expansion.

3. Parse into a small value object instead of scattering function calls.

final class IbanInfo
{
    public function __construct(
        public readonly string $machine,
        public readonly string $countryCode,
        public readonly ?string $iso3166,
        public readonly ?string $bankIdentifier,
        public readonly ?string $branchIdentifier,
        public readonly ?string $accountNumber,
    ) {}

    public static function fromValid(string $iban): self
    {
        $machine = iban_to_machine_format($iban);
        $country = iban_country($machine);

        return new self(
            $machine,
            $country,
            iban_country_get_iso3166($country) ?: null,
            iban_get_bank_identifier($machine) ?: null,
            iban_get_branch_identifier($machine) ?: null,
            iban_get_account_number($machine) ?: null,
        );
    }
}

Bind it through a BankAccountService and inject that into controllers/jobs. This also gives you the testing seam the package otherwise lacks (see Gotchas).

4. SEPA eligibility and payout routing. Validate first, then check the country: if your product only supports SEPA, filter on a SEPA country list (or the library's SEPA country helper in current releases) before accepting the IBAN. verify_iban() happily accepts 116 countries, including unofficial ones and IIBAN (AA), which you almost certainly do not want in a euro payout flow.

5. Error-correction as UX, never as an automatic write. iban_mistranscription_suggestions() is the differentiator versus hand-rolled regex. Use it to render a "did you mean…?" confirmation, then require the user to re-submit the corrected value. Never silently rewrite a stored IBAN from a suggestion — a wrong-but-valid IBAN is worse than a rejected one.

6. Import and reconciliation jobs. For CSV imports, normalize + validate per row, collect failures with row numbers, and only touch the DB for valid rows inside a transaction/chunk:

LazyCollection::make($rows)->chunk(500)->each(function ($chunk) {
    foreach ($chunk as $row) {
        $machine = iban_to_machine_format($row['iban']);
        if (!verify_iban($machine, true)) {
            $this->failures[] = $row['line'];
            continue;
        }
        BankAccount::updateOrCreate(
            ['iban' => $machine],
            ['country_code' => iban_country($machine)]
        );
    }
});

7. Presentation and logging. Use iban_to_human_format() for display and iban_to_obfuscated() for anything that leaves your trust boundary (support tickets, logs, notification emails). Treat an IBAN as PII: do not log the raw value at info level.

Gotchas and Tips

  • Global functions, not classes. There is nothing to mock. If you need to fake validation in tests, wrap the library in your own IbanValidator interface and bind it in a service provider, or you will end up with untestable controllers.
  • verify_iban()'s second argument matters. verify_iban($iban, true) means "machine format only" and rejects spaced input. If you validate before normalizing, users pasting GB82 WEST … will get a confusing failure. Normalize, then validate.
  • The $strict flag's semantics are country-specific. Check php-iban.php for the exact behaviour in the version you pin before relying on it; it is not a simple "stricter length check".
  • Normalization is not validation. iban_to_machine_format() will happily return garbage for garbage input. Always follow it with verify_iban().
  • iban_country(), iban_get_bank_identifier(), etc. assume a valid IBAN. Calling them on invalid input can yield empty or misleading fragments. Gate all parsing behind a successful verify_iban().
  • Branch and account fields can be absent. Some national BBAN formats have no fixed branch code, and the registry expresses different structures per country. Type these as nullable and do not build logic that assumes a bank/branch split exists everywhere.
  • Legacy national checksums are a bonus, not a baseline. Where a country defines one, the library can verify and set it — but "no national checksum defined" and "checksum failed" are different states. Don't collapse them into a single false and reject valid accounts from unsupported countries.
  • Unofficial countries and IIBAN (AA) are accepted. That is a feature for some products, a footgun for SEPA-only ones. Maintain an explicit allow-list of countries you support and check it independently of verify_iban().
  • Registry currency and central-bank metadata can lag SWIFT. The registry is manually maintained against a specific SWIFT release (README cites PDF #86 / January 2020 with ongoing updates). For anything financially material (currency, SEPA membership), cross-check against your own config rather than trusting iban_country_get_currency() blindly.
  • Kosovo (XK) and similar non-ISO entities. iban_country_get_iso3166() can come back empty. Handle the null path instead of assuming a two-letter ISO code.
  • IIBAN/length assumption. Official IBANs max at 34 characters, but don't hard-code varchar(34) if you also accept IIBAN or want headroom for registry changes. Use 42–64 and index it.
  • Version pinning and update cadence. The registry changes when new countries adopt IBAN or lengths change. Pin a version, subscribe to releases, and keep a regression test with a handful of real per-country IBANs from the package's tests/ directory so an upgrade that breaks parsing is caught immediately.
  • Performance in bulk. Per-call validation is cheap (regex + mod-97), but registry lookups and suggestion generation are heavier. For 100k-row imports, skip iban_mistranscription_suggestions() entirely — it's for interactive UX, not batch work.
  • LGPL-3.0 in a commercial app. Using it as an unmodified Composer dependency is fine. If you vendor and modify php-iban.php, you take on LGPL obligations for that file — keep modifications as an upstream PR instead of a local fork.
  • No Laravel wrapper exists (zero dependents). You own the glue: the rule, the value object, the service binding, and the country policy. Budget an hour for that, not a day.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
adachsoft/php-code-style