Delphi でチェコ EET 2.0 に対応: TsgcEETClient で売上を登録

· コンポーネント
Delphi でチェコ EET 2.0 に対応: TsgcEETClient で売上を登録 | eSeGeCe ブログ

チェコ共和国は売上の電子登録制度を復活させます。EET 2.0(Elektronická evidence tržeb)では、店舗の POS が各売上を発生と同時に税務当局に報告し、税務当局は受領確認コードである pok を返します。pok は売上が報告されたことの証明です。報告は 2027 年 1 月 1 日に開始され、レジを構築してテストするためのプレイグラウンドはすでに公開されています。

sgcSign には、このための新しいコンポーネント TsgcEETClient が加わりました。売上を検証し、メッセージを構築し、納税者の証明書で署名して送信し、受領確認の署名をチェックして結果を返します。本記事では、EET 2.0 が求める内容、コンポーネントが往復処理をどのように行うか、そして最初の売上、オフラインキュー、検証済みの受領確認のための Delphi コードを説明します。

Delphi デモによる売上登録。検証モードから実際の pok の取得までを紹介します。YouTube でもご覧いただけます

EET 2.0 は更新ではなく新しいプロトコル

最初の EET 制度向けにレジを構築したことがある場合は、白紙の状態から始めてください。データインターフェース バージョン 4.1 は旧バージョン 3.1 と互換性がなく、しかもより単純です。計算すべき PKP や BKP のセキュリティコードはなく、VAT の内訳もなく、TLS クライアント証明書も不要です。売上は 10 個のデータ属性で表されます。残るのは標準的な SOAP 1.1 Web サービスです。

納税者としての登録、MOJE daně ポータルでの証明書の取得、事業所番号の割り当ては、すべてコードを実行する前に済ませておく手続きです。これらの手続きの結果のうち、ライブラリが必要とするのは PKCS#12 ファイル 1 つと、納税者識別子および事業所識別子という 2 つの番号です。

TsgcEETClient による往復処理

Send を 1 回呼び出すだけで、すべてのステップが次の順序で実行されます。

  1. TsgcEETSale レコードのすべてのフィールドをスキーマの規則に照らして検証します。無効な売上は理由がわかる形でローカルで拒否され、サービスに届くことはありません。
  2. Trzba 要素を構築し、メッセージ用の新しい uuid_zpravy を付けて SOAP 1.1 エンベロープで包みます。
  3. 任意の sgcSign キープロバイダーの鍵を使い、WS-Security で SOAP 本文に署名します。PFX ファイル、Windows 証明書ストア、PKCS#11 トークンやスマートカード、クラウドの鍵サービスを利用できます。
  4. 何かを送信する前に、完成したエンベロープのサイズを 12 kB の上限と照らし合わせて計測します。
  5. 税務当局に送信します。デフォルトのエンドポイントはプレイグラウンドなので、フォームに配置しただけのコンポーネントが誤って実際の売上を登録することはありません。
  6. 応答を解析し、pok、受信日時、テストフラグ、警告、エラーコードを取り出します。
  7. 受領確認の署名を検証します。エラー応答は設計上署名されていないため、拒否が署名エラーに変わることはありません。

Delphi での最初の売上

仕様では、納税者はまず検証モードから始めるよう求められています。メッセージは実際のものとまったく同じように完全にチェックされた後に破棄されるため、何も登録されません。これに合格すれば、証明書、署名、TLS 接続、売上のすべてのフィールドが正しいことになります。以下のコードは、まずこのチェックを行い、その後に売上を実際に登録します。

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;

最初の実行がうまくいくかどうかは、いくつかの細かな点で決まります。

プレイグラウンド向けには、税務行政機関が CZ00000019 などの共有テスト証明書を eet.gov.cz で公開しています。プレイグラウンドからの受領確認には test="true" が含まれ、pok の末尾は ff になります。これは実際の売上について何も証明しません。

応答の読み取り

プロトコルの中で最も意外に感じやすいのがこの部分です。拒否を含むすべての結果が HTTP 200 で届くため、HTTP ステータスからは何もわかりません。さらに、検証モードの成功はコード 0 を持つエラー要素の中で届くため、まったく問題のない検証実行でも TsgcEETResponse.IsError は True になります。

sgcEETResponseOutcome は両方の規則を適用し、次の 3 つのうちいずれかを返します。

結果意味
eoAcknowledged売上は報告済みで、pok は TsgcEETResponse.POK に入っています。売上を報告する結果はこれだけです。
eoVerified検証モードの成功です。何も登録されていません。
eoRejectedそれ以外のすべてです。売上は報告されておらず、税務当局への報告義務が引き続き残っています。

警告は致命的ではありません。有効な受領確認に最大 10 件の警告が付くことがあり、OnWarning は警告ごとに 1 回発生します。一方、OnError はコードが 0 以外のエラーで発生します。すべてのメッセージの後に LastTransactionId、つまり X-Global-Transaction-Id レスポンスヘッダーをログに記録してください。EET サポートが最初に求めるのがこの値です。LastRequestXMLLastResponseXML は、両方のメッセージを送受信されたとおりに保持します。

回線が切断されたとき: オフラインキュー

レジは接続が切れても販売を続けなければなりません。TsgcEETClient は往復処理を分割しているため、POS はメッセージをキューに入れて後で送信できます。

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

キューを構築する前に知っておくべき落とし穴が 1 つあります。売上日時はタイムゾーンのオフセット付きで書き込まれ、売上レコードが独自のオフセットを持っていない場合、ライブラリはメッセージを構築した時点のマシンのオフセットを使用します。7 月の売上を 12 月に Resend で再送すると、12 月のオフセットが付けられてしまいます。オフセットは売上と一緒に保存し、再送する際に SaleOffsetMinutesHasSaleOffsetMinutes を設定してください。

受領確認の検証

VerifyResponseSignature はデフォルトで True なので、すべての受領確認の署名は初期状態のままでチェックされます。証明書チェーンもチェックするには適切なトラストアンカーが必要ですが、それは一見して思い浮かぶものではありません。受領確認は、プレイグラウンドのテスト資料に付属する EET 証明書ではなく、商用の I.CA 証明書で署名されており、I.CA のどちらの発行者も Windows のルートストアには含まれていません。I.CA Root CA/RSA 05/2022 と I.CA Public CA/RSA 06/2022 を ica.cz からダウンロードし、アンカーとして指定してください。

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

チェックに失敗した場合は、LastVerificationDetails が失敗したステップを報告します。受領確認は届いたもののその署名が検証できない場合、Send は例外を発生させますが、LastResponse には pok を含む解析済みの応答が残っているため、すでに登録された売上を誤って 2 回送信することはありません。

12 kB の上限

サービスは 12 kB を超えるメッセージをエラーコード 7 で拒否し、BuildMessage は何かを送信する前にサイズをチェックします。売上のフィールドはすべてスキーマで上限が決められているため、エンベロープの中でサイズが実際に変わるのは wsse:BinarySecurityToken に入る署名証明書だけです。エンベロープに SOAP ヘッダーがちょうど 1 つしかなく、コンポーネントに別のヘッダーを追加する手段がないのもこのためです。

C++Builder、.NET、サーバー、コマンドライン

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

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

試してみる

Demos\Delphi\EET の Delphi デモでは、1 つのフォーム上でプレイグラウンドに対する往復処理全体を試せます。テスト証明書を読み込んで検証モードで送信し、その後チェックを外して実際の売上を送信すると、pok 付きの受領確認が得られます。Build Message (no send) はオフラインキューが保存する署名済みエンベロープを表示し、Resend Stored Sale は最後の売上を再送信として送り直します。テスト証明書を配布する文書は公開が制限されているため、テスト証明書はデモに含まれていません。eet.gov.cz からダウンロードしてください。

すべてのプロパティ、メソッド、イベントは sgcSign オンラインヘルプに記載されており、sgcSign 国別プロファイルページの EET 2.0 セクションにはコンポーネントの概要がまとめられています。

提供状況

TsgcEETClient は Delphi、C++Builder、.NET 向けの sgcSign 2026.10 に含まれ、sgcSign Server のルートとコマンドラインツールの eet 動詞も同時に提供されます。

ご質問がある場合や、1 月までに準備を整えなければならないレジがある場合は、お問い合わせください。期待どおりに動作しない場合は、リクエストとレスポンスの XML を X-Global-Transaction-Id と一緒にお送りください。コードを書いた本人から返信が届きます。