Czechy przywracają elektroniczną ewidencję sprzedaży. W ramach EET 2.0 (Elektronická evidence tržeb) punkt sprzedaży zgłasza każdą sprzedaż organowi podatkowemu w chwili, gdy do niej dochodzi, a organ podatkowy odpowiada kodem potwierdzenia, czyli pok, który jest dowodem zgłoszenia sprzedaży. Raportowanie rozpoczyna się 1 stycznia 2027 r., a środowisko testowe playground, w którym buduje się i testuje kasy, jest dostępne już teraz.
sgcSign ma do tego nowy komponent, TsgcEETClient. Waliduje sprzedaż, buduje komunikat, podpisuje go certyfikatem podatnika, wysyła go, sprawdza podpis potwierdzenia i zwraca wynik. Ten wpis wyjaśnia, czego wymaga EET 2.0, jak komponent realizuje pełny cykl żądania i odpowiedzi, oraz pokazuje kod Delphi dla pierwszej sprzedaży, kolejki offline i zweryfikowanego potwierdzenia.
Zarejestrowana sprzedaż z demo Delphi, od trybu weryfikacji do prawdziwego pok. Również w serwisie YouTube.
EET 2.0 to nowy protokół, a nie aktualizacja
Jeśli masz kasę zbudowaną dla pierwszego systemu EET, zacznij od zera. Interfejs danych w wersji 4.1 nie jest zgodny ze starą wersją 3.1 i jest prostszy: nie ma kodu bezpieczeństwa PKP ani BKP do obliczania, nie ma rozbicia VAT ani certyfikatu klienta TLS. Sprzedaż to dziesięć atrybutów danych. Pozostaje standardowa usługa sieciowa SOAP 1.1:
- Jeden komunikat na sprzedaż, element
Trzbaw przestrzeni nazwhttp://fs.gov.cz/eet/schema/v4, wysyłany przez HTTPS z TLS 1.2 lub nowszym. - Podpis WS-Security nad treścią SOAP, z kanonizacją wyłączną, SHA-256 i RSA, oraz certyfikat podatnika przenoszony w
wsse:BinarySecurityToken. - Certyfikat podatnika jest plikiem PKCS#12, a jego nazwa pospolita jest identyfikatorem podatnika.
- Cała koperta nie może przekraczać 12 kB, czyli 12 288 bajtów.
- Odpowiedź zawiera pok, czas przyjęcia i ewentualne ostrzeżenia albo kod błędu.
Rejestracja jako podatnik, uzyskanie certyfikatu przez portal MOJE daně i nadanie numeru jednostki rejestracyjnej odbywają się, zanim zostanie uruchomiony jakikolwiek kod. Z tych formalności biblioteka potrzebuje jednego pliku PKCS#12 i dwóch numerów, identyfikatora podatnika i identyfikatora jednostki.
Jak TsgcEETClient realizuje pełny cykl żądania i odpowiedzi
Jedno wywołanie Send wykonuje wszystkie kroki, w tej kolejności:
- Waliduje każde pole rekordu
TsgcEETSalewzględem reguł schematu, dzięki czemu nieprawidłowa sprzedaż jest odrzucana lokalnie z czytelnym powodem i nigdy nie trafia do usługi. - Buduje element
Trzbai opakowuje go w kopertę SOAP 1.1 z nowymuuid_zpravydla komunikatu. - Podpisuje treść SOAP zgodnie z WS-Security kluczem dowolnego dostawcy klucza sgcSign: pliku PFX, magazynu certyfikatów Windows, tokena PKCS#11 lub karty inteligentnej albo chmurowej usługi kluczy.
- Sprawdza rozmiar gotowej koperty względem limitu 12 kB, zanim cokolwiek zostanie wysłane.
- Wysyła ją do organu podatkowego. Domyślnym punktem końcowym jest środowisko testowe playground, więc komponent upuszczony na formularz nie może przez przypadek zgłosić prawdziwej sprzedaży.
- Analizuje odpowiedź: pok, czas przyjęcia, flagę testową, ostrzeżenia i kod błędu.
- Weryfikuje podpis potwierdzenia. Odpowiedzi z błędem z założenia nie są podpisane, więc odrzucenie nigdy nie zamienia się w błąd podpisu.
Pierwsza sprzedaż w Delphi
Specyfikacja zaleca podatnikom rozpoczęcie od trybu weryfikacji. Komunikat jest sprawdzany w całości, dokładnie tak jak prawdziwy, a następnie porzucany, więc nic nie zostaje zgłoszone. Jeśli przejdzie weryfikację, certyfikat, podpis, połączenie TLS i każde pole sprzedaży są poprawne. Poniższy kod najpierw wykonuje to sprawdzenie, a potem zgłasza sprzedaż naprawdę.
var
oProvider: TsgcPFXKeyProvider;
oClient: TsgcEETClient;
oSale: TsgcEETSale;
oResponse: TsgcEETResponse;
begin
oProvider := TsgcPFXKeyProvider.Create(nil);
oClient := TsgcEETClient.Create(nil);
try
oProvider.FileName := 'CZ00000019.p12';
oProvider.Password := '...';
// Without LoadFromFile the certificate is empty and the message would
// carry no token for the tax authority to verify the signature with.
oProvider.LoadFromFile;
oClient.KeyProvider := oProvider as IsgcKeyProvider;
oClient.Environment := eetPlayground;
sgcEETInitSale(oSale);
oSale.SendDateTime := Now;
oSale.SaleDateTime := Now;
oSale.FirstSending := True;
// The common name of an EET certificate IS the taxpayer identifier.
oSale.TaxpayerEIC := oProvider.Certificate.SubjectCN;
oSale.UnitID := 11;
oSale.PosID := '1';
oSale.ReceiptNumber := '0/6460/ZQ42';
oSale.TotalAmount := 349;
// Verification mode first. Nothing is filed.
oClient.VerificationMode := True;
oResponse := oClient.Send(oSale);
if sgcEETResponseOutcome(oResponse) <> eoVerified then
raise Exception.CreateFmt('Verification failed, code %d: %s',
[oResponse.ErrorCode, oResponse.ErrorText]);
// Now for real. Only eoAcknowledged reports a sale.
oClient.VerificationMode := False;
oResponse := oClient.Send(oSale);
if sgcEETResponseOutcome(oResponse) = eoAcknowledged then
PrintReceipt(oResponse.POK, oResponse.Test) // your own routine
else
// Not filed. Store the sale and replay it later with Resend.
QueueSale(oSale); // your own routine
finally
oClient.Free;
oProvider.Free;
end;
end;
Kilka szczegółów decyduje o tym, czy to pierwsze uruchomienie się powiedzie:
- Wywołaj
LoadFromFile. UstawienieFileNameiPasswordjedynie zapamiętuje wartości, aBuildMessageodmawia zbudowania komunikatu bez certyfikatu. TaxpayerEICmusi być równe nazwie pospolitej certyfikatu podpisującego, w przeciwnym razie odpowiedź zawiera ostrzeżenie 1. OdczytCertificate.SubjectCNto gwarantuje.- Faktycznie nadany numer jednostki rejestracyjnej ma co najmniej dwie cyfry i kończy się cyfrą 1, 2, 3 lub 4. Wymyślona wartość, taka jak 1, przechodzi walidację, ale skutkuje ostrzeżeniem 6.
- Kwotę wpisaną przez kasjera odczytuj za pomocą
sgcEETParseAmount, a nieStrToCurr, która zależy od separatora dziesiętnego ustawionego na komputerze.
Na potrzeby środowiska testowego playground administracja podatkowa publikuje na eet.gov.cz współdzielone certyfikaty testowe, w tym CZ00000019. Potwierdzenie ze środowiska playground zawiera test="true" i pok kończący się na ff, co nie dowodzi niczego w odniesieniu do prawdziwej sprzedaży.
Odczyt odpowiedzi
To część protokołu, która najczęściej zaskakuje. Każdy wynik przychodzi jako HTTP 200, łącznie z odrzuceniem, więc status HTTP nic nie mówi. Co więcej, sukces w trybie weryfikacji przychodzi wewnątrz elementu błędu z kodem 0, więc TsgcEETResponse.IsError ma wartość True przy całkowicie poprawnym przebiegu weryfikacji.
sgcEETResponseOutcome stosuje obie reguły i zwraca jedną z trzech odpowiedzi:
| Wynik | Znaczenie |
|---|---|
eoAcknowledged | Sprzedaż została zgłoszona, a pok znajduje się w TsgcEETResponse.POK. To jedyny wynik, który oznacza zgłoszenie sprzedaży. |
eoVerified | Sukces w trybie weryfikacji. Nic nie zostało zgłoszone. |
eoRejected | Wszystko inne. Sprzedaż nie została zgłoszona i nadal należy ją zgłosić organowi podatkowemu. |
Ostrzeżenia nie są krytyczne. Do prawidłowego potwierdzenia może być dołączonych do dziesięciu ostrzeżeń, a zdarzenie OnWarning jest wywoływane raz dla każdego z nich, natomiast OnError jest wywoływane dla błędu o kodzie innym niż 0. Po każdym komunikacie zapisuj w dzienniku LastTransactionId, czyli nagłówek odpowiedzi X-Global-Transaction-Id, ponieważ to pierwsza rzecz, o którą prosi wsparcie EET. LastRequestXML i LastResponseXML przechowują oba komunikaty dokładnie w takiej postaci, w jakiej zostały przesłane.
Gdy łącze nie działa: kolejka offline
Kasa musi dalej sprzedawać, gdy połączenie zostanie przerwane. TsgcEETClient dzieli pełny cykl na etapy, dzięki czemu punkt sprzedaży może kolejkować komunikaty i wysyłać je później:
BuildMessagewaliduje, buduje, podpisuje i mierzy kopertę bez jej wysyłania. Użytyuuid_zpravyznajduje się wLastMessageUUID.SendRawwysyła zapisaną kopertę bez zmian, łącznie z jej oryginalnymiuuid_zpravyiprvni_zaslani, co jest właściwe dla komunikatu, który nigdy nie opuścił komputera.Resendponawia sprzedaż, która została wysłana, ale nigdy nie została potwierdzona, z nowymuuid_zpravyiprvni_zaslaniustawionym na false, co odpowiada ponowieniu opisanemu w specyfikacji.
// The line is down: sign the message now and keep it
sEnvelope := oClient.BuildMessage(oSale);
StoreInQueue(oClient.LastMessageUUID, sEnvelope); // your own storage
// The line is back: post the stored envelope exactly as it was built
oResponse := oClient.SendRaw(sEnvelope);
// Sent earlier but no answer arrived: replay the sale as a repeat
oResponse := oClient.Resend(oSale);
Przed zbudowaniem kolejki warto znać jedną pułapkę. Czas sprzedaży jest zapisywany z przesunięciem strefy czasowej i jeśli rekord sprzedaży nie zawiera własnego przesunięcia, biblioteka używa przesunięcia komputera z chwili budowania komunikatu. Lipcowa sprzedaż ponowiona przez Resend w grudniu otrzymałaby grudniowe przesunięcie. Przechowuj przesunięcie razem ze sprzedażą i przy ponawianiu ustaw SaleOffsetMinutes oraz HasSaleOffsetMinutes.
Weryfikacja potwierdzenia
VerifyResponseSignature ma domyślnie wartość True, więc podpis każdego potwierdzenia jest sprawdzany od razu. Sprawdzenie także łańcucha certyfikatów wymaga właściwych kotwic zaufania, a nie są to te oczywiste. Potwierdzenie jest podpisane komercyjnym certyfikatem I.CA, a nie certyfikatami EET dołączonymi do testowych materiałów środowiska playground, i żaden z wystawców I.CA nie znajduje się w magazynie certyfikatów głównych Windows. Pobierz I.CA Root CA/RSA 05/2022 i I.CA Public CA/RSA 06/2022 z ica.cz i wskaż je jako kotwice:
oClient.TrustedCertificates.Add('ica-root-ca-rsa-05-2022.cer');
oClient.TrustedCertificates.Add('ica-public-ca-rsa-06-2022.cer');
oClient.RequireTrustedChain := True;
Jeśli sprawdzenie się nie powiedzie, LastVerificationDetails wskazuje krok, który zawiódł. Gdy potwierdzenie nadejdzie, ale jego podpis nie przejdzie weryfikacji, Send zgłasza wyjątek, a LastResponse nadal przechowuje przeanalizowaną odpowiedź wraz z pok, dzięki czemu sprzedaż, która została już zarejestrowana, nigdy nie zostanie przez pomyłkę wysłana dwukrotnie.
Limit 12 kB
Usługa odrzuca komunikat większy niż 12 kB z kodem błędu 7, a BuildMessage sprawdza rozmiar, zanim cokolwiek zostanie wysłane. Każde pole sprzedaży ma limit określony w schemacie, więc jedyną częścią koperty, której rozmiar naprawdę się zmienia, jest certyfikat podpisujący w wsse:BinarySecurityToken. Z tego samego powodu koperta zawiera dokładnie jeden nagłówek SOAP, a komponent nie oferuje sposobu na dodanie kolejnego.
C++Builder, .NET, serwer i wiersz poleceń
- C++Builder używa tego samego komponentu, a
Demos\CBuilder\EETodzwierciedla demo Delphi. - .NET ma
TsgcEETClientz tym samym API oraz demo WinForms wdemos\EET. - sgcSign Server dodaje
POST /api/v1/sign/eet, który buduje i podpisuje sprzedaż kluczem przechowywanym na serwerze, a także może ją przesłać i zwrócić odpowiedź. Sieć kas może wtedy współdzielić jeden certyfikat podatnika przechowywany na serwerze, zamiast trzymać jego kopię w każdej kasie. - Narzędzie wiersza poleceń
sgcsignma odpowiadające mu polecenieeet. Odczytuje sprzedaż z pliku JSON, a jego kod wyjścia informuje skrypt kasy o tym, co się stało: 6 oznacza, że organ podatkowy odrzucił sprzedaż, 5 oznacza, że serwer nie mógł jej podpisać lub nie mógł połączyć się z organem podatkowym, a 4 oznacza, że nie udało się połączyć z samym serwerem sgcSign.
set SGCSIGN_SERVER=https://sign.shop.local:8443
set SGCSIGN_APIKEY=sgcsk_...
sgcsign eet --provider eet-taxpayer --submit sale.json
Wypróbuj
Demo Delphi w Demos\Delphi\EET przeprowadza cały cykl względem środowiska testowego playground na jednym formularzu. Załaduj certyfikat testowy, wyślij w trybie weryfikacji, a następnie odznacz tę opcję i wyślij prawdziwą sprzedaż, aby otrzymać potwierdzenie z pok. Build Message (no send) pokazuje podpisaną kopertę, którą zapisałaby kolejka offline, a Resend Stored Sale ponawia ostatnią sprzedaż jako powtórne przesłanie. Certyfikaty testowe nie są dołączone do demo, ponieważ dokument, który je udostępnia, ma ograniczony dostęp, więc pobierz je z eet.gov.cz.
Wszystkie właściwości, metody i zdarzenia opisano w pomocy online sgcSign, a sekcja EET 2.0 na stronie profili krajowych sgcSign podsumowuje komponent.
Dostępność
TsgcEETClient jest dostępny w sgcSign 2026.10 dla Delphi, C++Builder i .NET, razem z trasą sgcSign Server i poleceniem eet narzędzia wiersza poleceń.
Masz pytania albo kasę, która musi być gotowa na styczeń? Skontaktuj się. Jeśli coś nie działa zgodnie z oczekiwaniami, wyślij XML żądania i odpowiedzi razem z X-Global-Transaction-Id, a otrzymasz odpowiedź od osób, które napisały kod.
