laravel-debt-registers maintained by beziworld
Laravel Debt Registers
Laravel client for Polish debt registers (biura informacji gospodarczej). Check a person by PESEL or a company by NIP and report unpaid debts, with one driver per register and any number of accounts:
- KRD (Krajowy Rejestr Długów BIG S.A.), the register with publicly documented APIs:
- debtor checks over the Chase 3.3 SOAP service: consumers by PESEL, companies and sole traders by NIP, foreign numbers, incognito checks for entitled entities, report re-download by number
- typed results: unpaid obligations with creditor, debtor, amounts, dates, proceedings and objections; paid obligations (positive information); summaries; the API password expiry reported by KRD
- adding, updating and removing debts over the Siddin 2.1 service with Nicci 3.2 batches: a typed batch builder (validated against the official XSD during development), chunked upload, job status, processing report with the KRD identifiers of every case and obligation
DebtRegisters::driver('krd'), named connections per account, runtime credentials withusing(), custom drivers withextend()- PESEL and NIP checksum validation before any request, a
DebtorCheckedevent for audit trails - no auto-retry of anything that reached the register (checks are billed, submissions change data)
- demo environment by default, so a fresh install never runs billed production checks
Which registers have a public API?
| Register | Public API documentation | Driver |
|---|---|---|
| KRD (Krajowy Rejestr Długów) | Yes: info.krd.pl, Programista (Chase, Nicci/Siddin, Rastin, Solvig, Amnell, Wexler), public WSDL and a free demo environment | krd |
| BIG InfoMonitor | Message schemas only (big.pl/dokumentacja/api); the endpoint, authentication and test environment are given to subscribers | not included |
| ERIF BIG | No public API documentation | not included |
The architecture is driver-based, so a BIG InfoMonitor or ERIF driver can be registered with extend() by anyone
who has the subscriber documentation.
Why this package
Packagist has no Laravel package for KRD. The existing PHP packages are solvenfinance/krdapi (2017, Zend
Framework 2, PHP 5.6/7, last release 0.0.2) and goosfraba/krd-rastin-sdk (the Rastin PESEL and ID card
verification service only). This package covers the current Chase 3.3 check service and the Siddin/Nicci 3.2
submission service, works without ext-soap, returns typed results and keeps every call fakeable with Http::fake().
Installation
composer require beziworld/laravel-debt-registers
php artisan vendor:publish --tag=debt-registers-config # optional
KRD_ENVIRONMENT=demo # demo (demo.krd.pl) or production (services.krd.pl, billed)
KRD_LOGIN=your-api-login
KRD_PASSWORD=your-api-password
KRD_AUTH=password_hash # password, password_hash, certificate, certificate_with_credentials
KRD_CERTIFICATE= # PEM client certificate for certificate logins
KRD_CERTIFICATE_PASSWORD=
KRD_SSL_KEY=
KRD_CHUNK_SIZE=512000 # characters per Siddin upload chunk
DEBT_REGISTERS_TIMEOUT=60
DEBT_REGISTERS_RETRIES=0 # extra attempts only after a connection failure with no response
A free demo account is available on request from KRD (info.krd.pl, demo environment). In the demo, reports contain only the obligations added by the same account. API access in production requires an agreement with KRD.
Checking a company or a person
use Beziworld\DebtRegisters\Facades\DebtRegisters;
$result = DebtRegisters::driver('krd')->checkCompany('526-025-09-95'); // separators and "PL" are accepted
$result->reportNumber; // "U1/0000123456/2026"
$result->createdAt; // CarbonImmutable, Europe/Warsaw
$result->hasNegativeInformation(); // true when unpaid obligations are listed
$result->totalArrears(); // Money: amount "4520.11" (decimal string), currency "PLN"
$result->summary->creditorsCount; // 2
foreach ($result->obligations as $obligation) {
$obligation->title; // "FV/0001/2026"
$obligation->type; // "Invoice"
$obligation->arrears; // Money
$obligation->paymentDate; // CarbonImmutable
$obligation->disputed; // true when the debtor raised objections
$obligation->creditor->name; // "Hurtownia Alfa S.A."
$obligation->creditor->nip;
}
foreach ($result->paidObligations as $paid) {
$paid->paidDebt; // Money
$paid->paidDate;
}
Consumers are checked by PESEL with the date of their authorisation (Polish law requires the consumer's consent before the disclosure):
$result = DebtRegisters::checkConsumer('44051401359', consentGivenAt: $application->consent_at);
Every check in production is billed and appears in the subject's query register. Use the demo environment for development, and never re-run a check just to read the same data: download the report again by its number.
$krd = DebtRegisters::krd();
$krd->report('U1/0000123456/2026'); // GetDisclosureReport, not a new disclosure
$krd->checkCompanyByOtherNumber('DE123456789'); // foreign company number
$krd->checkConsumerByOtherNumber('X1234567', now());
$krd->incognito()->checkCompany('5260250995'); // art. 25 entities only: no trace in the query register
$krd->lastSession()?->passwordExpiresAt; // warn before the API password expires
$krd->lastSession()?->shouldChangePassword;
DebtorChecked (register, connection, result, incognito) is dispatched after every successful check, which is a
convenient place to keep the audit trail of disclosed information.
Several accounts
// config/debt-registers.php
'connections' => [
'krd' => ['driver' => 'krd', 'login' => env('KRD_LOGIN'), 'password' => env('KRD_PASSWORD')],
'krd_branch' => ['driver' => 'krd', 'environment' => 'production', 'login' => env('KRD_BRANCH_LOGIN'), 'password' => env('KRD_BRANCH_PASSWORD')],
],
DebtRegisters::driver('krd_branch')->checkCompany($nip);
// Credentials stored per tenant
DebtRegisters::using(['login' => $tenant->krd_login, 'password' => $tenant->krd_password, 'environment' => 'production'])
->checkCompany($nip);
Options outside connections (timeout, retries) are shared by all connections. DebtRegister (the contract)
and KrdDriver can be injected; they resolve to the default connection.
Reporting debts to KRD (Nicci 3.2)
Debts are sent as batches processed asynchronously by KRD. KRD asks for few large batches instead of many small ones.
use Beziworld\DebtRegisters\Enums\ObligationType;
use Beziworld\DebtRegisters\Nicci\Address;
use Beziworld\DebtRegisters\Nicci\Debtor;
use Beziworld\DebtRegisters\Nicci\NicciDocument;
use Beziworld\DebtRegisters\Nicci\Obligation;
$batch = (new NicciDocument())
->addCase(
orderId: 'order-1',
debtor: Debtor::company('Dłużnik Sp. z o.o.', '5260250995', Address::polish('ul. Prosta 1', '00-001', 'Warszawa')),
obligations: [
new Obligation(
title: 'FV/0001/2026',
paymentDate: $invoice->due_date,
callSent: $invoice->demand_sent_at, // payment demand with the warning about KRD
arrears: '1230.50',
debt: '1500.00',
type: ObligationType::Invoice,
userId: 'invoice-'.$invoice->id, // your id, used to update or remove it later
),
],
caseId: 'customer-'.$customer->id,
)
->addCase('order-2', Debtor::consumer('Jan', 'Kowalski', '44051401359', Address::polish('ul. Długa 2', '00-002', 'Warszawa')), [/* ... */])
->addCase('order-3', Debtor::soleTrader('Jan Nowak Usługi', '8226951300', 'Jan', 'Nowak'), [/* ... */]);
$submissions = DebtRegisters::krd()->submissions();
$jobId = $submissions->submit($batch, description: 'November 2026'); // never retried automatically
KRD accepts an obligation only when it has been due for at least 30 days, at least 30 days passed since the payment
demand (callSent), and the arrears exceed 200 PLN for a consumer or 500 PLN for a business. These rules are
checked by KRD and reported per order. Without objections an obligation is sent as undisputed.
Updating and removing:
use Beziworld\DebtRegisters\Enums\InformationType;
use Beziworld\DebtRegisters\Enums\RemoveReason;
$batch = (new NicciDocument())
->updateObligation('u-1', 'invoice-15', $obligationWithLowerArrears) // partial payment
->updateCase('u-2', 'customer-7', $debtor, $obligations) // obligations left out are removed
->remove('r-1', 'customer-9') // whole case paid
->remove('r-2', 'invoice-21', InformationType::ObligationInformation, RemoveReason::Cession);
Status and processing report:
$job = $submissions->job($jobId); // Job: status (Queued, Started, Processed, Canceled, Failed), progress, queueNumber
if ($job?->isProcessed()) {
$report = $submissions->result($jobId);
$report->successful(); // no fatal error and no failed order
$report->failedOrders(); // OrderResult with errorMessage and errorCode
$report->order('order-1')?->systemId; // KRD case id, e.g. "CAF/0000219303/2018"
$report->order('order-1')?->obligationSystemIds; // ['invoice-15' => 'OBF/0000408118/2018']
$report->xml; // the original report, for archiving
}
$submissions->jobs(changedSince: now()->subDay()); // for a scheduled poller
$submissions->cancel($jobId); // only while queued
A complete Nicci document built elsewhere can be passed to submit() as a string. NicciDocument::toXml()
returns the document for review or archiving.
Errors
| Situation | Result |
|---|---|
| Invalid login or password (SecurityFault code 1), empty Siddin ticket | AuthenticationFailed |
| Missing permission or access denied (codes 2, 3) | PermissionDenied |
| Data rejected by KRD (ValidationFault, SchemaValidationFault, codes 13, 14) | ValidationFailed with validationErrors() per field |
| Unknown report number (EntityNotFoundFault, code 50) | ReportNotFound |
| Other SOAP faults, HTTP errors, connection failures, unreadable responses | ApiException |
| Invalid PESEL or NIP checksum | InvalidIdentifier, before any request |
| Invalid batch data (empty batch, amounts, lengths, REGON, duplicate order ids) | InvalidSubmission, before any request |
| Report download in incognito mode, result of an unprocessed job, Siddin with a certificate-only connection | UnsupportedOperation |
| Missing credentials, unknown connection or driver | InvalidConfiguration |
ApiException (and its subclasses) expose errorCode(), faultType(), faultId() (the error id to quote to KRD
support at pomocit@krd.pl), httpStatus(), operation() and raw(). All exceptions extend
Beziworld\DebtRegisters\Exceptions\DebtRegistersException.
Not covered
- KRD registry of queries (SearchRegistryReport, GetGenericDisclosureReport), monitoring, positive information (PaidObligationCase), executive and alimony obligations in the batch builder (they can be sent in a raw Nicci document), Rastin (PESEL and ID card verification), Solvig (scoring), Amnell (PESEL reservation), Wexler (financial statements).
- BIG InfoMonitor and ERIF (no public endpoint and authentication documentation).
Development
composer install
composer ci # pint --test, phpstan (level 8), pest
Fixtures follow the examples and the WSDL/XSD published by KRD; no request leaves the test suite. The SOAP envelope, the endpoints and the fault mapping were checked against the KRD demo environment with invalid credentials only.
Author
Created and maintained by Kamil Ćwiertnia, beziworld.eu.
- E-mail: biuro@beziworld.eu
- Issues: GitHub issues
Need an integration? Get in touch through beziworld.eu.
Other packages by the author
laravel-company-data · laravel-krs · laravel-polish-validation · laravel-ksef
License
MIT. See LICENSE.md. This package is not affiliated with or endorsed by Krajowy Rejestr Długów Biuro Informacji Gospodarczej S.A., BIG InfoMonitor S.A. or ERIF Biuro Informacji Gospodarczej S.A. Use of the KRD services requires an agreement with KRD and compliance with the act on disclosure of economic information.