Ç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:
- Satış başına bir mesaj:
http://fs.gov.cz/eet/schema/v4ad alanında birTrzbaöğesi, TLS 1.2 veya üzeri ile HTTPS üzerinden gönderilir. - SOAP gövdesi üzerinde exclusive kanonikleştirme, SHA-256 ve RSA ile bir WS-Security imzası; vergi mükellefi sertifikası bir
wsse:BinarySecurityTokeniçinde taşınır. - Vergi mükellefi sertifikası bir PKCS#12 dosyasıdır ve ortak adı, vergi mükellefi tanımlayıcısıdır.
- Zarfın tamamı 12 kB'yi, yani 12.288 baytı aşmamalıdır.
- Yanıt pok değerini, alınma zamanını ve varsa uyarıları ya da bir hata kodunu taşı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:
TsgcEETSalekaydı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.Trzbaöğesini oluşturur ve mesaj için yeni biruuid_zpravyile bir SOAP 1.1 zarfına sarar.- 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.
- Hiçbir şey gönderilmeden önce tamamlanan zarfı 12 kB sınırına göre ölçer.
- 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.
- Yanıtı ayrıştırır: pok değeri, alınma zamanı, test bayrağı, uyarılar ve hata kodu.
- 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:
LoadFromFileçağırın.FileNamevePasswordayarlamak yalnızca değerleri kaydeder veBuildMessagesertifika olmadan mesaj oluşturmayı reddeder.TaxpayerEICimzalama sertifikasının ortak adına eşit olmalıdır, aksi halde yanıt 1 numaralı uyarıyı taşır.Certificate.SubjectCNdeğerini okumak bunu garanti eder.- Gerçekten atanmış bir kayıt birimi numarası en az iki basamaklıdır ve 1, 2, 3 veya 4 ile biter. 1 gibi uydurma bir değer doğrulamadan geçer ancak 6 numaralı uyarıyı alır.
- Kasiyerin yazdığı bir tutarı
sgcEETParseAmountile okuyun, makinenin ondalık ayırıcısını izleyenStrToCurrile değil.
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ı |
|---|---|
eoAcknowledged | Satış bildirilmiştir ve pok değeri TsgcEETResponse.POK içindedir. Bir satışı bildiren tek sonuç budur. |
eoVerified | Doğrulama modundaki başarı. Hiçbir şey kayda geçirilmedi. |
eoRejected | Diğ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:
BuildMessagezarfı göndermeden doğrular, oluşturur, imzalar ve ölçer. Kullandığıuuid_zpravydeğeriLastMessageUUIDiçindedir.SendRawsaklanmış bir zarfı, özgünuuid_zpravyveprvni_zaslanideğerleri dahil olmak üzere değiştirmeden gönderir; makineden hiç çıkmamış bir mesaj için doğru yol budur.Resendgönderilmiş ancak hiç onaylanmamış bir satışı yeni biruuid_zpravyile veprvni_zaslanifalse olarak ayarlanmış şekilde yineler; belirtimin tarif ettiği yeniden deneme budur.
// 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ı
- C++Builder aynı bileşeni kullanır ve
Demos\CBuilder\EETDelphi demosunun karşılığıdır. - .NET, aynı API'ye sahip
TsgcEETClientsınıfını vedemos\EETiçinde bir WinForms demosunu sunar. - sgcSign Server, sunucunun tuttuğu bir anahtarla bir satışı oluşturup imzalayan, ayrıca gönderip yanıtı döndürebilen
POST /api/v1/sign/eetuç noktasını ekler. Böylece bir mağaza zincirindeki yazar kasalar, her birinde bir kopya bulundurmak yerine sunucuda tutulan tek bir vergi mükellefi sertifikasını paylaşabilir. sgcsignkomut satırı aracı buna karşılık geleneetkomutuna sahiptir. Satışı bir JSON dosyasından okur ve çıkış kodu bir kasa betiğine ne olduğunu söyler: 6 vergi idaresinin satışı reddettiği, 5 sunucunun satışı imzalayamadığı veya vergi idaresine ulaşamadığı, 4 ise sgcSign sunucusunun kendisine ulaşılamadığı anlamına gelir.
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.
