====== KassenSichV-Guide — Kassensicherungsverordnung mit Lyx ====== Dieser Guide erklärt den praktischen Einsatz der ''kassensichv''-Bibliothek für die gesetzeskonforme TSE-Anbindung nach **KassenSichV**, **BSI TR-03153** und **DSFinV-K 2.3**. → [[lyx_-_programmiersprache:units:kassensichv|kassensichv (Unit-Referenz)]] · [[lyx_-_programmiersprache:units:kassensichv:manager|kassensichv.manager]] · [[lyx_-_programmiersprache:units:kassensichv:mock|Mock]] · [[lyx_-_programmiersprache:units:kassensichv:rest|REST]] · [[lyx_-_programmiersprache:units:kassensichv:file|USB]] ---- ===== Rechtliche Grundlagen ===== Die **Kassensicherungsverordnung (KassenSichV)** verpflichtet alle Kassensysteme in Deutschland seit dem 1. Januar 2020, jeden Kassenvorgang mit einer zertifizierten TSE (Technische Sicherheitseinrichtung) zu signieren. Jeder Bon muss diese **Pflichtangaben** enthalten: * Seriennummer der zertifizierten TSE * Fortlaufender Signaturzähler * Start- und Endzeitpunkt laut TSE (UTC) * Kryptografischer Signaturwert (ECDSA/SHA-256, Base64) * QR-Code-String nach BSI TR-03153 Anhang A Der **QR-Code-String** hat exakt dieses Format: V0;{TSE-Seriennummer};{StartUTC};{EndUTC};{Signaturzähler};{AnzahlTransaktionen};{Signaturwert} Die ''kassensichv''-Bibliothek erzeugt diesen String automatisch in ''SigErgebnis.qrCode'' — kein manuelles Formatieren nötig. ---- ===== Provider wählen ===== Das zentrale Konzept der Bibliothek: Die **gesamte Kassensoftware** interagiert nur mit dem ''TseManager''. Welche TSE physisch angebunden ist, bestimmt ausschließlich der Provider beim Start: ^ Provider ^ Unit ^ Typischer Einsatz ^ | ''TseMockNew()'' | ''kassensichv.mock'' | Tests, CI/CD, Bondruckvorschau — keine Hardware nötig | | ''TseRestNew(cfg)'' | ''kassensichv.rest'' | Cloud-TSE: Fiskaly, Deutsche Fiskal — billiger Einstieg | | ''TseFileNew(cfg)'' | ''kassensichv.file'' | USB-TSE: Swissbit, Epson — stationäre Kasse mit eigener Hardware | **Wechsel zwischen Providern:** Nur die ''TseXxxNew''-Zeile ändern. Der gesamte Beleg-Code bleibt identisch — das ist der Hauptvorteil der Dependency-Inversion-Architektur. ---- ===== Entwicklung: Mock-Provider ===== Während der Entwicklung und in CI/CD-Pipelines immer den Mock-Provider verwenden. Er benötigt keine Hardware, keine Netzwerkverbindung und keine API-Keys. import kassensichv.types; import kassensichv.mock; import kassensichv.manager; fn main(): int64 { var mgr: int64 := TseMockNew(); var beleg: BelegDaten; beleg.prozessTyp := PROZESSTYP_KASSENBELEG; beleg.kassenNr := "KASSE-001"c; beleg.prozessDaten := "Kaffee;2.50_0.00_0.00_0.00_0.00"c; beleg.umsatz := 250; // Eurocent (kein float — Rundungsfehler!) var sig: int64 := TseProcessBeleg(mgr, addr beleg); var s: SigErgebnis := (sig as *SigErgebnis)^; // Mock-Ausgabe: // V0;MOCK-TSE-0000000000000001;2026-06-12T09:30:00Z;2026-06-12T09:30:01Z;1;1;base64... PrintLn(s.qrCode as pchar); SigErgebnisFree(sig); TseManagerFree(mgr); return 0; } ==== Fehlersimulation ==== Der Mock kann alle Fehlerzustände simulieren — ohne echte TSE-Hardware: // Verbindungsabbruch simulieren (code=503) TseMockSetSimulateError(mgr, 1); var sig: int64 := TseProcessBeleg(mgr, addr beleg); var s: SigErgebnis := (sig as *SigErgebnis)^; // s.success == 0, s.errorMsg enthält "Simulierter Verbindungsfehler" SigErgebnisFree(sig); TseMockSetSimulateError(mgr, 0); // Timeout simulieren (code=408) TseMockSetSimulateTimeout(mgr, 1); // ... gleiche Struktur ... TseMockSetSimulateTimeout(mgr, 0); ---- ===== Produktion: Cloud-TSE (REST) ===== Für Cloud-TSE-Anbieter (Fiskaly, Deutsche Fiskal). Vorteil: Keine Hardware, zentrale Verwaltung, für mobile Kassen und Online-Shops geeignet. import kassensichv.types; import kassensichv.rest; import kassensichv.manager; fn ManagerFuerFiskaly(): int64 { // Konfiguration als JSON-String (in Produktion aus env/config laden) var cfg: pchar := concat( "{\"api_url\":\"https://kassensichv.io/api/v1\","c, "\"api_key\":\""c, EnvGet("FISKALY_API_KEY"c), "\","c, "\"client_id\":\""c, EnvGet("KASSEN_ID"c), "\"}"c ); return TseRestNew(cfg as int64); } **Empfohlene Umgebungsvariablen:** ^ Variable ^ Bedeutung ^ | ''FISKALY_API_KEY'' | Bearer-Token (nie im Code hardcoden!) | | ''KASSEN_ID'' | Kassen-ID beim Cloud-Anbieter | Der REST-Provider wiederholt Requests automatisch bei HTTP 503/504 (bis zu 3 Versuche, exponentielles Backoff: 1s → 2s → 4s). TLS 1.2 wird erzwungen. ---- ===== Produktion: USB-TSE (Dateiprotokoll) ===== Für stationäre Kassen mit USB-TSE-Stick (Swissbit, Epson). Der Stick muss als Dateisystem gemountet sein. import kassensichv.types; import kassensichv.file; import kassensichv.manager; fn ManagerFuerSwissbit(mountPunkt: pchar): int64 { // "{\"base_path\":\"/mnt/tse\",\"timeout_ms\":5000}" var cfg: pchar := concat( "{\"base_path\":\""c, mountPunkt, "\","c, "\"timeout_ms\":5000}"c ); return TseFileNew(cfg as int64); } Vorbereitung unter Linux: sudo mount /dev/sdb1 /mnt/tse Oder dauerhaft via ''/etc/fstab'': UUID=xxxx-yyyy /mnt/tse vfat auto,user,rw 0 0 ---- ===== Workflow 1: Einfacher Kassenbon ===== Der Standardfall für eine einzelne Transaktion (Bezahlung an der Kasse): import kassensichv.types; import kassensichv.mock; import kassensichv.manager; fn EinfacherKassenbon(mgr: int64): void { var beleg: BelegDaten; beleg.prozessTyp := PROZESSTYP_KASSENBELEG; beleg.kassenNr := "KASSE-001"c; beleg.prozessDaten := "Laptop;999.00_0.00_0.00_0.00_0.00"c; beleg.umsatz := 99900; // 999,00 EUR in Cent var sig: int64 := TseProcessBeleg(mgr, addr beleg); var s: SigErgebnis := (sig as *SigErgebnis)^; if s.success == 0 then { Print("[FEHLER] "c); PrintLn(s.errorMsg as pchar); SigErgebnisFree(sig); return; } // Pflichtfelder auf Bon ausgeben Print("TSE-Seriennummer: "c); PrintLn(s.tseSerial as pchar); Print("Signaturzähler: "c); PrintLn(IntToStr(s.sigZaehler)c); Print("Signaturwert: "c); PrintLn(s.sigWert as pchar); Print("QR-Code: "c); PrintLn(s.qrCode as pchar); SigErgebnisFree(sig); } ===== Workflow 2: Mehrstufige Transaktion (Tischbewirtung) ===== Für Restaurants, Servicebetriebe und andere Vorgänge mit Zwischenständen: fn Tischbestellung(mgr: int64, tischNr: int64): void { var beleg: BelegDaten; beleg.prozessTyp := PROZESSTYP_KASSENBELEG; beleg.kassenNr := "KASSE-001"c; // Transaktion öffnen — TSE vergiBt Zähler + Startzeitpunkt var transId: int64 := TseOpenBeleg(mgr, addr beleg); // Vorspeise geliefert (Zwischenstand, optional) beleg.prozessDaten := "Suppe;5.50_0.00_0.00_0.00_0.00"c; beleg.umsatz := 550; var zwi: int64 := TseUpdateBeleg(mgr, transId, addr beleg); SigErgebnisFree(zwi); // Hauptgang hinzu beleg.prozessDaten := "Suppe+Schnitzel;22.50_0.00_0.00_0.00_0.00"c; beleg.umsatz := 2250; var zwi2: int64 := TseUpdateBeleg(mgr, transId, addr beleg); SigErgebnisFree(zwi2); // Rechnung — Transaktion abschließen beleg.prozessDaten := "Suppe+Schnitzel+Dessert;30.00_0.00_0.00_0.00_0.00"c; beleg.umsatz := 3000; var sig: int64 := TseCloseBeleg(mgr, transId, addr beleg); var s: SigErgebnis := (sig as *SigErgebnis)^; if s.success == 1 then { PrintLn(s.qrCode as pchar); // QR-Code auf Bon } else { // COMPLIANCE-WARNUNG: Transaktion möglicherweise offen! Print("[COMPLIANCE] "c); PrintLn(s.errorMsg as pchar); } SigErgebnisFree(sig); free(transId); } ===== Workflow 3: Stornierung ===== Ein Stornobon ist ein eigenständiger Kassenbon mit dem Prozesstyp ''PROZESSTYP_STORNO''. Er wird genauso signiert wie ein normaler Bon — nur der Prozesstyp und die Belegdaten unterscheiden sich: fn Stornierung(mgr: int64, originalBetrag: int64): void { var beleg: BelegDaten; beleg.prozessTyp := PROZESSTYP_STORNO; beleg.kassenNr := "KASSE-001"c; beleg.prozessDaten := "Storno Laptop;-999.00_0.00_0.00_0.00_0.00"c; beleg.umsatz := -99900; // negativer Betrag var sig: int64 := TseProcessBeleg(mgr, addr beleg); var s: SigErgebnis := (sig as *SigErgebnis)^; if s.success == 1 then { PrintLn(s.qrCode as pchar); } SigErgebnisFree(sig); } ===== Workflow 4: Trainingsbetrieb ===== Testkäufe und Mitarbeiterschulungen müssen mit dem Prozesstyp ''PROZESSTYP_TRAINING'' signiert werden. Diese Vorgänge sind steuerlich nicht relevant, aber trotzdem TSE-pflichtig: var beleg: BelegDaten; beleg.prozessTyp := PROZESSTYP_TRAINING; beleg.kassenNr := "KASSE-001"c; beleg.prozessDaten := "Training;0.00_0.00_0.00_0.00_0.00"c; beleg.umsatz := 0; var sig: int64 := TseProcessBeleg(mgr, addr beleg); // ... Bon als "TRAINING" markieren, nicht als echter Kassenbon ===== Workflow 5: DSFinV-K-Export (Finanzamt-Prüfung) ===== Finanzämter können den Export der TSE-Audit-Logs anfordern. Die Bibliothek erzeugt: * ''{path}/tse_export.tar'' — TSE-intern signiertes Archiv (nicht verändern!) * ''{path}/index.json'' — Exportmetadaten nach DSFinV-K 2.3 fn FinanzamtExport(mgr: int64): int64 { var exportPfad: pchar := "/var/kassensichv/export"c; var ok: int64 := TseExportAuditData(mgr, exportPfad as int64, "KASSE-001"c); if ok == 0 then { PrintLn("Export fehlgeschlagen — Verzeichnis nicht beschreibbar?"c); return 0; } // Ergebnis: /var/kassensichv/export/tse_export.tar + index.json PrintLn("DSFinV-K-Export erfolgreich"c); return 1; } **index.json Inhalt:** { "dsfinvk_version": "2.3", "tse_serial": "SWB-0123456789ABCDEF", "kasse_id": "KASSE-001", "export_timestamp": "2026-06-12T09:00:00Z" } ---- ===== Fehlerbehandlung ===== ==== Normale Fehler ==== Alle Fehler werden über ''SigErgebnis.success'' und ''SigErgebnis.errorMsg'' zurückgegeben: var sig: int64 := TseProcessBeleg(mgr, addr beleg); var s: SigErgebnis := (sig as *SigErgebnis)^; if s.success == 0 then { Print("TSE-Fehler: "c); PrintLn(s.errorMsg as pchar); SigErgebnisFree(sig); return 0; } // Bon drucken ... SigErgebnisFree(sig); ==== Compliance-kritisch: Offene Transaktionen ==== Wenn ''TseCloseBeleg'' mit ''success=0'' zurückkommt, ist die TSE-Transaktion **möglicherweise noch offen**. Das ist ein Compliance-Problem — die Kassensoftware muss reagieren: var sig: int64 := TseCloseBeleg(mgr, transId, addr beleg); var s: SigErgebnis := (sig as *SigErgebnis)^; if s.success == 0 then { // PFLICHT: Vorfall protokollieren Print("[COMPLIANCE] Offene TSE-Transaktion — TransId: "c); PrintLn(transId as pchar); Print("[COMPLIANCE] Fehler: "c); PrintLn(s.errorMsg as pchar); // TSE-Status prüfen var status: int64 := TseGetStatus(mgr); Print("[COMPLIANCE] TSE-Status: "c); PrintLn(status as pchar); free(status); // Bon NICHT drucken bis Situation geklärt SigErgebnisFree(sig); free(transId); return 0; } **Faustregel:** Nie einen Kassenbon ausgeben, wenn ''success=0''. Die Kassensoftware muss die offene Transaktion manuell abschließen oder beim TSE-Anbieter melden. ==== Fehlercode-Tabelle ==== ^ Code ^ Bedeutung ^ Empfohlene Reaktion ^ | 400 | Konfigurationsfehler | Programm nicht starten, Konfiguration prüfen | | 403 | Exportpfad nicht schreibbar | Verzeichnis anlegen, Rechte prüfen | | 408 | Timeout | Verbindung zur TSE prüfen, USB-Stick eingesteckt? | | 409 | Offene Transaktion | Vorfall protokollieren, Support kontaktieren | | 500 | TSE-interner Signierfehler | TSE-Hardware defekt? TSE-Anbieter kontaktieren | | 503 | Verbindungsfehler | Netzwerk (REST) oder USB-Mount (File) prüfen | ---- ===== Initialisierung beim Programmstart ===== Konfigurationsfehler möglichst früh erkennen — nicht erst beim ersten Bon: import kassensichv.rest; import kassensichv.manager; fn TseInitPruefen(mgr: int64): int64 { // Status als erstes abfragen — erkennt Konfigurationsfehler sofort var status: int64 := TseGetStatus(mgr); if status == 0 then { PrintLn("[FEHLER] TSE konnte nicht initialisiert werden"c); return 0; } // Seriennummer prüfen — bei REST: wird von API geladen var serial: int64 := TseGetSerial(mgr); if serial == 0 then { PrintLn("[FEHLER] TSE-Seriennummer nicht abrufbar"c); free(status); return 0; } Print("TSE bereit: "c); PrintLn(serial as pchar); free(serial); free(status); return 1; } fn main(): int64 { var mgr: int64 := TseRestNew(cfg as int64); if TseInitPruefen(mgr) == 0 then { TseManagerFree(mgr); return 1; // Programm abbrechen } // Kassensoftware starten ... return 0; } ---- ===== Umsatz-Berechnung: Eurocent, kein Float ===== **Niemals** ''f64'' für Geldbeträge verwenden. IEEE-754-Gleitkommazahlen haben Rundungsfehler (0.1 + 0.2 ≠ 0.3). Das ''umsatz''-Feld in ''BelegDaten'' ist ''int64'' in **Eurocent**: // FALSCH — Rundungsfehler bei 0.1 + 0.2 var betrag: f64 := 9.99; beleg.umsatz := betrag as int64; // kann 998 oder 999 ergeben! // RICHTIG — integer Eurocent beleg.umsatz := 999; // 9,99 EUR = 999 Cent // RICHTIG — Addition var teilbetrag1: int64 := 550; // 5,50 EUR var teilbetrag2: int64 := 800; // 8,00 EUR beleg.umsatz := teilbetrag1 + teilbetrag2; // exakt 1350 Cent = 13,50 EUR Das DSFinV-K-Format erwartet Beträge mit Punkt als Dezimaltrennzeichen (''9.99'') im ''prozessDaten''-String — das ist reines Textformat und unabhängig von der internen Cent-Rechnung. ---- ===== Prozessdaten-Format (DSFinV-K) ===== Das ''prozessDaten''-Feld folgt dem **DSFinV-K 2.3-Schema**. Für einfache Kassenbons: {Artikel};{Betrag_19%}_{Betrag_7%}_{Betrag_0%}_{Betrag_special}_{Betrag_sonstig} Mehrwertsteuer-Zuordnung nach Steuersatz: ^ Spalte ^ Steuersatz ^ Typischer Einsatz ^ | Feld 1 (19%) | Normaler MwSt.-Satz | Elektronik, Kleidung, Haushaltsware | | Feld 2 (7%) | Ermäßigter MwSt.-Satz | Lebensmittel, Bücher, ÖPNV-Tickets | | Feld 3 (0%) | Steuerfreie Umsätze | Exportlieferungen, Versicherungen | | Feld 4 | Besonderer Steuersatz | Spezialfälle | | Feld 5 | Sonstige | Trinkgelder, Gutscheineinlösung | // Laptop (19% MwSt.): 999,00 EUR beleg.prozessDaten := "Laptop;999.00_0.00_0.00_0.00_0.00"c; // Buch (7% MwSt.): 15,90 EUR beleg.prozessDaten := "Buch;0.00_15.90_0.00_0.00_0.00"c; // Gemischter Einkauf beleg.prozessDaten := "Laptop+Buch;999.00_15.90_0.00_0.00_0.00"c; // Restaurant-Bestellung (Speisen 7%, Getränke 19%) beleg.prozessDaten := "Getränke+Speisen;8.50_12.00_0.00_0.00_0.00"c; Für vollständige DSFinV-K-Anforderungen (komplexe Bonpflicht-Details, Kassenabschlüsse) die offizielle **DSFinV-K 2.3-Spezifikation** des BMF (Bundesministerium der Finanzen) konsultieren. ---- ===== Testen ===== ==== Einheitstests mit Mock ==== import kassensichv.types; import kassensichv.mock; import kassensichv.manager; fn TestEinfacherBon(): int64 { var mgr: int64 := TseMockNew(); var beleg: BelegDaten; beleg.prozessTyp := PROZESSTYP_KASSENBELEG; beleg.kassenNr := "TEST-001"c; beleg.prozessDaten := "Test-Artikel;1.00_0.00_0.00_0.00_0.00"c; beleg.umsatz := 100; var sig: int64 := TseProcessBeleg(mgr, addr beleg); var s: SigErgebnis := (sig as *SigErgebnis)^; var ok: int64 := 1; // success muss 1 sein if s.success == 0 then { PrintLn("FAIL: success=0"c); ok := 0; } // Zähler muss 1 sein (erster Bon) if s.sigZaehler != 1 then { PrintLn("FAIL: sigZaehler != 1"c); ok := 0; } // QR-Code muss mit V0;MOCK beginnen // (Vergleich mit StringStartsWith aus std.string) SigErgebnisFree(sig); TseManagerFree(mgr); return ok; } fn TestSignaturZaehlerMonoton(): int64 { var mgr: int64 := TseMockNew(); var prevZaehler: int64 := 0; var i: int64 := 1; while i <= 5 do { var b: BelegDaten; b.prozessTyp := PROZESSTYP_KASSENBELEG; var sig: int64 := TseProcessBeleg(mgr, addr b); var s: SigErgebnis := (sig as *SigErgebnis)^; if s.sigZaehler <= prevZaehler then { PrintLn("FAIL: Zähler nicht monoton steigend"c); SigErgebnisFree(sig); TseManagerFree(mgr); return 0; } prevZaehler := s.sigZaehler; SigErgebnisFree(sig); i := i + 1; } TseManagerFree(mgr); return 1; } fn TestTimeout(): int64 { var mgr: int64 := TseMockNew(); TseMockSetSimulateTimeout(mgr, 1); var b: BelegDaten; b.prozessTyp := PROZESSTYP_KASSENBELEG; var sig: int64 := TseProcessBeleg(mgr, addr b); var s: SigErgebnis := (sig as *SigErgebnis)^; var ok: int64 := 1; if s.success != 0 then { PrintLn("FAIL: Timeout nicht erkannt"c); ok := 0; } SigErgebnisFree(sig); TseManagerFree(mgr); return ok; } ==== BSI TR-03153 QR-Code-Validierung ==== Die BSI TR-03153 (Anhang A) enthält offizielle Testvektoren für das QR-Code-Format. Beim Mock lässt sich das Format-Muster prüfen: Format: V0;{7 Felder, semikolon-getrennt} Feld 1: "V0" (fest) Feld 2: TSE-Seriennummer (nicht leer) Feld 3: Startzeitpunkt UTC (ISO 8601, endet auf "Z") Feld 4: Endzeitpunkt UTC (ISO 8601, endet auf "Z") Feld 5: Signaturzähler (positive ganze Zahl) Feld 6: Anzahl Transaktionen (positive ganze Zahl) Feld 7: Signaturwert (Base64, nicht leer) ---- ===== Provider-Entscheidungsguide ===== Welche Art TSE brauche ich? │ ├── Entwicklung / Tests / CI? ──────────────────► TseMockNew() │ (kassensichv.mock) │ ├── Stationäre Kasse, eigene Hardware gewünscht? │ │ │ ├── USB-TSE vorhanden (Swissbit / Epson)? │ │ └── Ja ─────────────────────────► TseFileNew(cfg) │ │ (kassensichv.file) │ └── Nein → weiter zu Cloud │ └── Mobile Kasse / Online-Shop / kein Sticks-Management? └──────────────────────────────────────────► TseRestNew(cfg) (kassensichv.rest) Fiskaly / Deutsche Fiskal ^ Kriterium ^ Mock ^ REST (Cloud) ^ File (USB) ^ | Hardware nötig | nein | nein | USB-TSE-Stick | | Netzwerk nötig | nein | ja | nein | | Monatliche Kosten | nein | ja (Anbietergebühr) | nein (nach Einmalkauf) | | Geeignet für Tests | ja | Sandbox | nein (Hardware) | | Offline-Betrieb | ja | nein | ja | | Mobile Kasse | — | empfohlen | möglich (USB-Hub) | | Zentrales Management | — | ja | nein | ---- ===== Checkliste vor der Inbetriebnahme ===== * [ ] TSE beim BSI-zertifizierten Anbieter angemeldet (Fiskaly, Swissbit, ...) * [ ] TSE innerhalb von **4 Wochen nach Inbetriebnahme** beim Finanzamt gemeldet (§ 146a AO) * [ ] Alle Bonds enthalten die 7 Pflichtfelder nach KassenSichV / BSI TR-03153 * [ ] ''SigErgebnis.success'' wird vor Bonausgabe geprüft * [ ] Offene Transaktionen (''success=0'' bei ''TseCloseBeleg'') werden protokolliert und behandelt * [ ] ''TseExportAuditData'' wurde getestet und ist einsatzbereit * [ ] Exportverzeichnis hat die richtigen Schreibrechte * [ ] Konfiguration (API-Key, Kassen-ID) kommt aus Umgebungsvariablen oder verschlüsseltem Config — **nie im Quellcode hardcoden** * [ ] CI/CD-Tests laufen mit Mock-Provider * [ ] BSI-Testvektoren für QR-Code-Format wurden geprüft > **Haftungshinweis:** Diese Bibliothek ist ein technisches Hilfsmittel. Kassenbetreiber sind für die gesetzeskonforme Integration und den ordnungsgemäßen Betrieb verantwortlich. Bei Unklarheiten zur rechtlichen Einordnung steuerlichen oder rechtlichen Rat einholen. ---- ===== Weiterführend ===== * [[lyx_-_programmiersprache:units:kassensichv|kassensichv — Unit-Referenz (alle 6 Units)]] * [[lyx_-_programmiersprache:units:kassensichv:manager|kassensichv.manager — vollständige API]] * [[lyx_-_programmiersprache:units:kassensichv:exceptions|kassensichv.exceptions — Fehlerbehandlung]] * [[lyx_-_programmiersprache:guides:welche-unit|Welche Unit? — Entscheidungsguide]] Letzte Aktualisierung: 2026-06-12