byrokrat/banking
PHP library for Swedish banking data: validate, format, and work with account and clearing numbers, Bankgiro and PlusGiro numbers, BIC/SWIFT codes, OCR references, and monetary amounts for payment integrations.
composer require byrokrat/banking
That is the whole install. There is no service provider, no config file, no facade — the package is a set of plain immutable PHP value objects plus factories. It drops into Laravel without any wiring, which is exactly why it is pleasant for a small set of well-defined problems.
Version 2.2.3 requires PHP >=7.3 and officially supports up to PHP 8.0. It is a maintenance release with no API changes: if you are already on 2.x, the upgrade is safe. If you are on PHP 8.0 you need at least this version — earlier 2.x releases failed there.
Where to look first:
README.md — one screen of usage, enough to get moving.src/ — the API surface is tiny and self-documenting; the Exception namespace tells you everything about failure modes.tests/ — this is the real specification. It is exhaustive over Swedish bank clearing ranges and check-digit algorithms, and it makes a good fixture source for your own test suite.The canonical first use case is validating an account number typed by a user on an invoice, payout or direct-debit form:
use Byrokrat\Banking\AccountFactory;
use Byrokrat\Banking\Exception\InvalidAccountNumberException;
try {
$account = (new AccountFactory)->createAccount('5001-2345');
$account->getBankName(); // bank owning the clearing range
$account->getClearingNumber(); // e.g. "5001"
$account->getSerialNumber(); // e.g. "2345"
$account->getCheckDigit(); // computed/validated check digit
} catch (InvalidAccountNumberException $e) {
// descriptive message about *why* it failed
}
The sibling types — clearing numbers, Bankgiro, Plusgiro, OCR references and BIC codes — follow the same shape: a factory parses a string, returns an immutable value object, and throws a descriptive exception on bad input. Confirm the exact factory/formatter class names against src/ once; the API is small and consistent across types.
Bind the factories in the container. They are cheap and stateless, but binding them keeps your call sites clean and makes them swappable in tests:
// AppServiceProvider::register()
$this->app->singleton(\Byrokrat\Banking\AccountFactory::class);
A dedicated validation rule beats inline parsing. Keep the parsing details out of controllers:
class SwedishAccountNumber implements ValidationRule
{
public function __construct(private AccountFactory $factory) {}
public function validate(string $attribute, mixed $value, Closure $fail): void
{
try {
$this->factory->createAccount(preg_replace('/\D/', '', (string) $value));
} catch (InvalidAccountNumberException) {
$fail('The :attribute is not a valid Swedish account number.');
}
}
}
Parse at the boundary, carry the value object inward. Turn the raw request string into an Account/Bankgiro in a FormRequest or an action, and let everything downstream depend on the object rather than a string. That is the whole point of the library: the invariant "this is a valid Swedish number" is proven once and never re-checked.
Store canonical digits, format on output. Persist the unformatted digits in a string column (never an integer — leading zeros are part of the number), and use the package's Byrokrat\Banking\Formatter classes to render the display form. getNumber() gives you a normalized grouped representation for API resources and PDFs.
Use an Eloquent cast for round-tripping.
public function get($model, $key, $value, $attributes)
{
return $value === null ? null : app(AccountFactory::class)->createAccount($value);
}
public function set($model, $key, $value, $attributes)
{
return [$key => $value instanceof AccountNumber
? $value->getClearingNumber() . $value->getSerialNumber()
: $value];
}
Batch imports must not abort on the first bad row. Wrap each row in its own try/catch, collect (row, raw input, exception message) into a failure report, and import the good rows. This is the single most valuable pattern for data migrations from legacy ledgers.
Payment file generation. A payout run typically needs account number + BIC + Bankgiro, with the OCR reference attached to the remittance line. The library gives you validated components and the bank name/BIC metadata you need to route the payment, so keep the objects (not strings) on your payment DTO.
getBankName() you get back is the best guess the hardcoded table can make — do not use it as a hard routing key for money movement.preg_replace('/\D/', '', $value) before parsing removes almost all of it.int. Clearing numbers and serial numbers routinely begin with zeros.Byrokrat\Banking\Exception, log getMessage() plus the raw input, and surface your own localized message to users.2.2.3 bumped the minimum to PHP 7.3 and fixed PHP 8.0 support; nothing newer is covered by the release. On PHP 8.1+ you are outside the tested matrix — run your own fixture tests in CI after every PHP upgrade, not just after package upgrades.2.2.3 is a maintenance release (PHP 8 support, PHPUnit 9 in the build) and does not touch the bank data, so the table is as old as the last feature release. Pin the version and re-run your own fixture tests after any update.app() inside the cast rather than type-hinting the factory.Format and Validator. If a bank is missing, you can compose the format/validator primitives rather than patching vendor code.composer.json PHP constraint if you were still allowing 7.2.How can I help you explore Laravel packages today?