Seit 2020 muss in Deutschland jedes elektronische Kassensystem jede Geschäftstransaktion durch eine zertifizierte Technische Sicherheitseinrichtung signieren lassen. Auf jedem Beleg müssen Signatur, Signaturzähler und Seriennummer stehen — in der Praxis als QR-Code.

Unsere Rechnungsstellung läuft in DataFlex. Eine TSE dort direkt einzubauen hätte bedeutet, in eine gewachsene Altanwendung HTTPS-Kommunikation, JSON-Verarbeitung und Zertifikatshandling einzuziehen. Genau das sollte vermieden werden.

Entscheidung: Die Signatur wandert komplett aus DataFlex heraus in ein eigenes Skript. DataFlex tauscht nur noch eine simple Textdatei mit vier Feldern aus und weiß nichts von HTTP, Tokens oder JSON.

Architektur

Drei Beteiligte, zwei Übergabepunkte. DataFlex schreibt eine Eingabedatei und startet das Skript; das Skript erledigt die HTTPS-Kommunikation mit dem Signaturdienst und legt das Ergebnis als Ausgabedatei zurück. Dieses Muster war bereits für andere Anbindungen etabliert und konnte unverändert übernommen werden.

   DataFlex            CPS                fiskaly
      │                 │                    │
      │  GO\...9AW      │                    │
      ├────────────────►│   POST /auth       │
      │                 ├───────────────────►│
      │                 │   PUT tx rev=1     │
      │                 ├───────────────────►│
      │                 │   PUT tx rev=2     │
      │                 ├───────────────────►│
      │  OK\...9AW      │◄─── Signatur ──────┤
      │◄────────────────┤                    │
      │                 │                    │
  Beleg mit QR

Der Datenaustausch

Bewusst so einfach wie möglich gehalten: KEY: VALUE-Zeilen, die beide Seiten ohne Parser lesen können. Eingabe sind vier Felder, von denen zwei Vorbelegungen haben.

──── Eingabe ────────────────────────
RNR:     12345
BRUTTO:  29.99
MWST:    NORMAL
ZAHLART: CASH

──── Ausgabe ────────────────────────
BACK:         0
RECEIPT_TYPE: RECEIPT
QR_TEXT:      V0;KASSE-01;Beleg;...
QR_SIGNATUR:  ;MEUCIQD...;BFqE...

Der Rückgabewert BACK ist die einzige Zahl, die DataFlex auswerten muss — die Fehlerklasse ist bereits darin kodiert.

BackBedeutungUrsache
0SigniertAlles in Ordnung, TSE-Daten liegen bei
1EingabefehlerPflichtfeld fehlt in der Eingabedatei
7HTTP / APINetz weg, Timeout, Dienst meldet Fehler
9AnmeldungKey oder Secret ungültig

Was gebaut wurde

Dreizehn Skripte, die sich sauber in drei Gruppen teilen: einmalige Einrichtung, laufender Betrieb, Diagnose. Nur die Betriebsskripte werden aus DataFlex heraus aufgerufen.

Laufender Betrieb

SkriptZweckRückgabe
fisk_bon.cpsBeleg signieren — der produktive PfadQR in zwei Teilen
fiskaly_rechnung.cpsSignieren mit zusätzlichem Druckbelegvolle TSE-Felder
fiskaly_storno.cpsStorno eines abgeschlossenen Belegsvolle TSE-Felder
fiskaly_export.cpsDSFinV-K-Export für die BetriebsprüfungPfad zur ZIP-Datei

Einrichtung und Diagnose

SkriptZweck
fiskaly_tss_create.cpsSicherheitseinrichtung anlegen, liefert den PUK
fiskaly_tss_init.cpsTSS scharfschalten, Admin-PIN setzen
fiskaly_client.cpsKasse als Client registrieren
fiskaly_setup.cpsAlles in einem Durchlauf — früher Stand
fiskaly_auth.cpsNur anmelden: prüft die Zugangsdaten
fiskaly_tss_info.cpsZeigt Zustand und Zähler der TSS
fiskaly_tx.cpsEinzelne Transaktion ohne Dateiaustausch

Dazu zwei Dokumente: eine Projektübersicht und eine Schritt-für-Schritt-Anleitung für den Wechsel von Test auf Live samt Rückfallweg.

Eine Signatur, drei Aufrufe

Ein Beleg entsteht in drei HTTPS-Aufrufen. Die Transaktion wird zuerst eröffnet und erst im zweiten Schritt mit den Beträgen abgeschlossen — so ist auch ein abgebrochener Kassiervorgang nachvollziehbar, wie es die Verordnung verlangt.

1. POST /auth
   → Token, rund 30 Minuten gültig

2. PUT  /tss/{tss}/tx/{uuid}?tx_revision=1
   { "state": "ACTIVE", "client_id": ... }
   → Transaktion eröffnet

3. PUT  /tss/{tss}/tx/{uuid}?tx_revision=2
   { "state": "FINISHED",
     "schema": { "standard_v1": { "receipt": {
        "receipt_type": "RECEIPT",
        "amounts_per_vat_rate":     [...],
        "amounts_per_payment_type": [...] }}}}
   → Signatur, Zähler, QR-Daten

Jeder Skriptlauf holt sich einen frischen Token. Auf ein Zwischenspeichern wurde verzichtet: Die Skripte leben nur Sekunden, und ein abgelaufener Token wäre die unangenehmere Fehlerquelle als ein zusätzlicher Aufruf.

Der QR-Code und das 255-Zeichen-Problem

Die schönste Fundsache des Projekts. Die QR-Daten sind länger als eine DataFlex-Zeichenkette fassen kann. Statt DataFlex umzubauen, trennt das Skript den QR-String direkt vor der Signatur und liefert zwei Teile zurück — das führende Semikolon bleibt am zweiten Teil. DataFlex hängt beide aneinander und ist fertig.

Storno

Ein Storno ist technisch eine eigene signierte Transaktion vom Typ ANNULATION mit negierten Beträgen, optional mit Verweis auf die ursprüngliche Rechnungsnummer. Im produktiven Skript genügt ein negativer Betrag in der Eingabedatei — der Belegtyp schaltet automatisch um.

Export für die Betriebsprüfung

Der Export läuft asynchron: anstoßen, dann warten, dann laden. Das Skript fragt alle fünf Sekunden nach, höchstens fünf Minuten lang, und lädt dann die ZIP-Datei. Ein Zeitraum lässt sich optional mitgeben, sonst kommen alle Daten.

Was uns aufgehalten hat

  • Test und Live sind zwei getrennte Welten hinter derselben Adresse. Nicht die URL entscheidet, sondern der API-Key — Test-Keys tragen ein test_ davor. Die Folge: TSS und Client mussten für Live komplett neu angelegt werden, die Test-Objekte existieren dort nicht.

  • Der PUK wird genau einmal gezeigt. Wer ihn nicht im selben Moment sichert, kann die TSS nie wieder administrieren. Deshalb schreiben die Skripte ihn sofort weg und stellen ihn zusätzlich hervorgehoben in die Konsole.

  • Storno heißt ANNULATION, nicht CANCELLATION. Der zweite Begriff bedeutet „Kassiervorgang mittendrin abgebrochen" und ist etwas völlig anderes. Eine Verwechslung hier erzeugt formal falsche Belege.

  • Das Scharfschalten der TSS ist keine einzelne Aktion. Es sind sechs aufeinander aufbauende Schritte über drei Zustandswechsel und eine Admin-Anmeldung hinweg. Das Einrichtungsskript prüft deshalb zuerst den Ist-Zustand und steigt an der passenden Stelle ein.

  • Die Signatur allein genügt dem Finanzamt nicht. Der DSFinV-K-Tagesabschluss der Kassenbuchhaltung bleibt Aufgabe der DataFlex-Seite; der Signaturdienst liefert nur den TSE-Anteil.

Projektverlauf

DatumSchritt
24.04.Anbindung steht. Anmeldung, TSS, Client, Transaktion, Rechnung und Storno — an einem Tag gegen die Testumgebung gebaut.
25.04.Export ergänzt. DSFinV-K-Export mit asynchronem Warten auf die Fertigstellung.
05.05.Umstellung auf Live. Neue TSS und neuer Client in der Live-Umgebung; Ablauf und Rückfallweg dokumentiert.
10.05.Projektübersicht. Architektur, Dateiübersicht und Stolperfallen festgehalten.
26.05.Belegvariante finalisiert. QR-Aufteilung für DataFlex und automatische Storno-Erkennung am negativen Betrag.
01.08.Ordnung und Absicherung. Eigener Projektordner; Zugangsdaten vor der Übernahme ins Repository ausgeschlossen und durch Vorlagen ersetzt.

Offene Punkte

Die Anbindung läuft produktiv. Drei Dinge sind bekannt und unerledigt — keines davon blockiert den Betrieb.

  • Die Ablage der Zugangsdaten ist noch nicht verschlüsselt. Das Skriptsystem bringt die Mittel dafür bereits mit; sie werden hier noch nicht genutzt.

  • Das All-in-one-Einrichtungsskript ist überholt. Es stammt aus der ersten Sitzung und schaltet die TSS über einen verkürzten Weg scharf, den die späteren Einzelskripte korrigiert haben. Für eine Neueinrichtung sind die Einzelskripte der richtige Weg.

  • Kein Wiederholungsversuch bei Netzstörungen. Fällt die Leitung während der Signatur aus, meldet das Skript einen Fehler und bricht ab. Für den Alltag genügt das, weil der Bediener es sofort sieht — eine automatische Wiederholung gibt es aber nicht.

Wenn das Thema wieder aufgegriffen wird

Zum Aufwärmen genügen drei Schritte: das Anmeldeskript prüft die Zugangsdaten, die TSS-Info muss INITIALIZED zeigen, und ein Testbeleg über die Eingabedatei beweist die ganze Kette. Erst danach lohnt es sich, in die Skripte zu schauen.