n1ebieski/ksef-php-client

PHP API client that allows you to interact with the API Krajowego Systemu e-Faktur

0.2.1 2025-05-31 07:07 UTC

This package is auto-updated.

Last update: 2025-05-31 07:10:54 UTC


README

1920x810

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

  1. Batch endpoints
  2. Prepare the package for release candidate

Special thanks

Special thanks to: