PHP code example of b44x / ksef-php

1. Go to this page and download the library: Download b44x/ksef-php library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

b44x / ksef-php example snippets


use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use B4x\Ksef\Auth\CertificateCredentials;
use B4x\Ksef\Auth\ContextIdentifier;
use B4x\Ksef\Environment;
use B4x\Ksef\Invoice\Address;
use B4x\Ksef\Invoice\Buyer;
use B4x\Ksef\Invoice\BuyerIdentifier;
use B4x\Ksef\Invoice\Invoice;
use B4x\Ksef\Invoice\InvoiceLine;
use B4x\Ksef\Invoice\Seller;
use B4x\Ksef\Invoice\VatRate;
use B4x\Ksef\KsefClient;
use B4x\Ksef\Support\Nip;

$factory = new HttpFactory();
$ksef = KsefClient::builder()
    ->environment(Environment::Test)                       // there is no default: choose explicitly
    ->httpClient(new Client(['timeout' => 30, 'connect_timeout' => 10]), $factory, $factory)
    ->context(ContextIdentifier::nip('5265877635'))        // on whose behalf you work
    ->credentials(CertificateCredentials::fromPemFiles('/secure/cert.pem', '/secure/key.pem'))
    ->build();

$invoice = Invoice::builder()
    ->number('FV/2026/06/001')
    ->issueDate('2026-06-01')
    ->seller(new Seller(Nip::of('5265877635'), 'Example Seller sp. z o.o.', Address::poland('ul. Prosta 1', '00-001 Warszawa')))
    ->buyer(new Buyer(BuyerIdentifier::nip(Nip::of('1234563218')), 'Sample Buyer S.A.'))
    ->addLine(InvoiceLine::of('Consulting', '10', 'h', '150.00', VatRate::Rate23))
    ->build();                                             // throws ValidationException listing every problem

$submission = $ksef->sendInvoice($invoice);                // accepted for processing – NOT yet approved
$result = $ksef->waitForInvoice($submission)->assertAccepted();

echo $result->ksefNumber;                                  // e.g. 5265877635-20260601-0100001AF629-15
$upo = $ksef->invoiceUpo($submission);                     // signed confirmation of receipt (XML)

// 1. Certificate (qualified signature/seal, KSeF certificate, or a self-signed one on TEST).
//    The AuthTokenRequest is signed with XAdES-BES (RSA >= 2048 bit or EC P-256+).
CertificateCredentials::fromPemFiles($certPath, $keyPath, $optionalPassphrase);
CertificateCredentials::fromPkcs12(file_get_contents($p12), $password);

// 2. KSeF token (generated in the KSeF application or with $ksef->createToken()).
//    Sent as RSA-OAEP(SHA-256) encrypted "token|challengeTimestampMs".
new KsefTokenCredentials(getenv('KSEF_TOKEN'));

$ksef = KsefClient::builder()->...->credentials(new ManualCredentials())->tokenStore($store)->build();

$request = $ksef->createAuthenticationRequest();            // an AuthTokenRequest to sign; valid 10 minutes
// ... the person signs $request->xml (XAdES, enveloped) with their tool and you get the signed document back ...
$ksef->authenticateWithSignedRequest($signedXml);           // KSeF verifies it; the client is logged in

KsefClient::builder()
    ->environment(...)            // or ->baseUrl('https://...')
    ->httpClient($client, $requestFactory, $streamFactory)
    ->context(...)->credentials(...)
    ->logger($psr3Logger)         // optional; never receives secrets or document contents
    ->retryPolicy(new RetryPolicy(maxAttempts: 3, baseDelaySeconds: 0.5, maxDelaySeconds: 30.0))
    ->polling(new PollingPolicy(timeoutSeconds: 120.0))      // default for waitFor*()
    ->authenticationPolling(...)->allowedIps(...)->publicKeyProvider(...)->clock(...)->sleeper(...)
    ->build();

$invoice = Invoice::builder()
    ->number('FV/2026/06/002')->issueDate('2026-06-01')->saleDate('2026-05-31')->issuePlace('Warszawa')
    ->currency('EUR')->exchangeRate('4.3210')                // foreign currency: VAT in PLN is derived
    ->seller($seller)->buyer($buyer)
    ->addLine(InvoiceLine::of('Software licence', '1', 'szt.', '1000.00', VatRate::Rate23, 'EUR'))
    ->addLine(InvoiceLine::of('Training', '2', 'h', '80.00', VatRate::Exempt, 'EUR'))
    ->annotations(new Annotations(splitPayment: true, exemptionBasis: 'Art. 43 ust. 1 pkt 29 lit. b ustawy o VAT'))
    ->payment(Payment::dueOn(new DateTimeImmutable('2026-06-15'), PaymentMethod::BankTransfer, ['PL61109010140000071219812874']))
    ->build();

// Advance invoice (ZAL): the tax is taken out of the gross payment; the lines describe the order.
Invoice::builder()->...->advance(new AdvancePayment(Money::pln('1230.00'), VatRate::Rate23, $paidOn))
    ->addLine(InvoiceLine::of('Custom software', '1', 'szt.', '5000.00', VatRate::Rate23))->build();

// Final invoice (ROZ): the lines carry the full sale; P_13/P_14/P_15 show what remains after the advances.
Invoice::builder()->...->settlement(new Settlement([AdvanceInvoiceReference::ksef($advanceKsefNumber)], Money::pln('1230.00')))
    ->addLine(...)->build();

// Correction of an advance invoice (KOR_ZAL): combine correction() and advance(); the advance amount is the
// CHANGE of the payment (negative when it decreases), lines are the order before (asBefore) and after.
Invoice::builder()->...->correction($correction)->advance(new AdvancePayment(Money::pln('-615.00'), VatRate::Rate23, $paidOn))
    ->addLine($orderLine->asBefore())->addLine($newOrderLine)->build();

// Correction of a final invoice (KOR_ROZ): combine correction() and settlement(); before/after lines.
Invoice::builder()->...->correction($correction)->settlement($settlement)->addLine($line->asBefore())->addLine($newLine)->build();

// Payments received before delivery, shown on the final invoice (ZaliczkaCzesciowa); P_15 stays the total.
Invoice::builder()->...->settlement($settlement)->addPriorPayment(new PriorPayment($receivedOn, Money::pln('500.00')))->addLine(...)->build();

// Collective correction for a period (art. 106j(3)): no lines, differences per rate.
Invoice::builder()->...->correction(new Correction($corrected, period: '2026-04-01 - 2026-06-30', amounts: [CorrectionAmount::of(VatRate::Rate23, '-100.00', '-23.00')]))->build();

// Simplified invoice (UPR): up to PLN 450 / EUR 100, buyer identified by NIP.
Invoice::builder()->...->simplified()->addLine(...)->build();

$document = InvoiceDocument::fromXml($xml);   // size, UTF-8/BOM, DOCTYPE, PI, namespace and XSD checks
$ksef->sendInvoice($document);

$submission = $ksef->sendInvoice($invoice);                  // one invoice, own short-lived session

$session = $ksef->openOnlineSession();                       // many invoices, one session (<= 12 h, <= 10,000 invoices)
$a = $session->send($invoiceA);
$b = $session->send($invoiceB);
$session->close();                                           // starts the aggregate UPO generation
$status = $session->waitUntilFinished();                     // SessionStatus incl. UPO page references

$batch = $ksef->sendBatch($invoices);                         // iterable of Invoice | InvoiceDocument | XML string
$status = $ksef->waitForSession($batch->sessionReference);    // SessionStatus (aggregate counts, UPO pages)
$page = $ksef->listSessionInvoices($batch->sessionReference);     // per invoice: ksefNumber, status, invoiceHash
// $batch->invoiceHashes lets you map results back to your own documents.

$ksef->grantPersonPermissions(PersonSubject::byPesel($pesel, 'Anna', 'Nowak'), [Permission::InvoiceRead, Permission::InvoiceWrite], 'accountant');
$ksef->grantEntityPermissions(EntitySubject::nip(Nip::of('5265877635'), 'Partner sp. z o.o.'), ['InvoiceRead' => true]);   // may delegate: true
foreach ($ksef->listPersonPermissions(grantedByMe: true)->items as $grant) { /* $grant->id, ->scope, ->holder */ }
$ksef->revokePermission($grant->id);

// Entity-level authorisations: self-invoicing, RR, tax representative, Peppol
$ksef->grantAuthorization(EntitySubject::nip(Nip::of('5265877635'), 'Partner sp. z o.o.'), EntityAuthorizationType::SelfInvoicing, 'self-billing');
$ksef->listAuthorizations(AuthorizationDirection::Granted);   // ...::Received
$ksef->revokeAuthorization($authorization->id);           // not revokePermission(): different endpoint

// Accounting office: a person works in the contexts of your customers
$ksef->grantIndirectPermissions($person, [EntityPermissionType::InvoiceRead], 'staff', IndirectTarget::allPartners());

// Subordinate units (local government, VAT groups) and EU entities
$ksef->grantSubunitAdministrator($person, SubunitContext::internalId('5265877635-12345'), 'branch admin');
$ksef->grantEuEntityAdministrator(EuEntitySubject::person(PersonSubject::byFingerprint(...)), new EuEntity('5265877635-DE123456789', 'Muster GmbH', 'Berlin'), 'admin');
$ksef->grantEuEntityRepresentative(EuEntitySubject::entity($fingerprint, 'Seal GmbH', 'Berlin'), [EuEntityPermissionType::InvoiceWrite], 'rep');

// Reading: listSubunitAdministrators(), listEuEntityPermissions(), listEntityRoles(), listSubordinateEntities(), attachmentStatus()

// Decrypted ZIP of {ksefNumber}.xml + _metadata.json; page through time with continueFrom (PermanentStorage).
$package = $ksef->exportInvoices(InvoiceSubjectType::Buyer, InvoiceDateType::PermanentStorage, $from, null, '/tmp/export.zip');
if ($package->isTruncated) { $from = $package->continueFrom; /* export again */ }

$ksef->contextLimits();   // max invoices / sizes per session type
$ksef->rateLimits();      // ['invoiceSend' => RateLimit(perSecond, perMinute, perHour), ...]
$ksef->listAuthSessions();    // active logins; $ksef->revokeAuthSession($ref) or revokeAuthSession() for the current one

// One payment reference for many invoices of the same seller
$id = $ksef->createCollectiveIdentifier([new CollectiveInvoice($ksefNumber1, Money::pln('123.00'), 'May'), new CollectiveInvoice($ksefNumber2)]);
$page = $ksef->listCollectiveIdentifiers($from, $to);                      // paged; pass $page->continuationToken for the next page
$ksef->listCollectiveIdentifierInvoices([$id]);                            // the invoices (payment details only for entitled parties)
$ksef->listCollectiveIdentifiersOf($ksefNumber1);                          // which identifiers an invoice belongs to

$ksef->listPeppolProviders();                                             // registered Peppol service providers
$ksef->subjectLimits();                                               // certificate enrolment / certificate limits of the taxpayer

$cert = $ksef->requestCertificate('billing service', CertificateType::Authentication);   // KeyType::EcP256
$credentials = $cert->toCredentials();               // log in with it from now on (no OCSP/CRL delay)

$offline = $ksef->requestCertificate('offline qr', CertificateType::Offline);
$signer = $offline->toOfflineCertificate();          // for VerificationLinks::certificateUrl()

$ksef->certificateLimits();  $ksef->searchCertificates(CertificateType::Offline);  $ksef->revokeCertificate($serial);

$links = new VerificationLinks(Environment::Production);
$url = $links->invoiceUrl($sellerNip, $issueDate, $document);            // KOD I, every invoice
$label = $links->label($ksefNumber);                                      // KSeF number, or "OFFLINE"
$url2 = $links->certificateUrl($context, $sellerNip, $document->hash(),   // KOD II, offline invoices only
    new OfflineCertificate($certPem, $keyPem));                           // KSeF "Offline" certificate

$renderer = new QrCodeRenderer(300);
$svg = $renderer->svg($url, $links->label($ksefNumber));                  // image with "5265877635-20260601-..." or "OFFLINE" below
$svg2 = $renderer->svg($url2, $links->certificateLabel());                // KOD II is captioned "CERTYFIKAT"

$invoice = $ksef->downloadInvoice($ksefNumber)->read();       // or (new InvoiceReader())->read($xmlOrDocument)

$invoice->number;  $invoice->issueDate;  $invoice->currency;  $invoice->type;            // InvoiceType
$invoice->seller->name;  $invoice->seller->taxId();  $invoice->buyer->address?->line1;
foreach ($invoice->lines as $line) { $line->name; $line->quantity; $line->netAmount; $line->vatRate; }
foreach ($invoice->taxSummary as $row) { $row->group; $row->net; $row->tax; }          // P_13_x / P_14_x
$invoice->total;  $invoice->payment?->dueDates;  $invoice->payment?->bankAccounts;
$invoice->correction?->correctedInvoices;  $invoice->advanceInvoices;  $invoice->hasAttachment;
$invoice->document->xml;                                                              // the original, untouched

$report = $ksef->availability();            // the Ministry's public availability service ("Latarnia"), no authentication
$report->status;                            // AvailabilityStatus: Available | Maintenance | Failure | TotalFailure
$report->isFailure();                       // true when invoices cannot be sent: issue offline invoices
$ksef->availabilityMessages();              // announcements: title, text, start, end (null while unknown)

->tokenStore(new FileTokenStore('/var/lib/myapp/ksef'))              // owner-only files in a private directory
->tokenStore(new Psr16TokenStore($privateCache))                     // any PSR-16 cache (composer 

$upo = $ksef->invoiceUpo($submission);
$upo->verifyHash();                                  // the hash KSeF announced matches the document
$check = $upo->verifySignature();                    // the Ministry's XML signature: valid, signer, certificate dates
$check = $upo->verifySignature($ministryCertificatePem);   // ... and `$check->trusted` when the signer is (or was issued by) that certificate

$invoice = $ksef->invoiceStatus($submission);          // one request
$invoice = $ksef->waitForInvoice($submission);         // polls until terminal; may be a rejection

$invoice->status->isAccepted();                        // code 200
$invoice->status->isRejected();                        // code >= 400
$invoice->status->isDuplicate();                       // 440 -> originalKsefNumber()
$invoice->assertAccepted();                            // throws InvoiceRejectedException with KSeF's explanation
$invoice->assertStored();                              // like assertAccepted(), but a 440 duplicate counts (already in KSeF)

// An accepted invoice becomes downloadable once `permanentStorageDate` is set:
$ksef->waitForInvoice($submission, null, untilStored: true);

$upo = $ksef->invoiceUpo($submission);                 // hash announced by KSeF is verified ($upo->verifyHash())
$status = $ksef->sessionStatus($sessionRef);           // $status->upoPages -> $ksef->sessionUpo($sessionRef, $page->referenceNumber)

$invoice = $ksef->downloadInvoice($ksefNumber);
//   Wait with waitForInvoice(..., untilStored: true) first. As a safety net, a download that still answers
//   HTTP 406 (observed for a few seconds after acceptance) raises InvoiceNotAvailableException, or is awaited
//   when you pass a PollingPolicy as second argument.

$page = $ksef->searchInvoices(InvoiceSubjectType::Buyer, InvoiceDateType::PermanentStorage, $from, $to);

// Laravel service provider / Symfony service definition: bind KsefClient as a shared service built
// with your framework's PSR-18 client, PSR-3 logger and a secret from your configuration.

$data = new TestData($http, $factory, $factory);
$data->grantPermissions($contextNip, TestIdentifierType::Pesel, $pesel, [Permission::InvoiceRead], 'accountant for tests');
$data->blockContext(ContextIdentifier::nip('5265877635'));         // see how your integration reacts to a refused login
$ksef->testData()->setRateLimits(['invoiceSend' => new RateLimit(1, 10, 100)]);   // provoke 429 answers
$ksef->testData()->setSessionLimits(new SessionLimits(5, 1, 2), new SessionLimits(7, 1, 2));   // tiny sessions
$ksef->testData()->resetRateLimits();  $ksef->testData()->resetSessionLimits();
bash
composer 
bash
git clone https://github.com/b44x/ksef-php.git && cd ksef-php && composer install
php examples/01-send-invoice.php