kasseneck_api
Austrian fiscal cash register (RKSV) for Flutter — signed receipts, card payments, receipt printing.
Kasseneck ist eine österreichische Registrierkasse nach RKSV. Dieses Paket ist
der Flutter-Client dafür: Ihre App stellt Belege aus, storniert sie, nimmt
Kartenzahlungen entgegen und druckt Bons. Es ist der Zwilling des
JavaScript-Pakets
@kreiseck/kasseneck-api:
dieselben Endpunkte, dieselben Modelle, dieselben Enum-Werte, im Test
gegeneinander geprüft.
Was eine Registrierkasse in Österreich können muss
Die Registrierkassen- und Belegerteilungspflicht steht in § 131b der Bundesabgabenordnung, die technischen Anforderungen an die Sicherheitseinrichtung in der Registrierkassensicherheitsverordnung (RKSV). Daraus ergibt sich eine ganze Kette von Aufgaben — die Tabelle zeigt, welche davon diese Software übernimmt und welche beim Betrieb selbst bleiben:
| Aufgabe | Wo sie erledigt wird |
|---|---|
| Signaturerstellungseinheit — jede Barzahlung wird signiert | Kasseneck, nichts zu installieren |
| Verkettung und DEP — jeder Beleg trägt den vorigen, das Protokoll ist exportierbar | Kasseneck-Backend |
| Startbeleg, Monatsbeleg, Jahresbeleg | Kasseneck, automatisch |
| Meldungen an FinanzOnline — Anmeldung, Ausfall, Außerbetriebnahme | Kasseneck-Backend |
| Belegerteilungspflicht — jeder Kunde bekommt einen Beleg | dieses Paket — Bon, Bildschirm, PDF oder Link |
| Ausfall der Signatureinheit — Sammelbeleg, Meldung, Nachsignatur | Kasseneck, automatisch |
| Kassennachschau — der Prüfer verlangt das DEP | Kasseneck, Export auf Knopfdruck |
| Anmeldung der Kasse, Aufbewahrung, steuerliche Würdigung | beim Unternehmer |
Kurz: Sie bauen die Kassenoberfläche, nicht die Sicherheitseinrichtung. Ein
sellReceipt(...) erzeugt einen signierten, verketteten und im DEP abgelegten
Beleg. Die Signaturkette wird gegen das offizielle Prüfwerkzeug des BMF getestet.
Kein Rechts- oder Steuerrat. Dieser Abschnitt beschreibt, was die Software tut. Er ersetzt keine Beratung und begründet keine Zusicherung, dass ein bestimmter Betrieb damit alle Pflichten erfüllt. Verbindlich sind die Bundesabgabenordnung, die RKSV und die Erlässe des BMF; die Verantwortung für Anmeldung, Betrieb und Aufbewahrung bleibt beim Unternehmer. Ausführlicher und mit Quellen: kasseneck.at/wissen. Stand: September 2026.
Lieber eine fertige Kasse?
Dieses Paket ist für alle, die eine eigene App bauen. Wer einfach kassieren will, muss nichts davon programmieren:
- Kasseneck — die fertige Registrierkasse für Telefon, Tablet und Browser, inklusive Signaturerstellungseinheit und FinanzOnline-Anmeldung.
- Lösungen nach Branche — vom Lokal bis zum Taxi.
- Preise · API-Doku
- Kontakt — auch für Kassenwechsel, Partnerschaften und eigene Integrationen.
Was drin ist
- RKSV-Belege — Standard, Storno, Null und Training; signierte JWS-Kette samt QR-Code
- Alle österreichischen Steuersätze — inklusive der 4,9 % für Grundnahrungsmittel (seit 1. Juli 2026)
- Beträge in ganzen Cent — intern nie Fließkomma, also keine Rundungsdrift
- Kartenzahlung ab Werk — hobex (Cloud und Terminal-HPS), myPOS, GP Tom, SumUp — und jedes andere Verfahren über
CreditCardProvider.custom - Gutscheine — Wert- und Rabattgutscheine, verkaufen und einlösen, mit anteiliger Steueraufteilung
- Trinkgeld — je Kassen-Benutzer, bar oder mit Karte; Mitarbeiter-Trinkgeld läuft als 0-%-Durchlauf, Chef-Trinkgeld als Erlös, auf die Steuersätze des Belegs verteilt
- Druck — Bluetooth und WLAN (ESC/POS) sowie der eingebaute myPOS-Drucker
- Fertiges Beleg-Widget für die Anzeige am Schirm
- Berichte und Rechnungen — Tages- und Monats-PDF
- Stripe-Zahllinks für Fern- und Onlinezahlungen
Voraussetzungen
- Flutter, Dart ab 3.6
- Ein Kasseneck-API-Schlüssel und ein Kassen-Token — Kontakt
- Für Kartenzahlung und Bluetooth-Druck ein Android-Gerät oder -Terminal: die Terminal-Pakete (myPOS, SumUp) sind Android-only. Der Rest des Pakets läuft überall, wo Flutter läuft.
Einbinden
dependencies:
kasseneck_api: ^6.12.1
flutter pub get
Schnellstart
import 'package:kasseneck_api/kasseneck_api.dart';
import 'package:kasseneck_api/models/kasseneck_item.dart';
import 'package:kasseneck_api/enums/vat_rate.dart';
import 'package:kasseneck_api/enums/keck_payment_method.dart';
final kasseneck = KasseneckApi(
apiKey: 'IHR_API_SCHLUESSEL',
cashregisterToken: 'IHR_KASSEN_TOKEN',
);
// Ein Barverkauf mit zwei Posten — Preise sind ganze Cent (320 = 3,20 €)
final receipt = await kasseneck.sellReceipt(
paymentMethod: KeckPaymentMethod.cash,
customerDetails: ['Max Mustermann'],
items: [
KasseneckItem(name: 'Kaffee', quantity: 2, vat: VatRate.vat20, priceCents: 320),
KasseneckItem(name: 'Brot', quantity: 1, vat: VatRate.vat4komma9, priceCents: 240),
// oder, wenn Sie Euro-Doubles haben: KasseneckItem.euro(..., singlePrice: 3.20)
],
);
print('Beleg ${receipt?.receiptId} — signiert: ${receipt?.signatureSuccess}');
Modelle und Enums liegen in eigenen Dateien — importieren Sie die, die Sie brauchen (
models/…,enums/…). Zahlung, Gutschrift, Null- und Trainingsbelege laufen alle über dieselbeKasseneckApi-Instanz. Stornos gehen überRegisterReceiptClient.stornieren(siehe unten);cancelReceiptundcreateCancelReceiptaufKasseneckApisind der veraltete alte Weg.
Storno, ganz oder in Teilen
Ein Storno ist ein neuer signierter Beleg, der einen bestehenden umkehrt —
vollständig oder teilweise. Der Kassen-Client
(package:kasseneck_api/kasse.dart, RegisterReceiptClient) spricht den
Endpunkt cancelReceipt des Backends an; der Server negiert die Zeilen, prüft
Restmengen und Rechte, verknüpft beide Belege und druckt die Bezugszeile auf den
Stornobeleg.
import 'package:kasseneck_api/kasse.dart';
final ergebnis = await client.stornieren(
originalReceiptId: 'KASSE1-ID-42',
grund: 'fehleingabe', // Katalog: stornogruende
positionen: [(index: 0, menge: 1)], // weglassen = alles Verbliebene stornieren
anmerkung: 'Kunde wollte nur eine', // interne Notiz, wird nie gedruckt
);
ergebnis.beleg; // der signierte Stornobeleg
ergebnis.restmengen; // was je Zeile des Originals noch offen ist
Entscheiden Sie am Fehlercode, nie an der Meldung. Jeder fachliche Fehler von
cancelReceipt trägt KasseneckApiError.code aus stornoFehlercodes — etwa
bereits_storniert, menge_ueber_rest, nur_eigene_belege. Die deutsche
message ist für die Anzeige und kann sich ändern.
try {
await client.stornieren(originalReceiptId: id, grund: 'fehleingabe');
} on KasseneckApiError catch (e) {
switch (e.code) {
case 'bereits_storniert': // Beleg als storniert zeigen, Knopf sperren
case 'menge_ueber_rest': // Restmengen neu laden (jemand war schneller)
case 'nur_eigene_belege': // den Chef fragen
break;
default:
rethrow;
}
}
Gutscheine. Ein Wertgutschein wird nur bei einem Vollstorno gespiegelt (ohne
positionen) — er ist unteilbar. Ein Rabattgutschein steckt bereits im Umsatz des
Originals; jedes Storno nimmt ihn anteilig zur stornierten Menge zurück:
3 × 10 € mit 6 € Rabatt sind 8 € je Stück, der Stornobeleg zeigt also „−10,00"
und dazu eine Zeile „Gutschein-Ausgleich +2,00". Was ein Storno gewährt hat,
steht an seinem Eintrag in receipt.cancellations als promoAdjustmentCents
(Cent je Steuerkorb) — die Kasse kann es anzeigen und muss es nie selbst rechnen.
Vor einem Storno liefert restmengen(beleg) die Restmengen aus der
cancellations-Liste des Originals; maßgeblich bleibt der Server.
Beleg per E-Mail senden
Der Gast nennt an der Kasse eine Adresse und bekommt einen Link auf die öffentliche Belegseite — ohne PDF im Anhang: diese Seite nutzt dasselbe Zeilenmodell wie Schirm und Bondrucker und bietet dort ein PDF an. Das Belegdokument selbst bleibt byteidentisch (es ist das DEP, § 131 BAO); das Backend protokolliert den Versand daneben.
// Kassen-Anmeldung (package:kasseneck_api/kasse.dart)
final erg = await client.belegSenden(fullReceiptId: beleg.fullReceiptId, an: 'gast@example.com');
// API-Schlüssel (package:kasseneck_api/kasseneck_api.dart)
final erg2 = await kasseneck.belegSenden(fullReceiptId: id, an: 'gast@example.com');
erg.to; // normalisierte Adresse, wie das Backend sie protokolliert hat
erg.at; // ISO-Zeitstempel in Wiener Zeit
erg.via; // 'eigen' | 'plattform' | 'plattform-fallback'
Entscheiden Sie auch hier am Fehlercode — KasseneckApiError.code aus
belegMailFehlercodes: adresse_ungueltig (korrigieren lassen), zu_oft (das
Backend erlaubt fünf Mails je Beleg in 24 Stunden und 30 je Kasse pro Stunde —
später, nicht jetzt), versand_fehlgeschlagen (nichts ist hinausgegangen, ein
neuer Versuch ist in Ordnung), beleg_nicht_gefunden (unbekannter Beleg oder
einer, der zu einer anderen Kasse gehört — das Backend antwortet bewusst in
beiden Fällen gleich). Die Adresse prüft allein das Backend: eine zweite,
strengere Prüfung hier würde Adressen abweisen, die der Server annimmt.
Kartenzahlung
Kartenzahlung läuft ab Werk mit mehreren Terminals — und Sie sind an keines gebunden:
| Verfahren | Wie |
|---|---|
| hobex Cloud (empfohlen) | HobexCloudPayments — pay(...) mit aufgelöstem, dreiwertigem Ausgang |
| hobex HPS (Terminal vor Ort, empfohlen) | HpsPayments — pay/refund/cancel, derselbe dreiwertige Ausgang |
| myPOS · GP Tom · SumUp | unterstützt und am Beleg ausgewiesen |
| Jedes andere Terminal | CreditCardProvider.custom — eigene Kartendaten übergeben |
Welches Terminal auch immer: das Ergebnis geht als cardPaymentData an
sellReceipt(...) und wird am Beleg gespeichert und gedruckt.
Warum HpsPayments/HobexCloudPayments statt des Terminals direkt: Eine
Kartenzahlung hat drei mögliche Ausgänge, nicht zwei — genehmigt, sicher
abgelehnt, oder unbekannt (Zeitablauf, abgerissene Verbindung, das Terminal hat
nie geantwortet). „Unbekannt" als „abgelehnt" zu behandeln und es erneut zu
versuchen, ist genau der Weg, auf dem ein Kunde zweimal belastet wird. Beide
Klassen legen die Transaktionskennung vor dem ersten Netzaufruf fest und
lösen dieselbe Kennung beim Terminal bzw. in der Cloud auf, wenn die erste
Antwort verlorengeht, statt still einen neuen Versuch zu starten — eine verlorene
Antwort endet also in CardPaymentOutcome.unresolved (Kennung behalten, später
auflösen) statt geraten zu werden.
Beispiel — hobex-Terminal vor Ort (HPS) zum signierten Beleg
import 'package:kasseneck_api/hobex_hps.dart'; // HpsClient, HpsPayments, HpsResult, CardPaymentOutcome, HobexReceipt
final hps = HpsPayments(HpsClient(tid: '3600335')); // TID ohne führende Null
// Die Kennung steht fest, BEVOR die Anfrage hinausgeht — sofort speichern, damit
// eine verlorene Antwort später aufgelöst und nicht blind wiederholt wird.
final transactionId = HpsClient.newTransactionId();
final result = await hps.pay(amount: 12.50, transactionId: transactionId);
switch (result.outcome) {
case CardPaymentOutcome.approved:
break; // weiter unten
case CardPaymentOutcome.declined:
return; // sicher kein Geld geflossen — ein neuer Versuch ist unbedenklich
case CardPaymentOutcome.unresolved:
// Nicht innerhalb des Auflösungsbudgets geklärt (90 s, einstellbar).
//
// Hier NICHT wiederholen. Am echten Terminal gemessen (26.08.2026): dieselbe
// transactionId erneut zu senden startet einen ZWEITEN Kartenvorgang — das
// Terminal erkennt sie nicht als dieselbe Transaktion. Eine Wiederholung ist
// eine echte zweite Belastung, keine gefahrlose Wiedervorlage.
//
// `transactionId` behalten, den Ausgang zuerst auflösen —
// `HpsClient.transactionStatus(...)`, sobald das Terminal wieder antwortet —
// und erst auf einen bekannten Ausgang hin handeln.
// doc/kartenzahlung.md beschreibt, was die einzelnen Antwortcodes bedeuten.
return;
}
// Terminal-Ergebnis übernehmen, dann den signierten Beleg erzeugen.
final card = HobexReceipt.fromHps(result.response!);
await kasseneck.sellReceipt(
paymentMethod: KeckPaymentMethod.creditCard,
creditCardProvider: card.creditCardProvider, // hobexHps
cardPaymentId: card.transactionId,
cardPaymentData: card.toCardPaymentData(),
items: [KasseneckItem(name: 'Mittagessen', quantity: 1, vat: VatRate.vat10, priceCents: 1250)],
);
Ebenfalls vorhanden: hps.refund(...), hps.cancel(...) — mit demselben
aufgelösten Ausgang. Ein HpsObserver am Konstruktor von HpsPayments
protokolliert Anfragen, Fehlschläge und den Weg, auf dem ein Ausgang aufgelöst
wurde.
Beispiel — hobex Cloud zum signierten Beleg
import 'package:kasseneck_api/kasseneck_api.dart'; // HobexCloudPayments, HobexCloudResult, CardPaymentOutcome
final cloud = HobexCloudPayments(kasseneck);
// Dieselbe Regel wie bei HPS: die Kennung legt der Aufrufer fest, bevor die Anfrage hinausgeht.
final transactionId = KasseneckApi.newHobexTransactionId();
final result = await cloud.pay(transactionId: transactionId, amount: 12.50);
switch (result.outcome) {
case CardPaymentOutcome.approved:
break; // weiter unten
case CardPaymentOutcome.declined:
return; // sicher kein Geld geflossen — ein neuer Versuch ist unbedenklich
case CardPaymentOutcome.unresolved:
// Nicht im Auflösungsbudget geklärt. NICHT blind wiederholen — `transactionId`
// behalten und später auflösen, siehe das HPS-Beispiel oben.
return;
}
final card = result.receipt!;
await kasseneck.sellReceipt(
paymentMethod: KeckPaymentMethod.creditCard,
creditCardProvider: card.creditCardProvider,
cardPaymentId: card.transactionId,
cardPaymentData: card.toCardPaymentData(),
items: [KasseneckItem(name: 'Mittagessen', quantity: 1, vat: VatRate.vat10, priceCents: 1250)],
);
HobexCloudPayments hat kein cancel() — eine Cloud-Gutschrift läuft weiterhin
über den rohen Aufruf kasseneck.hobexRefund(...) (siehe unten), unaufgelöst wie
der einfache Aufruf.
Roher Zugriff — HpsClient / kasseneck.hobexPay(...)
Sowohl der lokale HpsClient (import 'package:kasseneck_api/hobex_hps.dart';)
als auch die Cloud-Aufrufe kasseneck.hobexPay(...) / hobexRefund(...) bleiben
direkt verfügbar, für volle Kontrolle über die Anfrage. Keiner von beiden löst
den Ausgang auf: ein roher Aufruf, der nie eine Antwort bekommt, bleibt für
immer ungeklärt — einen Zahlungsablauf darauf zu bauen heißt, genau das Problem
noch einmal zu lösen, das HpsPayments und HobexCloudPayments bereits lösen,
mit dem echten Risiko, die Frage „wurde belastet?" ausgerechnet unter den
Bedingungen falsch zu beantworten (Zeitablauf, Verbindungsabriss), unter denen
eine falsche Antwort teuer ist. Zum rohen Client greifen Sie nur, wenn Sie etwas
brauchen, das die aufgelöste Hülle nicht zeigt (etwa hps.diagnosis(),
hps.transactionStatus(...)).
// Bluetooth (ESC/POS)
await kasseneck.initBluetoothPrinter(printerAddress: 'AA:BB:CC:DD:EE:FF');
await receipt!.printReceiptBluetooth();
// QR verstümmelt oder fehlt? Drucker verstehen unterschiedliche Befehle:
await receipt.printReceiptBluetooth(qrMode: QrPrintMode.imageBitImage); // oder .native
// Manche günstigen Drucker kennen nur das ältere QR-Modell 1:
await receipt.printReceiptBluetooth(qrMode: QrPrintMode.nativeModel1);
// Der native Befehl bemisst seine Module selbst am Papier (Ruhezone inbegriffen),
// der QR wird also nie breiter als der Kopf drucken kann. Begrenzen Sie ihn, wenn
// er kleiner oder größer sein soll — die Grenze vergrößert nie über das Passende hinaus:
await receipt.printReceiptBluetooth(qrGroesse: QrModulGroesse.gross);
// WLAN
await kasseneck.initWifiPrinter('192.168.0.50', KeckPaperSize.mm80);
await receipt.printReceiptWifi();
// Kassenlade öffnen
await KasseneckApi.openCashDrawer();
Beleg am Schirm
Ein fertiges Widget zeichnet den ganzen Beleg (Logo, Posten, Steuertabelle, QR, Kartendaten):
KeckReceiptWidget(receipt: receipt);
Berichte und Rechnungen
final monthly = await kasseneck.downloadMonthlyReport(ReportMonth.now()); // Uint8List (PDF)
final daily = await kasseneck.downloadDailyReport(DateTime.now());
final history = await kasseneck.getReceipts(start, end);
RKSV im Detail
Jeder Beleg ist verkettet und signiert (ES256 / JWS) und liegt als
maschinenlesbare QR-Nutzlast vor, genau wie die RKSV es verlangt. Ausfälle der
Signatureinheit werden erkannt (receipt.signatureSuccess /
receipt.isSigFailed) und am Beleg ausgewiesen.
Versionen
Das Paket folgt der semantischen Versionierung — was sich wann geändert hat, steht im CHANGELOG.
Lizenz
MIT — siehe LICENSE.
Kasseneck ist ein Produkt von Kreiseck Software Solutions aus Salzburg — Apps, Kassensysteme und Automatisierungen. Fragen zur Schnittstelle, zu eigenen Integrationen oder zu einer Partnerschaft: kasseneck.at/kontakt.
Libraries
- enums/cashbox_status
- enums/credit_card_provider
- enums/keck_invoice_payment_methode
- enums/keck_month
- enums/keck_paper_size
- enums/keck_payment_method
- enums/qr_print_mode
- enums/receipt_print_type
- enums/receipt_type
- enums/signature_status
- enums/stripe_link_mode
- enums/vat_rate
- enums/voucher_action
- enums/voucher_type
- hobex_hps
- hobex Payment Service (HPS) — typed client for the terminal's local REST
API (
http://127.0.0.1:8080when the app runs on the terminal). - kasse
- Die Kasse am Tresen: Einstellungen, Warenkorb, Kassieren und Belege — gemeinsam von Browser-Kasse und App.
- kasseneck_api
- models/beleg_layout
- Beleg-Zeilenmodell (Zwilling von
@kreiseck/kasseneck-api/receiptReceiptLayout): das Backend liefert es beigetReceiptalslayoutmit, damit App, Bondrucker, Browser-Kasse und PDF dieselben Zeilen zeigen — Kopf/Fuß wie beim Ausstellen des Belegs, Belegart-Aufdruck (STORNOBELEG, TRAININGSBELEG, NULLBELEG …), reduzierter Nullbeleg, Testkasse/Testsignatur. - models/beleg_raster
- Zeichenraster (Zwilling von
renderReceiptGridim JS-Paket@kreiseck/kasseneck-api): der Beleg als Zeilen mit exakt N Zeichen (58 mm = 32, 80 mm = 48) — die eine Wahrheit für Bildschirm, Bondruck und PDF. Regeln (fest, damit überall dasselbe herauskommt): - models/cashregister
- models/hobex_receipt
- models/kasseneck_item
- models/kasseneck_receipt
- models/keck_customer
- models/keck_customer_address
- models/keck_invoice
- models/keck_invoice_item
- models/keck_print_result
- models/keck_sepa_info
- models/keck_tip
- models/keck_tip_person
- models/keck_user
- models/keck_voucher
- models/print_paper
- models/report_month
- models/stripe_url_seesion
- models/sumup_checkout_response
- printing
- Oeffentliches Barrel fuer den vendierten ESC/POS-Druck-Stack.
- register
- Kopplung und Anmeldung eines Kassengeräts (RKSV-Kasse).
- services/keck_printer
- services/logo_service
- services/printer_service
- services/rksv_service
- services/sumup_service
- services/vienna_time
- widgets/keck_receipt_lines_widget
- widgets/keck_receipt_widget