Czeska EET 2.0 w Delphi: rejestracja sprzedaży z TsgcEETClient

· Komponenty
Czeska EET 2.0 w Delphi: rejestracja sprzedaży z TsgcEETClient | Blog eSeGeCe

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:

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:

  1. Waliduje każde pole rekordu TsgcEETSale względem reguł schematu, dzięki czemu nieprawidłowa sprzedaż jest odrzucana lokalnie z czytelnym powodem i nigdy nie trafia do usługi.
  2. Buduje element Trzba i opakowuje go w kopertę SOAP 1.1 z nowym uuid_zpravy dla komunikatu.
  3. 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.
  4. Sprawdza rozmiar gotowej koperty względem limitu 12 kB, zanim cokolwiek zostanie wysłane.
  5. 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.
  6. Analizuje odpowiedź: pok, czas przyjęcia, flagę testową, ostrzeżenia i kod błędu.
  7. 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:

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:

WynikZnaczenie
eoAcknowledgedSprzedaż została zgłoszona, a pok znajduje się w TsgcEETResponse.POK. To jedyny wynik, który oznacza zgłoszenie sprzedaży.
eoVerifiedSukces w trybie weryfikacji. Nic nie zostało zgłoszone.
eoRejectedWszystko 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:

// 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ń

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.