Tsjechië voert de elektronische registratie van verkopen opnieuw in. Onder EET 2.0 (Elektronická evidence tržeb) meldt een verkooppunt elke verkoop op het moment zelf bij de belastingdienst, en de belastingdienst antwoordt met een bevestigingscode, de pok, het bewijs dat de verkoop is gemeld. De rapportage begint op 1 januari 2027, en de playground waar kassa's worden gebouwd en getest is nu al open.
sgcSign heeft er een nieuwe component voor, TsgcEETClient. Die valideert de verkoop, bouwt het bericht, ondertekent het met het certificaat van de belastingplichtige, verstuurt het, controleert de handtekening op de bevestiging en geeft het resultaat terug. Deze post legt uit wat EET 2.0 vraagt en hoe de component de hele cyclus afhandelt, en toont de Delphi-code voor een eerste verkoop, een offline wachtrij en een geverifieerde bevestiging.
Een geregistreerde verkoop uit de Delphi-demo, van verificatiemodus tot een echte pok. Ook op YouTube.
EET 2.0 is een nieuw protocol, geen update
Als je een kassa hebt gebouwd voor de eerste EET-regeling, begin dan met een schone lei. Versie 4.1 van de data-interface is niet compatibel met de oude versie 3.1, en ze is eenvoudiger: er hoeft geen PKP- of BKP-beveiligingscode berekend te worden, er is geen btw-uitsplitsing en geen TLS-clientcertificaat. Een verkoop bestaat uit tien data-attributen. Wat overblijft is een standaard SOAP 1.1-webservice:
- Eén bericht per verkoop, een element
Trzbain de namespacehttp://fs.gov.cz/eet/schema/v4, verstuurd via HTTPS met TLS 1.2 of hoger. - Een WS-Security-handtekening over de SOAP-body, met exclusieve canonicalization, SHA-256 en RSA, en het certificaat van de belastingplichtige in een
wsse:BinarySecurityToken. - Het certificaat van de belastingplichtige is een PKCS#12-bestand, en de common name ervan is de identificatie van de belastingplichtige.
- De hele envelope mag niet groter zijn dan 12 kB, oftewel 12.288 bytes.
- Het antwoord bevat de pok, het ontvangsttijdstip en eventuele waarschuwingen, of een foutcode.
Registreren als belastingplichtige, het certificaat verkrijgen via het portaal MOJE daně en een nummer voor de registratie-eenheid toegewezen krijgen, gebeurt allemaal voordat er ook maar één regel code draait. Uit dat papierwerk heeft de bibliotheek één PKCS#12-bestand en twee nummers nodig, de identificatie van de belastingplichtige en de identificatie van de eenheid.
Hoe TsgcEETClient de cyclus afhandelt
Eén aanroep van Send doorloopt elke stap, in deze volgorde:
- Valideert elk veld van het record
TsgcEETSaletegen de schemaregels, zodat een ongeldige verkoop lokaal wordt geweigerd met een leesbare reden en de service nooit bereikt. - Bouwt het element
Trzbaen verpakt het in een SOAP 1.1-envelope, met een nieuweuuid_zpravyvoor het bericht. - Ondertekent de SOAP-body onder WS-Security met de sleutel van een willekeurige sleutelprovider van sgcSign: een PFX-bestand, de Windows-certificaatopslag, een PKCS#11-token of smartcard, of een cloud-sleuteldienst.
- Meet de voltooide envelope af tegen het plafond van 12 kB voordat er iets wordt verstuurd.
- Verstuurt hem naar de belastingdienst. De playground is het standaard-endpoint, zodat een component die op een formulier wordt neergezet nooit per ongeluk een echte verkoop kan indienen.
- Verwerkt het antwoord: de pok, het ontvangsttijdstip, de testvlag, de waarschuwingen en de foutcode.
- Verifieert de handtekening op de bevestiging. Foutantwoorden zijn bewust niet ondertekend, zodat een afwijzing nooit verandert in een handtekeningfout.
Je eerste verkoop in Delphi
De specificatie adviseert belastingplichtigen om te beginnen met de verificatiemodus. Het bericht wordt volledig gecontroleerd, precies zoals een echt bericht, en daarna weggegooid, dus er wordt niets ingediend. Als het slaagt, zijn het certificaat, de handtekening, de TLS-verbinding en elk veld van de verkoop correct. De onderstaande code voert eerst die controle uit en dient de verkoop daarna echt in.
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;
Een paar details bepalen of die eerste run werkt:
- Roep
LoadFromFileaan. Het instellen vanFileNameenPasswordlegt alleen de waarden vast, enBuildMessageweigert een bericht te bouwen zonder certificaat. TaxpayerEICmoet gelijk zijn aan de common name van het ondertekeningscertificaat, anders bevat het antwoord waarschuwing 1. DoorCertificate.SubjectCNuit te lezen is dat gegarandeerd.- Een nummer van een registratie-eenheid dat echt is toegewezen, heeft minstens twee cijfers en eindigt op 1, 2, 3 of 4. Een verzonnen waarde zoals 1 komt door de validatie, maar levert waarschuwing 6 op.
- Lees een bedrag dat een kassamedewerker intypt in met
sgcEETParseAmount, niet metStrToCurr, dat het decimaalteken van de machine volgt.
Voor de playground publiceert de belastingadministratie gedeelde testcertificaten, waaronder CZ00000019, op eet.gov.cz. Een bevestiging van de playground bevat test="true" en een pok die eindigt op ff, wat niets bewijst over een echte verkoop.
Het antwoord lezen
Dit is het deel van het protocol dat je het meest zal verrassen. Elke uitkomst komt binnen als HTTP 200, een afwijzing inbegrepen, dus de HTTP-status zegt je niets. En een geslaagde run in verificatiemodus komt binnen in een foutelement met code 0, zodat TsgcEETResponse.IsError True is bij een volkomen correcte verificatierun.
sgcEETResponseOutcome past beide regels toe en geeft een van drie antwoorden terug:
| Uitkomst | Betekenis |
|---|---|
eoAcknowledged | De verkoop is gemeld en de pok staat in TsgcEETResponse.POK. Dit is de enige uitkomst die een verkoop meldt. |
eoVerified | Het succes van de verificatiemodus. Er is niets ingediend. |
eoRejected | Al het andere. De verkoop is niet gemeld en de melding is nog steeds verschuldigd aan de belastingdienst. |
Waarschuwingen zijn niet kritiek. Er kunnen er tot tien meekomen met een geldige bevestiging, en OnWarning wordt voor elk ervan één keer aangeroepen, terwijl OnError wordt aangeroepen bij een fout waarvan de code niet 0 is. Log na elk bericht LastTransactionId, de response-header X-Global-Transaction-Id, want dat is het eerste waar de EET-support om vraagt. LastRequestXML en LastResponseXML bewaren beide berichten precies zoals ze zijn verstuurd.
Als de verbinding wegvalt: een offline wachtrij
Een kassa moet blijven verkopen als de verbinding wegvalt. TsgcEETClient splitst de cyclus op, zodat een verkooppunt berichten in een wachtrij kan zetten en later kan versturen:
BuildMessagevalideert, bouwt, ondertekent en meet de envelope zonder hem te versturen. De gebruikteuuid_zpravystaat inLastMessageUUID.SendRawverstuurt een opgeslagen envelope ongewijzigd, inclusief de oorspronkelijkeuuid_zpravyenprvni_zaslani, wat klopt voor een bericht dat de machine nooit heeft verlaten.Resendherhaalt een verkoop die is verstuurd maar nooit is bevestigd, met een nieuweuuid_zpravyenprvni_zaslaniop false, precies de nieuwe poging die de specificatie beschrijft.
// 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);
Er is één valkuil die je moet kennen voordat je de wachtrij bouwt. Het tijdstip van de verkoop wordt geschreven met een tijdzone-offset, en tenzij het verkooprecord een eigen offset bevat, gebruikt de bibliotheek de offset van de machine op het moment dat het bericht wordt gebouwd. Een verkoop uit juli die in december met Resend wordt herhaald, zou de offset van december krijgen. Sla de offset op bij de verkoop, en stel SaleOffsetMinutes en HasSaleOffsetMinutes in wanneer je hem herhaalt.
De bevestiging verifiëren
VerifyResponseSignature is standaard True, dus de handtekening op elke bevestiging wordt direct gecontroleerd. Om ook de certificaatketen te controleren zijn de juiste trust anchors nodig, en dat zijn niet de voor de hand liggende. De bevestiging is ondertekend met een commercieel I.CA-certificaat, niet met de EET-certificaten die bij het testmateriaal van de playground horen, en geen van beide I.CA-uitgevers staat in de rootstore van Windows. Download I.CA Root CA/RSA 05/2022 en I.CA Public CA/RSA 06/2022 van ica.cz en geef ze op als anchors:
oClient.TrustedCertificates.Add('ica-root-ca-rsa-05-2022.cer');
oClient.TrustedCertificates.Add('ica-public-ca-rsa-06-2022.cer');
oClient.RequireTrustedChain := True;
Als een controle mislukt, meldt LastVerificationDetails welke stap is mislukt. Wanneer er een bevestiging binnenkomt maar de handtekening niet kan worden geverifieerd, genereert Send een exception, en LastResponse bevat nog steeds het verwerkte antwoord met de pok, zodat een verkoop die al is geregistreerd nooit per vergissing twee keer wordt verstuurd.
Het plafond van 12 kB
De service weigert een bericht groter dan 12 kB met foutcode 7, en BuildMessage controleert de grootte voordat er iets wordt verstuurd. Elk verkoopveld wordt door het schema begrensd, dus het enige deel van de envelope waarvan de grootte echt varieert, is het ondertekeningscertificaat in de wsse:BinarySecurityToken. Daarom bevat de envelope ook precies één SOAP-header, en biedt de component geen manier om er nog een toe te voegen.
C++Builder, .NET, de server en de command line
- C++Builder gebruikt dezelfde component, en
Demos\CBuilder\EETis een spiegel van de Delphi-demo. - .NET heeft
TsgcEETClientmet dezelfde API, en een WinForms-demo indemos\EET. - sgcSign Server voegt
POST /api/v1/sign/eettoe, dat een verkoop bouwt en ondertekent met een sleutel die de server bewaart, en die verkoop kan indienen en het antwoord kan teruggeven. Een keten van kassa's kan dan één certificaat van de belastingplichtige delen dat op de server staat, in plaats van dat elke kassa een kopie heeft. - De command-line tool
sgcsignheeft het bijbehorende subcommandoeet. Dat leest de verkoop uit een JSON-bestand, en de exitcode vertelt een kassascript wat er is gebeurd: 6 betekent dat de belastingdienst de verkoop heeft afgewezen, 5 betekent dat de server hem niet kon ondertekenen of de belastingdienst niet kon bereiken, en 4 betekent dat de sgcSign-server zelf niet bereikbaar was.
set SGCSIGN_SERVER=https://sign.shop.local:8443
set SGCSIGN_APIKEY=sgcsk_...
sgcsign eet --provider eet-taxpayer --submit sale.json
Probeer het
De Delphi-demo in Demos\Delphi\EET doorloopt de hele cyclus tegen de playground op één formulier. Laad een testcertificaat, verstuur in verificatiemodus, vink die daarna uit en verstuur een echte verkoop om een bevestiging met de pok te krijgen. Build Message (no send) toont de ondertekende envelope die een offline wachtrij zou opslaan, en Resend Stored Sale herhaalt de laatste verkoop als herhaalde indiening. De testcertificaten worden niet met de demo meegeleverd, omdat het document waarin ze worden verstrekt niet openbaar is, dus download ze van eet.gov.cz.
Elke property, methode en event staat beschreven in de online help van sgcSign, en de sectie EET 2.0 van de pagina met landprofielen van sgcSign vat de component samen.
Beschikbaarheid
TsgcEETClient wordt geleverd in sgcSign 2026.10 voor Delphi, C++Builder en .NET, samen met de route van sgcSign Server en het subcommando eet van de command-line tool.
Vragen, of een kassa die klaar moet zijn voor januari? Neem contact op. Als iets zich niet gedraagt zoals je verwacht, stuur dan de request- en response-XML samen met de X-Global-Transaction-Id, en je krijgt een antwoord van de mensen die de code hebben geschreven.
