La Repubblica Ceca reintroduce la registrazione elettronica delle vendite. Con l'EET 2.0 (Elektronická evidence tržeb) un punto vendita comunica ogni vendita all'amministrazione fiscale nel momento in cui avviene, e l'amministrazione fiscale risponde con un codice di conferma, il pok, che prova che la vendita è stata comunicata. L'obbligo di comunicazione decorre dal 1° gennaio 2027, e il playground in cui si sviluppano e si collaudano le casse è già aperto.
sgcSign ha un nuovo componente dedicato, TsgcEETClient. Valida la vendita, costruisce il messaggio, lo firma con il certificato del contribuente, lo invia, controlla la firma della conferma e restituisce il risultato. Questo articolo spiega cosa richiede l'EET 2.0, come il componente esegue l'intero ciclo e il codice Delphi per una prima vendita, una coda offline e una conferma verificata.
Una vendita registrata dalla demo Delphi, dalla modalità di verifica a un pok reale. Anche su YouTube.
L'EET 2.0 è un nuovo protocollo, non un aggiornamento
Se hai sviluppato una cassa per il primo schema EET, riparti da zero. La versione 4.1 dell'interfaccia dati non è compatibile con la vecchia versione 3.1, ed è più semplice: non c'è alcun codice di sicurezza PKP o BKP da calcolare, nessuna ripartizione dell'IVA e nessun certificato client TLS. Una vendita è composta da dieci attributi di dati. Quello che resta è un servizio web SOAP 1.1 standard:
- Un messaggio per ogni vendita, un elemento
Trzbanel namespacehttp://fs.gov.cz/eet/schema/v4, inviato via HTTPS con TLS 1.2 o superiore. - Una firma WS-Security sul body SOAP, con canonicalizzazione esclusiva, SHA-256 e RSA, e il certificato del contribuente trasportato in un
wsse:BinarySecurityToken. - Il certificato del contribuente è un file PKCS#12, e il suo common name è l'identificativo del contribuente.
- L'intero envelope non deve superare 12 kB, ovvero 12.288 byte.
- La risposta contiene il pok, l'orario di ricezione ed eventuali avvisi, oppure un codice di errore.
La registrazione come contribuente, l'ottenimento del certificato tramite il portale MOJE daně e l'assegnazione di un numero di unità di registrazione avvengono tutti prima di eseguire qualsiasi codice. Da queste pratiche la libreria ricava un file PKCS#12 e due numeri, l'identificativo del contribuente e l'identificativo dell'unità.
Come TsgcEETClient esegue l'intero ciclo
Una sola chiamata a Send esegue tutti i passaggi, in questo ordine:
- Valida ogni campo del record
TsgcEETSalerispetto alle regole dello schema, così una vendita non valida viene rifiutata in locale con un motivo leggibile e non raggiunge mai il servizio. - Costruisce l'elemento
Trzbae lo racchiude in un envelope SOAP 1.1, con un nuovouuid_zpravyper il messaggio. - Firma il body SOAP secondo WS-Security con la chiave di qualsiasi provider di chiavi di sgcSign: un file PFX, l'archivio certificati di Windows, un token PKCS#11 o una smart card, oppure un servizio di chiavi cloud.
- Misura l'envelope completo rispetto al limite di 12 kB prima di inviare qualsiasi cosa.
- Lo invia all'amministrazione fiscale. Il playground è l'endpoint predefinito, così un componente trascinato su un form non può comunicare per errore una vendita reale.
- Analizza la risposta: il pok, l'orario di ricezione, il flag di test, gli avvisi e il codice di errore.
- Verifica la firma della conferma. Le risposte di errore sono prive di firma per progettazione, così un rifiuto non si trasforma mai in un errore di firma.
La tua prima vendita in Delphi
La specifica indica ai contribuenti di iniziare con la modalità di verifica. Il messaggio viene controllato per intero, esattamente come uno reale, e poi scartato, quindi non viene comunicato nulla. Se il controllo va a buon fine, il certificato, la firma, la connessione TLS e ogni campo della vendita sono corretti. Il codice seguente esegue prima quel controllo e poi comunica la vendita in modo effettivo.
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;
Alcuni dettagli determinano se la prima esecuzione funziona:
- Chiama
LoadFromFile. ImpostareFileNameePasswordmemorizza soltanto i valori, eBuildMessagesi rifiuta di costruire un messaggio senza un certificato. TaxpayerEICdeve coincidere con il common name del certificato di firma, altrimenti la risposta contiene l'avviso 1. LeggereCertificate.SubjectCNlo garantisce.- Un numero di unità di registrazione realmente assegnato ha almeno due cifre e termina con 1, 2, 3 o 4. Un valore inventato come 1 supera la validazione ma produce l'avviso 6.
- Leggi un importo digitato da un cassiere con
sgcEETParseAmount, non conStrToCurr, che segue il separatore decimale della macchina.
Per il playground, l'amministrazione fiscale pubblica su eet.gov.cz dei certificati di test condivisi, tra cui CZ00000019. Una conferma del playground contiene test="true" e un pok che termina con ff, e non prova nulla riguardo a una vendita reale.
Leggere la risposta
Questa è la parte del protocollo che più facilmente può sorprenderti. Ogni esito arriva come HTTP 200, compreso un rifiuto, quindi lo stato HTTP non dice nulla. Inoltre un esito positivo in modalità di verifica arriva dentro un elemento di errore con codice 0, quindi TsgcEETResponse.IsError è True anche in un'esecuzione di verifica perfettamente riuscita.
sgcEETResponseOutcome applica entrambe le regole e restituisce una di tre risposte:
| Esito | Significato |
|---|---|
eoAcknowledged | La vendita è comunicata e il pok si trova in TsgcEETResponse.POK. È l'unico esito che comunica una vendita. |
eoVerified | L'esito positivo della modalità di verifica. Non è stato comunicato nulla. |
eoRejected | Tutto il resto. La vendita non è stata comunicata e resta ancora da comunicare all'amministrazione fiscale. |
Gli avvisi non sono critici. Fino a dieci possono accompagnare una conferma valida, e OnWarning scatta una volta per ciascuno, mentre OnError scatta per un errore il cui codice è diverso da 0. Dopo ogni messaggio, registra nel log LastTransactionId, l'header di risposta X-Global-Transaction-Id, perché è la prima cosa che chiede il supporto EET. LastRequestXML e LastResponseXML conservano entrambi i messaggi esattamente come sono transitati.
Quando la connessione cade: una coda offline
Una cassa deve continuare a vendere quando la connessione cade. TsgcEETClient suddivide il ciclo così che un punto vendita possa mettere in coda i messaggi e inviarli più tardi:
BuildMessagevalida, costruisce, firma e misura l'envelope senza inviarlo. L'uuid_zpravyutilizzato si trova inLastMessageUUID.SendRawinvia un envelope memorizzato senza modificarlo, compresi il suouuid_zpravyoriginale eprvni_zaslani, ed è la scelta corretta per un messaggio che non ha mai lasciato la macchina.Resendripete una vendita inviata ma mai confermata, con un nuovouuid_zpravyeprvni_zaslaniimpostato a false, ovvero il nuovo tentativo descritto dalla specifica.
// 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);
C'è una trappola da conoscere prima di costruire la coda. L'orario della vendita viene scritto con uno scostamento di fuso orario, e se il record della vendita non contiene un proprio scostamento la libreria usa quello della macchina nel momento in cui il messaggio viene costruito. Una vendita di luglio ripetuta con Resend a dicembre riceverebbe lo scostamento di dicembre. Memorizza lo scostamento insieme alla vendita, e imposta SaleOffsetMinutes e HasSaleOffsetMinutes quando la ripeti.
Verificare la conferma
VerifyResponseSignature è True per impostazione predefinita, quindi la firma di ogni conferma viene controllata fin da subito. Per controllare anche la catena di certificati servono i trust anchor giusti, e non sono quelli più ovvi. La conferma è firmata da un certificato commerciale I.CA, non dai certificati EET forniti con il materiale di test del playground, e nessuno dei due emittenti I.CA è presente nell'archivio dei certificati radice di Windows. Scarica I.CA Root CA/RSA 05/2022 e I.CA Public CA/RSA 06/2022 da ica.cz e indicali come trust anchor:
oClient.TrustedCertificates.Add('ica-root-ca-rsa-05-2022.cer');
oClient.TrustedCertificates.Add('ica-public-ca-rsa-06-2022.cer');
oClient.RequireTrustedChain := True;
Se un controllo fallisce, LastVerificationDetails indica il passaggio che non è riuscito. Quando arriva una conferma ma la sua firma non risulta valida, Send solleva un'eccezione, e LastResponse contiene comunque la risposta analizzata con il suo pok, così una vendita già registrata non viene mai inviata due volte per errore.
Il limite di 12 kB
Il servizio rifiuta un messaggio più grande di 12 kB con il codice di errore 7, e BuildMessage controlla la dimensione prima di inviare qualsiasi cosa. Ogni campo della vendita ha una lunghezza massima fissata dallo schema, quindi l'unica parte dell'envelope la cui dimensione varia davvero è il certificato di firma nel wsse:BinarySecurityToken. È anche il motivo per cui l'envelope contiene esattamente un header SOAP, e per cui il componente non offre alcun modo di aggiungerne un altro.
C++Builder, .NET, il server e la riga di comando
- C++Builder usa lo stesso componente, e
Demos\CBuilder\EETreplica la demo Delphi. - .NET dispone di
TsgcEETClientcon la stessa API, e di una demo WinForms indemos\EET. - sgcSign Server aggiunge
POST /api/v1/sign/eet, che costruisce e firma una vendita con una chiave custodita dal server, e può inviarla e restituire la risposta. Una catena di casse può così condividere un unico certificato del contribuente conservato sul server, invece di tenerne una copia su ogni cassa. - Lo strumento a riga di comando
sgcsigndispone del verbo corrispondenteeet. Legge la vendita da un file JSON, e il suo codice di uscita indica allo script della cassa cosa è successo: 6 significa che l'amministrazione fiscale ha rifiutato la vendita, 5 significa che il server non ha potuto firmarla o raggiungere l'amministrazione fiscale, e 4 significa che non è stato possibile raggiungere il server sgcSign stesso.
set SGCSIGN_SERVER=https://sign.shop.local:8443
set SGCSIGN_APIKEY=sgcsk_...
sgcsign eet --provider eet-taxpayer --submit sale.json
Provalo
La demo Delphi in Demos\Delphi\EET percorre l'intero ciclo con il playground in un unico form. Carica un certificato di test, invia in modalità di verifica, poi deseleziona l'opzione e invia una vendita reale per ottenere una conferma con il suo pok. Build Message (no send) mostra l'envelope firmato che una coda offline memorizzerebbe, e Resend Stored Sale ripete l'ultima vendita come invio ripetuto. I certificati di test non sono inclusi nella demo, perché il documento che li distribuisce è ad accesso riservato, quindi scaricali da eet.gov.cz.
Ogni proprietà, metodo ed evento è documentato nella guida online di sgcSign, e la sezione EET 2.0 della pagina dei profili per paese di sgcSign riassume il componente.
Disponibilità
TsgcEETClient è disponibile in sgcSign 2026.10 per Delphi, C++Builder e .NET, insieme alla route di sgcSign Server e al verbo eet dello strumento a riga di comando.
Domande, o una cassa che deve essere pronta per gennaio? Contattaci. Se qualcosa non si comporta come ti aspetti, invia l'XML della richiesta e della risposta insieme all'X-Global-Transaction-Id, e riceverai una risposta dalle persone che hanno scritto il codice.
