La République tchèque rétablit l'enregistrement électronique des ventes. Avec EET 2.0 (Elektronická evidence tržeb), un point de vente déclare chaque vente à l'administration fiscale au moment où elle a lieu, et l'administration fiscale répond par un code d'accusé de réception, le pok, qui prouve que la vente a été déclarée. La déclaration débute le 1er janvier 2027, et l'environnement playground dans lequel les caisses sont développées et testées est déjà ouvert.
sgcSign propose un nouveau composant pour cela, TsgcEETClient. Il valide la vente, construit le message, le signe avec le certificat du contribuable, l'envoie, contrôle la signature de l'accusé de réception et renvoie le résultat. Cet article explique ce qu'exige EET 2.0, comment le composant effectue l'aller-retour, et présente le code Delphi pour une première vente, une file d'attente hors ligne et un accusé de réception vérifié.
Une vente enregistrée depuis la démo Delphi, du mode vérification jusqu'à un vrai pok. Également sur YouTube.
EET 2.0 est un nouveau protocole, pas une mise à jour
Si tu as développé une caisse pour le premier dispositif EET, repars de zéro. La version 4.1 de l'interface de données n'est pas compatible avec l'ancienne version 3.1, et elle est plus simple : aucun code de sécurité PKP ou BKP à calculer, aucune ventilation de la TVA et aucun certificat client TLS. Une vente se résume à dix attributs de données. Il reste un service web SOAP 1.1 standard :
- Un message par vente, un élément
Trzbadans l'espace de nomshttp://fs.gov.cz/eet/schema/v4, envoyé en HTTPS avec TLS 1.2 ou supérieur. - Une signature WS-Security sur le corps SOAP, avec canonicalisation exclusive, SHA-256 et RSA, et le certificat du contribuable transporté dans un
wsse:BinarySecurityToken. - Le certificat du contribuable est un fichier PKCS#12, et son nom commun est l'identifiant du contribuable.
- L'enveloppe complète ne doit pas dépasser 12 kB, soit 12 288 octets.
- La réponse contient le pok, l'heure de réception et les éventuels avertissements, ou bien un code d'erreur.
L'inscription en tant que contribuable, l'obtention du certificat via le portail MOJE daně et l'attribution d'un numéro d'unité d'enregistrement ont toutes lieu avant qu'une seule ligne de code ne s'exécute. De toutes ces démarches, la bibliothèque n'a besoin que d'un fichier PKCS#12 et de deux numéros, l'identifiant du contribuable et l'identifiant de l'unité.
Comment TsgcEETClient effectue l'aller-retour
Un seul appel à Send exécute chaque étape, dans cet ordre :
- Valide chaque champ de l'enregistrement
TsgcEETSaleselon les règles du schéma, de sorte qu'une vente invalide est refusée localement avec un motif lisible et n'atteint jamais le service. - Construit l'élément
Trzbaet l'encapsule dans une enveloppe SOAP 1.1, avec un nouveluuid_zpravypour le message. - Signe le corps SOAP selon WS-Security avec la clé de n'importe quel fournisseur de clés sgcSign : un fichier PFX, le magasin de certificats Windows, un token PKCS#11 ou une carte à puce, ou un service de clés cloud.
- Mesure l'enveloppe finale par rapport au plafond de 12 kB avant tout envoi.
- L'envoie à l'administration fiscale. L'environnement playground est l'endpoint par défaut, de sorte qu'un composant déposé sur une fiche ne peut pas déclarer une vraie vente par accident.
- Analyse la réponse : le pok, l'heure de réception, l'indicateur de test, les avertissements et le code d'erreur.
- Vérifie la signature de l'accusé de réception. Les réponses d'erreur ne sont pas signées par conception, donc un rejet ne se transforme jamais en échec de signature.
Ta première vente en Delphi
La spécification demande aux contribuables de commencer par le mode vérification. Le message est contrôlé intégralement, exactement comme un vrai, puis écarté, donc rien n'est déclaré. S'il passe, le certificat, la signature, la connexion TLS et chaque champ de la vente sont corrects. Le code ci-dessous effectue d'abord ce contrôle, puis déclare la vente pour de bon.
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;
Quelques détails décident de la réussite de ce premier essai :
- Appelle
LoadFromFile. DéfinirFileNameetPasswordne fait qu'enregistrer les valeurs, etBuildMessagerefuse de construire un message sans certificat. TaxpayerEICdoit être identique au nom commun du certificat de signature, sinon la réponse contient l'avertissement 1. LireCertificate.SubjectCNle garantit.- Un numéro d'unité d'enregistrement réellement attribué comporte au moins deux chiffres et se termine par 1, 2, 3 ou 4. Une valeur inventée comme 1 passe la validation mais vaut l'avertissement 6.
- Lis un montant saisi par un caissier avec
sgcEETParseAmount, et non avecStrToCurr, qui suit le séparateur décimal de la machine.
Pour l'environnement playground, l'administration fiscale publie des certificats de test partagés, dont CZ00000019, sur eet.gov.cz. Un accusé de réception du playground contient test="true" et un pok se terminant par ff, ce qui ne prouve rien pour une vraie vente.
Lire la réponse
C'est la partie du protocole la plus susceptible de te surprendre. Chaque résultat arrive en HTTP 200, rejet compris, donc le statut HTTP ne t'apprend rien. Et un succès en mode vérification arrive dans un élément d'erreur portant le code 0, donc TsgcEETResponse.IsError vaut True lors d'une vérification parfaitement réussie.
sgcEETResponseOutcome applique ces deux règles et renvoie l'une de trois réponses :
| Résultat | Signification |
|---|---|
eoAcknowledged | La vente est déclarée et le pok se trouve dans TsgcEETResponse.POK. C'est le seul résultat qui déclare une vente. |
eoVerified | Le succès du mode vérification. Rien n'a été déclaré. |
eoRejected | Tout le reste. La vente n'a pas été déclarée et reste due à l'administration fiscale. |
Les avertissements ne sont pas critiques. Jusqu'à dix d'entre eux peuvent accompagner un accusé de réception valide, et OnWarning se déclenche une fois pour chacun, tandis que OnError se déclenche pour une erreur dont le code n'est pas 0. Après chaque message, journalise LastTransactionId, l'en-tête de réponse X-Global-Transaction-Id, car c'est la première chose que demande le support EET. LastRequestXML et LastResponseXML conservent les deux messages exactement tels qu'ils ont circulé.
Quand la ligne est coupée : une file d'attente hors ligne
Une caisse doit continuer à vendre quand la connexion tombe. TsgcEETClient découpe l'aller-retour pour qu'un point de vente puisse mettre des messages en file d'attente et les envoyer plus tard :
BuildMessagevalide, construit, signe et mesure l'enveloppe sans l'envoyer. L'uuid_zpravyutilisé se trouve dansLastMessageUUID.SendRawenvoie une enveloppe stockée sans la modifier,uuid_zpravyetprvni_zaslanid'origine compris, ce qui convient à un message qui n'a jamais quitté la machine.Resendrenvoie une vente envoyée mais jamais acquittée, avec un nouveluuid_zpravyetprvni_zaslanià false, ce qui correspond à la nouvelle tentative décrite par la spécification.
// 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);
Un piège mérite d'être connu avant de construire la file d'attente. L'heure de la vente est écrite avec un décalage de fuseau horaire, et à moins que l'enregistrement de la vente ne porte son propre décalage, la bibliothèque utilise le décalage de la machine au moment où le message est construit. Une vente de juillet renvoyée avec Resend en décembre serait horodatée avec le décalage de décembre. Stocke le décalage avec la vente, et définis SaleOffsetMinutes et HasSaleOffsetMinutes quand tu la renvoies.
Vérifier l'accusé de réception
VerifyResponseSignature vaut True par défaut, donc la signature de chaque accusé de réception est contrôlée d'emblée. Contrôler aussi la chaîne de certificats demande les bonnes ancres de confiance, et ce ne sont pas celles auxquelles on pense. L'accusé de réception est signé par un certificat commercial I.CA, et non par les certificats EET fournis avec le matériel de test du playground, et aucun des deux émetteurs I.CA ne figure dans le magasin racine de Windows. Télécharge I.CA Root CA/RSA 05/2022 et I.CA Public CA/RSA 06/2022 depuis ica.cz et déclare-les comme ancres :
oClient.TrustedCertificates.Add('ica-root-ca-rsa-05-2022.cer');
oClient.TrustedCertificates.Add('ica-public-ca-rsa-06-2022.cer');
oClient.RequireTrustedChain := True;
Si un contrôle échoue, LastVerificationDetails indique l'étape en échec. Quand un accusé de réception arrive mais que sa signature ne se vérifie pas, Send lève une exception, et LastResponse contient toujours la réponse analysée avec son pok, de sorte qu'une vente déjà enregistrée n'est jamais envoyée deux fois par erreur.
Le plafond de 12 kB
Le service refuse un message de plus de 12 kB avec le code d'erreur 7, et BuildMessage vérifie la taille avant tout envoi. Chaque champ de la vente est plafonné par le schéma, donc la seule partie de l'enveloppe dont la taille varie réellement est le certificat de signature dans le wsse:BinarySecurityToken. C'est aussi pourquoi l'enveloppe porte exactement un en-tête SOAP, et pourquoi le composant ne permet pas d'en ajouter un autre.
C++Builder, .NET, le serveur et la ligne de commande
- C++Builder utilise le même composant, et
Demos\CBuilder\EETreproduit la démo Delphi. - .NET dispose de
TsgcEETClientavec la même API, et d'une démo WinForms dansdemos\EET. - sgcSign Server ajoute
POST /api/v1/sign/eet, qui construit et signe une vente avec une clé détenue par le serveur, et peut la soumettre et renvoyer la réponse. Une chaîne de caisses peut alors partager un seul certificat de contribuable conservé sur le serveur, au lieu que chaque caisse en détienne une copie. - L'outil en ligne de commande
sgcsigndispose du verbeeetcorrespondant. Il lit la vente depuis un fichier JSON, et son code de sortie indique à un script de caisse ce qui s'est passé : 6 signifie que l'administration fiscale a rejeté la vente, 5 que le serveur n'a pas pu la signer ou n'a pas pu joindre l'administration fiscale, et 4 que le serveur sgcSign lui-même était injoignable.
set SGCSIGN_SERVER=https://sign.shop.local:8443
set SGCSIGN_APIKEY=sgcsk_...
sgcsign eet --provider eet-taxpayer --submit sale.json
Essaie-le
La démo Delphi dans Demos\Delphi\EET parcourt tout l'aller-retour avec l'environnement playground sur une seule fiche. Charge un certificat de test, envoie en mode vérification, puis décoche-le et envoie une vraie vente pour obtenir un accusé de réception avec son pok. Build Message (no send) affiche l'enveloppe signée qu'une file d'attente hors ligne stockerait, et Resend Stored Sale renvoie la dernière vente comme une soumission répétée. Les certificats de test ne sont pas inclus avec la démo, car le document qui les distribue est à diffusion restreinte, télécharge-les donc depuis eet.gov.cz.
Chaque propriété, méthode et événement est documenté dans l'aide en ligne de sgcSign, et la section EET 2.0 de la page des profils par pays de sgcSign résume le composant.
Disponibilité
TsgcEETClient est livré dans sgcSign 2026.10 pour Delphi, C++Builder et .NET, avec la route sgcSign Server et le verbe eet de l'outil en ligne de commande.
Des questions, ou une caisse qui doit être prête pour janvier ? Contacte-nous. Si quelque chose ne se comporte pas comme prévu, envoie le XML de la requête et de la réponse avec le X-Global-Transaction-Id, et tu obtiendras une réponse des personnes qui ont écrit le code.
