Delphi'de Çekya EET 2.0: TsgcEETClient ile Satışları Kaydedin

· Bileşenler
Delphi'de Çekya EET 2.0: TsgcEETClient ile Satışları Kaydedin

Çekya, satışların elektronik kaydını geri getiriyor. EET 2.0 (Elektronická evidence tržeb) kapsamında bir satış noktası her satışı gerçekleştiği anda vergi idaresine bildirir ve vergi idaresi bir onay koduyla, yani satışın bildirildiğinin kanıtı olan pok değeriyle yanıt verir. Bildirim 1 Ocak 2027'de başlıyor ve yazar kasaların geliştirilip test edildiği playground ortamı şimdiden açık.

sgcSign bunun için yeni bir bileşen sunuyor: TsgcEETClient. Bileşen satışı doğrular, mesajı oluşturur, vergi mükellefi sertifikasıyla imzalar, gönderir, onay üzerindeki imzayı denetler ve sonucu geri verir. Bu yazı EET 2.0'ın ne istediğini, bileşenin gidiş dönüşü nasıl gerçekleştirdiğini ve ilk satış, çevrimdışı kuyruk ve doğrulanmış bir onay için Delphi kodunu açıklıyor.

Delphi demosunda kaydedilen bir satış, doğrulama modundan gerçek bir pok değerine kadar. YouTube'da da izleyin.

EET 2.0 bir güncelleme değil, yeni bir protokol

İlk EET düzeni için bir yazar kasa geliştirdiyseniz sıfırdan başlayın. 4.1 sürümlü veri arayüzü eski 3.1 sürümüyle uyumlu değildir ve daha basittir: hesaplanacak bir PKP veya BKP güvenlik kodu, KDV dökümü ya da TLS istemci sertifikası yoktur. Bir satış on veri özniteliğinden oluşur. Geriye standart bir SOAP 1.1 web hizmeti kalır:

Vergi mükellefi olarak kaydolmak, sertifikayı MOJE daně portalı üzerinden almak ve bir kayıt birimi numarası atanması, herhangi bir kod çalışmadan önce gerçekleşir. Bu evrak işlerinden sonra kütüphanenin ihtiyaç duyduğu tek şey bir PKCS#12 dosyası ve iki numaradır: vergi mükellefi tanımlayıcısı ve birim tanımlayıcısı.

TsgcEETClient gidiş dönüşü nasıl gerçekleştirir

Send için yapılan tek bir çağrı her adımı şu sırayla çalıştırır:

  1. TsgcEETSale kaydının her alanını şema kurallarına göre doğrular; böylece geçersiz bir satış okunabilir bir gerekçeyle yerel olarak reddedilir ve hizmete asla ulaşmaz.
  2. Trzba öğesini oluşturur ve mesaj için yeni bir uuid_zpravy ile bir SOAP 1.1 zarfına sarar.
  3. SOAP gövdesini WS-Security kapsamında herhangi bir sgcSign anahtar sağlayıcısının anahtarıyla imzalar: bir PFX dosyası, Windows sertifika deposu, bir PKCS#11 belirteci veya akıllı kart ya da bir bulut anahtar hizmeti.
  4. Hiçbir şey gönderilmeden önce tamamlanan zarfı 12 kB sınırına göre ölçer.
  5. Zarfı vergi idaresine gönderir. Varsayılan uç nokta playground ortamıdır, böylece bir forma bırakılan bileşen yanlışlıkla gerçek bir satışı kayda geçiremez.
  6. Yanıtı ayrıştırır: pok değeri, alınma zamanı, test bayrağı, uyarılar ve hata kodu.
  7. Onay üzerindeki imzayı doğrular. Hata yanıtları tasarım gereği imzasızdır, bu nedenle bir ret asla bir imza hatasına dönüşmez.

Delphi'de ilk satışınız

Belirtim, vergi mükelleflerine doğrulama moduyla başlamalarını söyler. Mesaj tıpkı gerçek bir mesaj gibi eksiksiz denetlenir ve ardından atılır, böylece hiçbir şey kayda geçirilmez. Denetimden geçerse sertifika, imza, TLS bağlantısı ve satışın her alanı doğrudur. Aşağıdaki kod önce bu denetimi çalıştırır, ardından satışı gerçekten kayda geçirir.

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;

İlk çalıştırmanın başarılı olup olmayacağını birkaç ayrıntı belirler:

Playground ortamı için vergi idaresi, aralarında CZ00000019'un da bulunduğu ortak test sertifikalarını eet.gov.cz üzerinde yayımlar. Playground ortamından gelen bir onay test="true" taşır ve ff ile biten bir pok değeri içerir; bu da gerçek bir satış hakkında hiçbir şey kanıtlamaz.

Yanıtı okumak

Protokolün sizi en çok şaşırtabilecek kısmı burasıdır. Bir ret dahil her sonuç HTTP 200 olarak gelir, bu nedenle HTTP durumu size hiçbir şey söylemez. Ayrıca doğrulama modundaki bir başarı, 0 kodunu taşıyan bir hata öğesinin içinde gelir, yani tamamen başarılı bir doğrulama çalıştırmasında TsgcEETResponse.IsError True olur.

sgcEETResponseOutcome her iki kuralı da uygular ve üç yanıttan birini döndürür:

SonuçAnlamı
eoAcknowledgedSatış bildirilmiştir ve pok değeri TsgcEETResponse.POK içindedir. Bir satışı bildiren tek sonuç budur.
eoVerifiedDoğrulama modundaki başarı. Hiçbir şey kayda geçirilmedi.
eoRejectedDiğer her şey. Satış bildirilmemiştir ve hâlâ vergi idaresine bildirilmesi gerekir.

Uyarılar kritik değildir. Geçerli bir onayla birlikte en fazla on uyarı gelebilir ve OnWarning her biri için bir kez tetiklenir, OnError ise kodu 0 olmayan bir hata için tetiklenir. Her mesajdan sonra LastTransactionId değerini, yani X-Global-Transaction-Id yanıt başlığını günlüğe kaydedin, çünkü EET desteğinin ilk istediği şey budur. LastRequestXML ve LastResponseXML her iki mesajı da iletildikleri haliyle saklar.

Hat kesildiğinde: çevrimdışı kuyruk

Bir yazar kasa bağlantı koptuğunda da satış yapmaya devam etmelidir. TsgcEETClient gidiş dönüşü böler, böylece bir satış noktası mesajları kuyruğa alıp daha sonra gönderebilir:

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

Kuyruğu kurmadan önce bilmeye değer bir tuzak var. Satış zamanı bir saat dilimi farkıyla yazılır ve satış kaydı kendi farkını taşımıyorsa kütüphane, mesajın oluşturulduğu andaki makinenin farkını kullanır. Temmuz ayında yapılmış bir satış aralık ayında Resend ile yinelenirse aralık ayının farkıyla damgalanır. Farkı satışla birlikte saklayın ve satışı yinelerken SaleOffsetMinutes ile HasSaleOffsetMinutes değerlerini ayarlayın.

Onayı doğrulamak

VerifyResponseSignature varsayılan olarak True'dur, bu nedenle her onay üzerindeki imza ek bir ayar gerekmeden denetlenir. Sertifika zincirini de denetlemek doğru güven çapalarını gerektirir ve bunlar akla ilk gelenler değildir. Onay, playground test malzemesiyle gelen EET sertifikalarıyla değil, ticari bir I.CA sertifikasıyla imzalanır ve I.CA düzenleyicilerinden hiçbiri Windows kök deposunda bulunmaz. I.CA Root CA/RSA 05/2022 ve I.CA Public CA/RSA 06/2022 sertifikalarını ica.cz adresinden indirin ve bunları çapa olarak belirtin:

oClient.TrustedCertificates.Add('ica-root-ca-rsa-05-2022.cer');
oClient.TrustedCertificates.Add('ica-public-ca-rsa-06-2022.cer');
oClient.RequireTrustedChain := True;

Bir denetim başarısız olursa LastVerificationDetails başarısız olan adımı bildirir. Bir onay gelir ancak imzası doğrulanmazsa Send bir istisna fırlatır ve LastResponse ayrıştırılmış yanıtı pok değeriyle birlikte tutmaya devam eder; böylece zaten kaydedilmiş bir satış yanlışlıkla asla iki kez gönderilmez.

12 kB sınırı

Hizmet 12 kB'den büyük bir mesajı 7 hata koduyla reddeder ve BuildMessage hiçbir şey gönderilmeden önce boyutu denetler. Her satış alanı şema tarafından sınırlandırılmıştır, bu nedenle zarfın boyutu gerçekten değişen tek kısmı wsse:BinarySecurityToken içindeki imzalama sertifikasıdır. Zarfın tam olarak tek bir SOAP başlığı taşımasının ve bileşenin başka bir başlık eklemenin hiçbir yolunu sunmamasının nedeni de budur.

C++Builder, .NET, sunucu ve komut satırı

set SGCSIGN_SERVER=https://sign.shop.local:8443
set SGCSIGN_APIKEY=sgcsk_...

sgcsign eet --provider eet-taxpayer --submit sale.json

Deneyin

Demos\Delphi\EET içindeki Delphi demosu, tek bir form üzerinde playground ortamına karşı tüm gidiş dönüşü adım adım gösterir. Bir test sertifikası yükleyin, doğrulama modunda gönderin, ardından işareti kaldırıp pok değeri içeren bir onay almak için gerçek bir satış gönderin. Build Message (no send) bir çevrimdışı kuyruğun saklayacağı imzalı zarfı gösterir, Resend Stored Sale ise son satışı tekrarlanan bir gönderim olarak yineler. Test sertifikaları demoyla birlikte gelmez, çünkü onları dağıtan belgenin erişimi kısıtlıdır; bu nedenle onları eet.gov.cz adresinden indirin.

Her özellik, metot ve olay sgcSign çevrimiçi yardımında belgelenmiştir ve sgcSign ülke profilleri sayfasının EET 2.0 bölümü bileşeni özetler.

Kullanıma sunulma

TsgcEETClient, Delphi, C++Builder ve .NET için sgcSign 2026.10 sürümünde yer alır. sgcSign Server rotası ve komut satırı aracının eet komutu da aynı sürümle gelir.

Sorularınız mı var ya da ocak ayına kadar hazır olması gereken bir yazar kasanız mı? İletişime geçin. Bir şey beklediğiniz gibi çalışmazsa isteği ve yanıt XML'ini X-Global-Transaction-Id ile birlikte gönderin; kodu yazan kişilerden bir yanıt alacaksınız.