EET 2.0 ceca in Delphi: registrare le vendite con TsgcEETClient

· Componenti
EET 2.0 ceca in Delphi: registrare le vendite con TsgcEETClient | Blog eSeGeCe

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:

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:

  1. Valida ogni campo del record TsgcEETSale rispetto alle regole dello schema, così una vendita non valida viene rifiutata in locale con un motivo leggibile e non raggiunge mai il servizio.
  2. Costruisce l'elemento Trzba e lo racchiude in un envelope SOAP 1.1, con un nuovo uuid_zpravy per il messaggio.
  3. 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.
  4. Misura l'envelope completo rispetto al limite di 12 kB prima di inviare qualsiasi cosa.
  5. 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.
  6. Analizza la risposta: il pok, l'orario di ricezione, il flag di test, gli avvisi e il codice di errore.
  7. 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:

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:

EsitoSignificato
eoAcknowledgedLa vendita è comunicata e il pok si trova in TsgcEETResponse.POK. È l'unico esito che comunica una vendita.
eoVerifiedL'esito positivo della modalità di verifica. Non è stato comunicato nulla.
eoRejectedTutto 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:

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

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.