Krok po kroku — od certyfikatu do pierwszej faktury w Krajowym Systemie e-Faktur
Cześć! Na samym wstępie muszę się przyznać — jestem wieloletnim i oddanym fanem systemu LMS :D Poniższy projekt powstawał momentami w bólach, prawdopodobnie bywa pełen chaosu i z pewnością nie wygra konkursu na najlepiej udokumentowany kod w historii. Początkowo tworzyłem to wyłącznie na swoje własne, absolutnie prywatne potrzeby, żeby jakoś okiełznać nadchodzącego potwora zwanego KSeF. Nie ukrywam! W dużej mierze pomogła tutaj sztuczna inteligencja...
Jednak, ulegając presji tłumu i na prośbę wielu z Was, postanowiłem podzielić się tym dziełem zupełnie nieodpłatnie. Musicie jednak wiedzieć o jednym: nie działam w porozumieniu z autorami ani programistami oficjalnego LMS-a. Oznacza to, że wszelkie ich poprawki i aktualizacje mogą znacząco wpłynąć na (nie)działanie niniejszej integracji. Mój projekt nie jest oficjalną częścią systemu. Oczywiście bardzo się starałem, by całość była jak najbardziej spójna i niczego nie zepsuła, ale korzystacie z tego na własną odpowiedzialność! Oczywiście udostępniony kod może być także inspiracją dla innych, śmiało korzystacjie! Zachęcam również do dzielenia się :P
| Składnik | Minimalna wersja | Sprawdź |
|---|---|---|
| LMS | 28-git, migracje ≥ 2026033100 | SELECT keyvalue FROM dbinfo WHERE keytype='dbversion' |
| PHP | 8.1+ | php -v |
| Composer | dowolna | composer --version |
| Biblioteka KSeF | n1ebieski/ksef-php-client | ls vendor/n1ebieski/ |
| Certyfikat KSeF | Plik .p12 tworzony z plików .crt, .key oraz hasła wygenerowanych w portalu KSeF | — |
| ext-zip | wymagane przez PHP | php -m | grep zip |
Jeśli brak biblioteki ksef-php-client:
cd /sciezka/do/lms
composer require n1ebieski/ksef-php-client
Aby uwierzytelnić się w systemie KSeF, potrzebujesz certyfikatu. W portalu KSeF Ministerstwa Finansów (Aplikacja Podatnika → Certyfikaty) generujesz parę plików o rozszerzeniach .crt oraz .key, a także podajesz dla nich hasło, które musisz zapamiętać i wpisać do pliku lms.ini. Z tych danych należy wygenerować docelowy plik .p12 (PKCS#12) przy użyciu poniższego polecenia:
# Połącz .crt i .key w plik .p12 openssl pkcs12 -export \ -in certyfikat.crt \ -inkey certyfikat.key \ -out /etc/lms/ksef_cert.p12 \ -passout pass:TwojeHaslo123! # Zabezpiecz plik chmod 640 /etc/lms/ksef_cert.p12 chown root:apache /etc/lms/ksef_cert.p12
Edytuj plik /etc/lms/lms.ini i dodaj / uzupełnij poniższe sekcje:
[phpui] plugins = KSeFSubmit # dodaj do istniejącej listy pluginów [ksef] environment = prod # prod / test / demo auth_method = certificate # certificate (zalecane) lub token certificate = /etc/lms/ksef_cert.p12 password = "TwojeHaslo123!" boundary_date = 2026/04/01 # data od której wysyłamy faktury encryption_key = Wpisz32ZnakowyLosowyKluczAES256! encryption_iv = 16ZnakowIV1234 show_rozliczenie = 0 # <- tożsame z brakiem wpisu debug_log = /var/log/lms-ksef-gui.log [invoices] issuer = 'Dział Obsługi Klienta' info_box_text = 'Przelew 14 dni\nKonto: XX XXXX XXXX XXXX XXXX XXXX XXXX' info_box_text_b2c = 'Dziękujemy za korzystanie z naszych usług' [sendinvoices] # faktury B2B (firmy) sender_email = biuro@twojafirma.pl sender_name = Twoja Firma Sp. z o.o. mail_subject = Faktura VAT %invoice mail_body = /etc/lms/mail_body_b2b.txt mail_format = html invoice_filename = Faktura_%number [sendinvoices-b2c] # faktury B2C (osoby fizyczne) sender_email = biuro@twojafirma.pl sender_name = Twoja Firma Sp. z o.o. mail_subject = Faktura %invoice mail_body = /etc/lms/mail_body_b2c.txt mail_format = html invoice_filename = Faktura_%number
# encryption_key — 32 znaki losowe openssl rand -base64 24 | tr -d '=+/' | head -c 32 # encryption_iv — 16 znaków losowych openssl rand -base64 12 | tr -d '=+/' | head -c 16
0 lub nie dodawaj go wcale. Jeśli chcesz go mieć, ustaw wartość na 1.Rozpakuj paczkę i skopiuj pliki do katalogu LMS:
# Uruchom z katalogu z rozpakowaną paczką PACK=/tmp/lms-ksef-pack LMS=/sciezka/do/lms cp $PACK/lib/KSeF/KSeF.php $LMS/lib/KSeF/KSeF.php cp $PACK/lib/LMSDocuments/LMSTcpdfInvoice.php $LMS/lib/LMSDocuments/LMSTcpdfInvoice.php mkdir -p $LMS/plugins/KSeFSubmit/handlers $LMS/plugins/KSeFSubmit/lib cp $PACK/plugins/KSeFSubmit/KSeFSubmit.php $LMS/plugins/KSeFSubmit/ cp $PACK/plugins/KSeFSubmit/handlers/KSeFSubmitHandler.php $LMS/plugins/KSeFSubmit/handlers/ cp $PACK/plugins/KSeFSubmit/lib/KSeFApiService.php $LMS/plugins/KSeFSubmit/lib/ cp $PACK/bin/lms-ksef.php $LMS/bin/ cp $PACK/bin/lms-ksef-download.php $LMS/bin/ cp $PACK/bin/lms-ksef-sync.php $LMS/bin/ cp $PACK/bin/lms-ksef-bulk-correct.php $LMS/bin/ chmod +x $LMS/bin/lms-ksef*.php
Lokalizacja: getInvoiceXml(), pierwsza linia funkcji
Problem: lib/locale/Localisation.php (linia ~246) ustawia LC_NUMERIC na pl_PL.UTF-8 podczas inicjalizacji LMS. Skutek: sprintf('%.2f', 35.5) zwraca 35,50. KSeF odrzuca XML z przecinkami w kwotach (walidacja XSD).
public function getInvoiceXml(array $invoice)
{
setlocale(LC_NUMERIC, 'C'); // poprawka 1
if (!isset($this->divisions[$invoice['divisionid']])) {
Lokalizacja: getInvoiceXml(), sekcja <Rozliczenie> (~linia 1001)
Problem: Oryginał pobiera getCustomerBalance() i wpisuje "dotychczasową niedopłatę" do XML.
Rozwiązanie: Sterowanie przez lms.ini:
[ksef] ; show_rozliczenie = 0 ← domyślnie gdy brak klucza = wyłączone ; show_rozliczenie = 1 ← oryginalne zachowanie z saldem
Gdy show_rozliczenie = 0 lub brak klucza — sekcja w XML zawiera tylko bieżącej kwotę faktury:
<Rozliczenie> <Obciazenia> <Kwota>2595.30</Kwota> <Powod>dokument LMS_002/04/2026</Powod> </Obciazenia> <SumaObciazen>2595.30</SumaObciazen> <SumaOdliczen>0.00</SumaOdliczen> <!-- wymagane przez XSD --> <DoZaplaty>2595.30</DoZaplaty> </Rozliczenie>
Przejdź do katalogu głównego LMS, odśwież mapę klas Composera, a następnie wykonaj migracje bazy danych:
# Przeładuj klasy (wymagane po dodaniu nowych plików wtyczki) composer dump-autoload # Zaktualizuj bazę i wyczyść cache szablonów php bin/lms-upgradedb.php -C /etc/lms/lms.ini rm -rf templates_c/*
ksefinvoices, ksefdocuments, ksefbatchsessions i powiązane.
Jeśli wszystkie kroki przebiegły prawidłowo, w systemie LMS powinna pojawić się nowa wtyczka w Konfigruacja->Wtyczki, którą należy włączyć, jak na poniższym obrazku:
Pierwsze uruchomienie — pobierz wszystkie faktury które wystawcy przesłali do KSeF na Twój NIP. Skrypt używa Export API (jeden zaszyfrowany ZIP zamiast setek osobnych zapytań):
cd /sciezka/do/lms/bin
# Najpierw dry-run — sprawdź ile faktur zostanie pobranych
./lms-ksef-sync.php -C /etc/lms/lms.ini --from=2026/01/01 --dry-run
# Właściwy sync (zakres maksymalnie 3 miesiące, podziel jeśli większy)
./lms-ksef-sync.php -C /etc/lms/lms.ini --from=2026/01/01 --to=2026/03/31
./lms-ksef-sync.php -C /etc/lms/lms.ini --from=2026/04/01
# Dry-run — sprawdź ile faktur do wysłania ./lms-ksef.php -C /etc/lms/lms.ini --dry-run # Właściwa wysyłka ./lms-ksef.php -C /etc/lms/lms.ini
# crontab -e # Wysyłka faktur sprzedaży — 1. dnia każdego miesiąca 0 2 1 * * /sciezka/lms/bin/lms-payments.php -C /etc/lms/lms.ini 10 2 1 * * php /sciezka/lms/bin/lms-ksef.php -C /etc/lms/lms.ini >> /var/log/lms-ksef.log 2>&1 30 2 1 * * /sciezka/lms/bin/lms-sendinvoices.php -C /etc/lms/lms.ini --ksef 35 2 1 * * /sciezka/lms/bin/lms-sendinvoices.php -C /etc/lms/lms.ini --without-ksef --section=sendinvoices-b2c # Sprawdzanie statusu sesji batch (co godzinę) 0 * * * * php /sciezka/lms/bin/lms-ksef.php -C /etc/lms/lms.ini --status-only >> /var/log/lms-ksef.log 2>&1 # Pobieranie faktur zakupowych z KSeF (codziennie o 6:15) 15 6 * * * php /sciezka/lms/bin/lms-ksef-download.php -C /etc/lms/lms.ini -q >> /var/log/lms-ksef-download.log 2>&1
| lms-ksef.php | Wysyła faktury sprzedaży do KSeF w paczce batch | 1. dnia miesiąca |
| lms-ksef.php --status-only | Pobiera numery KSeF dla wysłanych faktur | co godzinę |
| lms-sendinvoices.php --ksef | Wysyła e-mail z PDF do klientów B2B (po otrzymaniu numeru KSeF) | 1. dnia miesiąca |
| lms-sendinvoices.php --without-ksef | Wysyła e-mail z PDF do klientów B2C (osoby fizyczne) | 1. dnia miesiąca |
| lms-ksef-download.php | Pobiera nowe faktury zakupowe od dostawców | codziennie |
Szanowni Państwo,<br> <br> w załączniku faktura VAT <b>%invoice</b>.<br> Numer KSeF: <b>%ksef-number</b><br> Kwota do zapłaty: <b>%value PLN</b><br> Numer konta: %bankaccount<br> <br> Z poważaniem
Szanowny Kliencie,<br> <br> w załączniku faktura <b>%invoice</b>.<br> Kwota do zapłaty: <b>%value PLN</b><br> Numer konta: %bankaccount<br> <br> Dziękujemy!
| Zmienna | Znaczenie |
|---|---|
%invoice | Numer faktury (np. LMS_001/04/2026) |
%ksef-number | Numer KSeF (po potwierdzeniu przez MF) |
%value | Kwota brutto |
%bankaccount | Numer konta bankowego |
%customerid | ID klienta w LMS |
%pin | PIN klienta |
Przed wysyłką do KSeF warto sprawdzić czy XML faktury wygląda poprawnie. Skrypt gen-xml.php generuje XML bez wysyłki:
cd /sciezka/do/lms php bin/gen-xml.php -C /etc/lms/lms.ini ID_FAKTURY # XML zapisywany jest do /tmp/ID_FAKTURY.xml cat /tmp/ID_FAKTURY.xml
Sprawdź w wygenerowanym XML:
2595.30<Rozliczenie> zawiera kwotę faktury i <DoZaplaty><Podmiot2> z NIPem| Sytuacja | Rozwiązanie |
|---|---|
| Faktury wysłane, brak numerów KSeF (status 100) | Poczekaj — skrypt --status-only w cronie odpytuje co godzinę. Można też uruchomić ręcznie: ./lms-ksef.php --status-only |
| Faktury z błędem (status 400) | ./lms-ksef.php --retry-errors |
| Błąd 429 Too Many Requests przy pobieraniu | MF ma limit ~64 zapytań/h. Odczekaj godzinę. Do historycznego sync używaj lms-ksef-sync.php zamiast lms-ksef-download.php |
| Błędna kwota w KSeF (niedopłata) | Upewnij się że w lms.ini NIE MA wpisu show_rozliczenie = 1. Wystaw korekty: ./lms-ksef-bulk-correct.php --dry-run |
| Awaria MF (kilka dni) | Faktury są w kolejce — skrypt automatycznie nadrobi po powrocie MF |
| Błąd certyfikatu | openssl pkcs12 -in /etc/lms/ksef_cert.p12 -info — sprawdź czy certyfikat jest ważny i hasło się zgadza |
Pobierz wymagane paczki do prawidłowej instalacji i integracji:
Informacja: Załączony zip z LMS-MASTER z GIT na dzień 03.04.2026 23:50. Wszystkie pliki z paczki KSEF_PACK.ZIP działają stabilnie z tą wersją. W miarę możliwości postaram się uaktualniać pliki, jeśli będą się pojawiały znaczące zmiany w repozytorium GITHUB (https://github.com/chilek/lms). Plik który wymaga drobnych zmian do poprawnego działania skryptów integrujących oraz pluginu, to lib/KSeF/KSeF.php.
cd /sciezka/do/lms && bash check-netlink-patches.sh
Uwagi lub zapytania dotyczące działania integracji można zgłaszać na adres: cocoban78@gmail.com.