n1ebieski / ksef-php-client
PHP API client that allows you to interact with the API Krajowego Systemu e-Faktur
Requires
- php: ^8.4.0
- cuyz/valinor: ^1.15
- krowinski/bcmath-extended: ^6.0
- php-http/discovery: ^1.20.0
- psr-discovery/log-implementations: ^1.1
- psr/http-client: ^1.0.3
- psr/http-client-implementation: ^1.0.1
- psr/http-factory-implementation: *
- psr/http-message: ^1.1.0|^2.0.0
- symfony/uid: ^7.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.75
- guzzlehttp/guzzle: ^7.9.2
- guzzlehttp/psr7: ^2.7.0
- monolog/monolog: ^3.9
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^12.1
- rector/rector: ^2.0
- vlucas/phpdotenv: ^5.6
This package is auto-updated.
Last update: 2025-05-31 07:10:54 UTC
README
KSEF PHP Client
This package is not production ready yet!
PHP API client that allows you to interact with the API Krajowego Systemu e-Faktur
Table of Contents
Get Started
Requires PHP 8.4+
First, install ksef-php-client
via the Composer package manager:
composer require n1ebieski/ksef-php-client
Ensure that the php-http/discovery
composer plugin is allowed to run or install a client manually if your project does not already have a PSR-18 client integrated.
composer require guzzlehttp/guzzle
Client configuration
use N1ebieski\KSEFClient\ClientBuilder; use N1ebieski\KSEFClient\ValueObjects\Mode; use N1ebieski\KSEFClient\Factories\EncryptionKeyFactory; $client = new ClientBuilder() ->withMode(Mode::Production) // Choice between: Test, Demo, Production ->withApiUrl($_ENV['KSEF_API_URL']) // Optional, default is set by Mode selection ->withHttpClient(new \GuzzleHttp\Client([])) // Optional PSR-18 implementation, default is set by Psr18ClientDiscovery::find() ->withLogger(new \Monolog\Logger(...)) // Optional PSR-3 implementation, default is set by PsrDiscovery\Discover::log() ->withLogPath($_ENV['PATH_TO_LOG_FILE'], $_ENV['LOG_LEVEL']) // Optional, level: null disables logging ->withSessionToken($_ENV['SESSION_TOKEN']) // Optional, if present, auto authorization is skipped ->withApiToken($_ENV['KSEF_KEY']) // Required for API Token authorization ->withKSEFPublicKeyPath($_ENV['PATH_TO_KSEF_PUBLIC_KEY']) // Required (including for API Token authorization and encryption), you can find it on https://ksef.mf.gov.pl ->withCertificatePath($_ENV['PATH_TO_CERTIFICATE'], $_ENV['CERTIFICATE_PASSPHRASE']) // Required .p12 file for Certificate authorization ->withEncryptionKey(EncryptionKeyFactory::makeRandom()) // Optional for online resources, required for batch resources. Remember to save this value! ->withNIP('NIP_NUMBER') // Required for Mode::Production and Mode::Demo if you want to use auto authorization, optional for Mode::Test ->build();
Auto mapping
Each resource supports mapping through both an array and a DTO, for example:
use N1ebieski\KSEFClient\Requests\Common\Status\StatusRequest; use N1ebieski\KSEFClient\Requests\ValueObjects\ReferenceNumber; $commonStatus = $client->common()->status(new StatusRequest( referenceNumber: ReferenceNumber::from('20250508-EE-B395BBC9CD-A7DB4E6095-BD') ))->object();
or:
$commonStatus = $client->common()->status([ 'referenceNumber' => '20250508-EE-B395BBC9CD-A7DB4E6095-BD' ])->object();
Authorization
Auto authorization via API Token
use N1ebieski\KSEFClient\ClientBuilder; $client = new ClientBuilder() ->withApiToken($_ENV['KSEF_KEY']) ->withKSEFPublicKeyPath($_ENV['PATH_TO_KSEF_PUBLIC_KEY']) ->withNIP('NIP_NUMBER') ->build(); // Do something with the available resources $client->online()->session()->terminate();
Auto authorization via certificate .p12
use N1ebieski\KSEFClient\ClientBuilder; $client = new ClientBuilder() ->withCertificatePath($_ENV['PATH_TO_CERTIFICATE'], $_ENV['CERTIFICATE_PASSPHRASE']) ->withKSEFPublicKeyPath($_ENV['PATH_TO_KSEF_PUBLIC_KEY']) ->withNIP('NIP_NUMBER') ->build(); // Do something with the available resources $client->online()->session()->terminate();
Manual authorization
use N1ebieski\KSEFClient\ClientBuilder; use N1ebieski\KSEFClient\Requests\Online\Session\AuthorisationChallenge\AuthorisationChallengeRequest; use N1ebieski\KSEFClient\Requests\Online\Session\InitSigned\InitSignedXmlRequest; use N1ebieski\KSEFClient\Requests\Online\Session\DTOs\InitSessionSigned; $client = new ClientBuilder() ->withKSEFPublicKeyPath($_ENV['PATH_TO_KSEF_PUBLIC_KEY']) ->build(); $nip = 'NIP_NUMBER'; $authorisationChallengeResponse = $client->online()->session()->authorisationChallenge([ 'contextIdentifier' => [ 'subjectIdentifierByGroup' => [ 'subjectIdentifierByCompany' => $nip ] ] ])->object(); $xml = InitSessionSigned::from([ 'challenge' => $authorisationChallengeResponse->challenge, 'timestamp' => $authorisationChallengeResponse->timestamp, 'identifier' => $nip ])->toXml(); $signedXml = // Sign a xml document via Szafir, ePUAP etc. $initSignedResponse = $client->online()->session()->initSigned( new InitSignedXmlRequest($signedXml) )->object(); $client = $client->withSessionToken($initSignedResponse->sessionToken->token); // Do something with the available resources $client->online()->session()->terminate();
Resources
Common
Status
Checking the status of batch processing (with UPO after finalization)
use N1ebieski\KSEFClient\Requests\Common\Status\StatusRequest; $response = $client->common()->status( new StatusRequest(...) )->object();
Online
Session
Authorisation challenge
Initialize the authentication and authorization mechanism.
use N1ebieski\KSEFClient\Requests\Online\Session\AuthorisationChallenge\AuthorisationChallengeRequest; $response = $client->online()->session()->authorisationChallenge( new AuthorisationChallengeRequest(...) )->object();
Init token
Initializing an interactive session. KSeF public key encrypted document http://ksef.mf.gov.pl/schema/gtw/svc/online/auth/request/2021/10/01/0001/InitSessionTokenRequest
use N1ebieski\KSEFClient\Requests\Online\Session\InitToken\InitTokenRequest; $response = $client->online()->session()->initToken( new InitTokenRequest(...) )->object();
Init signed
Initializing an interactive session. Signed document http://ksef.mf.gov.pl/schema/gtw/svc/online/auth/request/2021/10/01/0001/InitSessionSignedRequest
use N1ebieski\KSEFClient\Requests\Online\Session\InitSigned\InitSignedRequest; $response = $client->online()->session()->initSigned( new InitSignedRequest(...) )->object();
or:
use N1ebieski\KSEFClient\Requests\Online\Session\InitSigned\InitSignedXmlRequest; $response = $client->online()->session()->initSigned( new InitSignedXmlRequest($signedXml) )->object();
Session status
Checking the status of current interactive processing or based on the reference number.
use N1ebieski\KSEFClient\Requests\Online\Session\Status\StatusRequest; $response = $client->online()->session()->status( new StatusRequest(...) )->object();
Terminate
Forcing the closing of an active interactive session
$response = $client->online()->session()->terminate()->object();
Invoice
Get an invoice
Invoice download.
use N1ebieski\KSEFClient\Requests\Online\Invoice\Get\GetRequest; $response = $client->online()->invoice()->get( new GetRequest(...) )->body();
Send an invoice
use N1ebieski\KSEFClient\Requests\Online\Invoice\Send\SendRequest; $response = $client->online()->invoice()->send( new SendRequest(...) )->object();
Invoice status
Checking the status of a sent invoice.
use N1ebieski\KSEFClient\Requests\Online\Invoice\Status\StatusRequest; $response = $client->online()->invoice()->status( new StatusRequest(...) )->object();
Query
Invoice
Sync
Search and filter invoices
use N1ebieski\KSEFClient\Requests\Online\Query\Invoice\Sync\SyncRequest; $response = $client->online()->query()->invoice()->sync( new SyncRequest(...) )->object();
Async
Fetch init
Initialization of invoice fetch request
use N1ebieski\KSEFClient\Requests\Online\Query\Invoice\Async\Init\InitRequest; $response = $client->online()->query()->invoice()->async()->init( new InitRequest(...) )->object();
Fetch status
Checking the status of invoice fetch request
use N1ebieski\KSEFClient\Requests\Online\Query\Invoice\Async\Status\StatusRequest; $response = $client->online()->query()->invoice()->async()->status( new StatusRequest(...) )->object();
Fetch invoices
Downloading invoice query results
use N1ebieski\KSEFClient\Requests\Online\Query\Invoice\Async\Fetch\FetchRequest; $response = $client->online()->query()->invoice()->async()->fetch( new FetchRequest(...) )->body();
Examples
Send an invoice and check for UPO
use N1ebieski\KSEFClient\ClientBuilder; use N1ebieski\KSEFClient\Support\Utility; use N1ebieski\KSEFClient\Testing\Fixtures\Requests\Online\Invoice\Send\SendFakturaSprzedazyTowaruRequestFixture; use N1ebieski\KSEFClient\ValueObjects\Mode; $client = new ClientBuilder() ->withMode(Mode::Test) ->withApiToken($_ENV['KSEF_KEY']) ->withLogPath(__DIR__ . '/../var/logs/monolog.log') ->withKSEFPublicKeyPath(__DIR__ . '/../config/keys/publicKey.pem') ->build(); Utility::retry(function () use ($client) { $statusResponse = $client->online()->session()->status()->object(); if ($statusResponse->processingCode === 315) { return $statusResponse; } }); // Send an invoice $sendResponse = $client->online()->invoice()->send( new SendFakturaSprzedazyTowaruRequestFixture()->withTodayDate()->data )->object(); // Check status of invoice generation Utility::retry(function () use ($client, $sendResponse) { $statusResponse = $client->online()->invoice()->status([ 'invoiceElementReferenceNumber' => $sendResponse->elementReferenceNumber ])->object(); if ($statusResponse->processingCode === 200) { return $statusResponse; } }); // Close session (only then will the UPO be generated) $client->online()->session()->terminate(); // We don't need to authorize for UPO $client = new ClientBuilder() ->withMode(Mode::Test) ->withKSEFPublicKeyPath(__DIR__ . '/../config/keys/publicKey.pem') ->build(); // Check status of UPO generation $commonStatus = Utility::retry(function () use ($client, $sendResponse) { $commonStatus = $client->common()->status([ 'referenceNumber' => $sendResponse->referenceNumber ])->object(); if ($commonStatus->processingCode === 200) { return $commonStatus; } }); $xml = base64_decode($commonStatus->upo);
Fetch invoices using encryption key
use N1ebieski\KSEFClient\ClientBuilder; use N1ebieski\KSEFClient\Factories\EncryptionKeyFactory; use N1ebieski\KSEFClient\Support\Utility; use N1ebieski\KSEFClient\Testing\Fixtures\Requests\Online\Query\Invoice\Async\Init\InitRequestFixture; use N1ebieski\KSEFClient\ValueObjects\Mode; $encryptionKey = EncryptionKeyFactory::makeRandom(); $client = new ClientBuilder() ->withMode(Mode::Test) ->withApiToken($_ENV['KSEF_KEY']) ->withKSEFPublicKeyPath(__DIR__ . '/../config/keys/publicKey.pem') ->withLogPath(__DIR__ . '/../var/logs/monolog.log') ->withEncryptionKey($encryptionKey) ->build(); Utility::retry(function () use ($client) { $statusResponse = $client->online()->session()->status()->object(); if ($statusResponse->processingCode === 315) { return $statusResponse; } }); // Firstly we need to init the preparation of the invoice package based on the query parameters $initResponse = $client->online()->query()->invoice()->async()->init( new InitRequestFixture()->withRange('-2 weeks')->withSubjectType('subject1')->data )->object(); // Preparing invoice packs is asynchronous so it's better to save SessionToken // and queryElementReferenceNumber, go for coffee and come back in a while :) $sessionToken = $client->getSessionToken(); $queryElementReferenceNumber = $initResponse->elementReferenceNumber; // Ok after a few minutes... $client = new ClientBuilder() ->withMode(Mode::Test) ->withSessionToken($sessionToken) ->withKSEFPublicKeyPath(__DIR__ . '/../config/keys/publicKey.pem') ->withLogPath(__DIR__ . '/../var/logs/monolog.log') ->withEncryptionKey($encryptionKey) ->build(); // Check if packages are ready to download $statusResponse = Utility::retry(function () use ($client, $queryElementReferenceNumber) { $statusResponse = $client->online()->query()->invoice()->async()->status([ 'queryElementReferenceNumber' => $queryElementReferenceNumber ])->object(); if ($statusResponse->processingCode === 200) { return $statusResponse; } }); // Downloading... foreach ($statusResponse->partList as $part) { $fetchResponse = $client->online()->query()->invoice()->async()->fetch([ 'queryElementReferenceNumber' => $queryElementReferenceNumber, 'partElementReferenceNumber' => $part->partReferenceNumber ])->body(); file_put_contents(__DIR__ . "/../var/zip/{$part->partReferenceNumber}.zip", $fetchResponse); } $client->online()->session()->terminate();
Testing
The package uses unit tests via PHPUnit.
TestCase is located in the location of src/Testing/AbstractTestCase
Fake request and responses fixtures for resources are located in the location of src/Testing/Fixtures/Requests
Run all tests:
composer install
vendor/bin/phpunit
Roadmap
- Batch endpoints
- Prepare the package for release candidate
Special thanks
Special thanks to:
- all the helpful people on the 4programmers.net forum
- authors of the repository grafinet/xades-tools for the Xades document signing tool