kasseneck_api
Austrian fiscal cash register (RKSV) for Flutter: signed receipts, card payments, receipt printing.
kasseneck_api is the Flutter client for Kasseneck, a fiscal cash register (Registrierkasse) for Austria that follows the RKSV, the Austrian cash register security regulation. Your Flutter app sells, cancels and prints receipts; the Kasseneck backend does the receipt signing, chains every receipt into the data capture log (DEP) and reports to FinanzOnline. The package also covers card payments (hobex terminal and cloud, Stripe payment links), ESC/POS receipt printers and invoices.
It is the twin of the JavaScript package
@kreiseck/kasseneck-api:
both share endpoint names, enum values, error codes and golden receipts, and the
test suite checks them against each other. The partner API of the JavaScript
package is server-to-server only and deliberately not part of this package.
Version 10 speaks the English API /v3 and nothing else, and its own
surface is English too: class, field, parameter and enum names, error codes,
library paths. Upgrading from 9.x is one breaking step; see
Upgrading from 9.x.
Contents
- Quick start
- Upgrading from 9.x
- Libraries
- What a fiscal cash register in Austria has to do
- Features
- Requirements and platforms
- Three ways to authenticate
- The /v3 wire: marker, fail closed, unknown outcome
- Selling: items, amounts, vouchers, tips
- Cancellations (Storno)
- Sending a receipt by email
- Register settings
- Error handling
- Card payments
- Printing and displaying receipts
- Reports, receipt history, FinanzOnline status
- Invoices (invoice API)
- RKSV details
- Glossary
Quick start
dependencies:
kasseneck_api: ^10.1.2
flutter pub get
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';
import 'package:kasseneck_api/models/keck_payment.dart';
final kasseneck = KasseneckApi(
apiKey: 'YOUR_API_KEY', // kr_live_… or kr_test_…
cashregisterToken: 'YOUR_CASHBOX_TOKEN', // cb_live_… or cb_test_…
);
// A cash sale with two items. Prices are integer cents (320 = EUR 3.20).
final items = [
KasseneckItem(name: 'Coffee', quantity: 2, vat: VatRate.vat20, priceCents: 320),
KasseneckItem(name: 'Bread', quantity: 1, vat: VatRate.vat4_9, priceCents: 240),
// If you only have euro doubles: KasseneckItem.euro(..., singlePrice: 3.20)
];
// payments is mandatory and must add up to the amount due (here 880).
final receipt = await kasseneck.sellReceipt(
items: items,
payments: [KeckPaymentInput(method: KeckPaymentMethod.cash, amountCents: 880, tenderedCents: 1000)],
customerDetails: ['Max Mustermann'],
);
print('Receipt ${receipt?.receiptId}, signed: ${receipt?.signatureSuccess}');
sellReceipt returns a signed receipt (Beleg) that is already chained into the
DEP. Models and enums live in their own files; import the ones you need
(models/…, enums/…). A runnable example is in
example/example.dart.
You need a Kasseneck API key and a cashbox token. Ask for them via kasseneck.at/kontakt.
Upgrading from 9.x
10.0 is a breaking release. The CHANGELOG lists every change
under "Migrating from 9.x"; doc/migration-10.md has
the complete table of renamed names (old name, new name, file). The short
version:
-
Wire: only
/v3. Receipts, reports and card calls go tohttps://api.kasseneck.at/v3(kPublicBaseUrl), the register path tohttps://kasse.kasseneck.at/api/v3(kPosBaseUrl). A custombaseUrlmust end in/v3, otherwise the client throws when it is created. -
Names: every public German name is English now (
storniereniscancelReceipt,KasseSettingsisPosSettings,lib/kasse.dartislib/pos.dart,lib/rechnung.dartislib/invoice.dart, ...). The compiler finds each of them; the table maps them. -
Payments:
sellReceipt(payments:)andRegisterReceiptClient.sell(payments:)are mandatory;paymentMethod,creditCardProvider,cardPaymentIdandcardPaymentDataare gone. See Selling. -
Errors: everything the backend or the transport reports arrives as a typed
KasseneckApiErrororKasseneckHttpErrorwith anoutcome, never as a plainException,TimeoutExceptionorClientException. See Error handling. -
Stored receipts: a
KasseneckReceipt.toJson()you stored with 9.x uses German keys;KasseneckReceipt.fromJsonreads only the/v3form. Run such a map throughmigrateStoredReceiptJsonfirst:import 'package:kasseneck_api/models/kasseneck_receipt.dart'; final receipt = KasseneckReceipt.fromJson(migrateStoredReceiptJson(storedMap));Register settings and articles cached by 9.x are read as they are (
PosSettings.fromJson,PosArticle.fromJson); they are written back in the/v3form.
The 8.x and 9.x lines are frozen. They keep talking to the old routes and
get fixes only, from the branches
release/8.x
and release/9.x.
Pin ^9.1.0 (or ^8.0.0) if you are not ready to move; nothing forces an
upgrade while the old routes are served.
Libraries
| Import | What it holds |
|---|---|
package:kasseneck_api/kasseneck_api.dart |
KasseneckApi (API key path), error types, receiptDueCents, receiptLayoutFromResult, migrateStoredReceiptJson, code catalogues, hobex Cloud, receipt widgets |
package:kasseneck_api/register.dart |
RegisterClient (pairing, sign-in, sessions), RegisterTransport, registerErrorCodes, error types |
package:kasseneck_api/pos.dart |
the register (RegisterReceiptClient, PosSettingsClient, PosPrinterClient, articles, cart, tiles, themes, posErrorCodes) |
package:kasseneck_api/invoice.dart |
InvoiceApi, invoice models and computeInvoiceTotals, error types |
package:kasseneck_api/printing.dart |
KeckPrinter, KeckPrinterService, ESC/POS builder |
package:kasseneck_api/hobex_hps.dart |
HpsClient, HpsPayments, terminal discovery |
package:kasseneck_api/models/…, enums/…, services/…, widgets/… |
single models, enums and services, one file each |
What a fiscal cash register in Austria has to do
The obligation to use a fiscal cash register and the obligation to issue receipts (Belegerteilungspflicht) are laid down in § 131b of the Austrian Federal Fiscal Code (BAO). The technical requirements for the security device are in the cash register security regulation (Registrierkassensicherheitsverordnung, RKSV). The table shows which of the resulting tasks this software handles and which stay with the business. The linked pages are in German.
| Task | Where it is handled |
|---|---|
| Signature creation unit (Signaturerstellungseinheit): every receipt is signed | Kasseneck, nothing to install |
| Chaining and data capture log (DEP): every receipt carries its predecessor, the log can be exported | Kasseneck backend |
| Start receipt, monthly receipt, annual receipt (Startbeleg, Monatsbeleg, Jahresbeleg) | Kasseneck, automatically |
| Reports to FinanzOnline: registration, failure, decommissioning (Außerbetriebnahme) | Kasseneck backend |
| Obligation to issue receipts (Belegerteilungspflicht): every customer gets a receipt | this package: printed receipt, screen, or a link by email |
| Signature unit failure (Ausfall der Signatureinheit): collective receipt, report, re-signing | Kasseneck, automatically |
| Cash register audit (Kassennachschau): the auditor asks for the DEP | Kasseneck, DEP export |
| Registering the register, retention, tax assessment | the business owner |
In short: you build the register's user interface, not the security device. One
sellReceipt(...) call produces a signed, chained receipt stored in the DEP. The
backend tests its signature chain against the official verification tool of the
Austrian Federal Ministry of Finance (BMF).
Not legal or tax advice. This section describes what the software does. It does not replace professional advice and does not promise that a particular business meets all of its obligations by using it. The binding sources are the BAO, the RKSV and the rulings of the BMF. Registering the cash register, operating it and retaining records remain the duty of the business owner. More detail with sources (in German): kasseneck.at/wissen. As of September 2026.
Prefer a ready-made register?
This package is for people building their own app. If you just want to take payments, you do not need to write any code:
- Kasseneck, the ready-made fiscal cash register for phone, tablet and browser, including the signature creation unit and the FinanzOnline registration.
- Solutions by industry, from restaurants to taxis.
- Pricing · API docs
- Contact, also for switching registers, partnerships and custom integrations.
Features
- RKSV receipts: sale (standard), cancellation and zero receipt (Nullbeleg), each signed (ES256, JWS) and delivered with its QR code payload.
- All Austrian VAT (USt) rates as
VatRate: 0, 4.9 (basic food, since 1 July 2026), 10, 13, 19 and 20 %. - Integer cents for receipt amounts, so there is no rounding drift.
- Cancellations in full or in part, with reason, remaining quantities and stable error codes.
- Receipt by email as a link to the public receipt page.
- Card payments: hobex terminal (HPS, local REST API) and hobex Cloud with a three-valued outcome, Stripe payment links, and card data from any other terminal stored and printed on the receipt.
- Vouchers: value and promo vouchers, sold and redeemed.
- Tips: per register user, cash or card. Staff tips are booked as a 0 % pass-through item, owner tips as revenue spread over the receipt's VAT rates.
- Printing: ESC/POS over Wi-Fi (raw TCP) and Bluetooth Low Energy, plus the built-in printer of myPOS devices. Raw ESC/POS builder for your own layouts.
- Receipt widgets that render the same receipt as the printer.
- Reports: daily and monthly report PDFs, receipt history.
- Register login flow: device pairing, PIN login and sessions for register
users (
register.dart,pos.dart). - Invoice API: invoices under § 11 UStG (not receipts), customers, cancellation and credit notes, PDF and e-invoice XML (UBL or CII).
Requirements and platforms
- Dart SDK
^3.12.1, Flutter>=3.44.0(seepubspec.yaml). - A Kasseneck API key and cashbox token for the receipt API. A test environment
with its own
kr_test_…key exists; ask via kasseneck.at/kontakt. - Platforms: pub.flutter-io.cn lists Android only, because the bundled myPOS plugin is
Android-only. The package is also used in iOS apps; there the myPOS calls do
not work. Web is not supported (printing and terminal discovery use
dart:iosockets). - Bluetooth printing uses
flutter_blue_plus, i.e. Bluetooth Low Energy. Printers that only speak classic Bluetooth (SPP) are not reachable. - The hobex HPS client talks HTTP to the terminal in the local network (or to
127.0.0.1:8080when the app runs on the terminal itself).
Three ways to authenticate
| Client | Import | Credentials | Use it for |
|---|---|---|---|
KasseneckApi |
kasseneck_api.dart |
API key as bearer + cashregister-token header, base URL https://api.kasseneck.at/v3 |
POS devices and apps: selling, cancelling, reports, card payments |
RegisterClient, RegisterReceiptClient |
register.dart, pos.dart |
pairing code, then device secret + PIN, then a Firebase ID token and a register session, base URL https://kasse.kasseneck.at/api/v3 |
Register apps where staff log in personally (permissions per user) |
InvoiceApi |
invoice.dart |
API key only, no cashbox token, base URL https://api.kasseneck.at/v3 |
Invoices and customers, typically from a server |
RegisterClient, RegisterTransport, RegisterSessionClient,
InvoiceTransport and InvoiceApi take an optional baseUrl (a local
emulator, for example); it must end in /v3. KasseneckApi always uses
kPublicBaseUrl.
The register login in short: RegisterClient().pairRegisterDevice(code: …)
exchanges a pairing code from the Kasseneck panel for a permanent device
identity; registerUserLogin(…) or registerPinLogin(…) returns a
customToken and a sessionId. Your app signs in to Firebase Auth with the
customToken (this package does not depend on firebase_auth) and builds the
session transport:
import 'package:kasseneck_api/pos.dart';
import 'package:kasseneck_api/register.dart';
final transport = RegisterTransport(
idToken: () async => firebaseUser.getIdToken(), // asked on every call
sessionId: () async => currentSessionId, // renew every 30 s, lives 90 s
cashregisterId: device.cashregisterId,
);
final client = RegisterReceiptClient(transport);
final receipt = await client.sell(
items: [KasseneckItem(name: 'Coffee', quantity: 1, vat: VatRate.vat20, priceCents: 320)],
payments: [KeckPaymentInput(method: KeckPaymentMethod.cash, amountCents: 320, tenderedCents: 500)],
);
RegisterSessionClient.fromTransport(transport).renewRegisterSession() keeps the session
alive. Nothing on this path is retried automatically: a receipt is not safely
repeatable.
The /v3 wire: marker, fail closed, unknown outcome
Marker. Every request to a Kasseneck base carries
Kasseneck-Api-Version: v3 and Kasseneck-Client: kasseneck_api/<version>.
An app that names itself passes clientHeader: 'kasse-app/1.0.3+34' (the
product is one of kasse-app, kasse-web, kasseneck-api, kasseneck_api,
the version letters, digits and .+-);
omitKasseneckHeaders: true leaves both headers out. Bases that are not
Kasseneck hosts (an emulator, a proxy) never get them.
Fail closed. Every response must carry Kasseneck-Api-Version: v3. The
package checks that before it reads the body, and never interprets an answer
that lacks it:
| What came back | Error | Outcome |
|---|---|---|
| No marker | KasseneckApiError dialect_mismatch |
unknown |
| HTTP 200 with an HTML page (hosting fallback, no function saw the call) | KasseneckApiError route_missing |
rejected |
| HTTP 404 with marker and error envelope | KasseneckApiError with the envelope's code |
rejected |
| Other HTTP status, empty body, no JSON, invalid UTF-8 | KasseneckHttpError (server-error, empty-body, not-json) |
see below |
One exception: HTML with the marker on a call that signs or moves money is
KasseneckHttpError not-json with an unknown outcome, because the handler may
have run. A test double or proxy in your own tests has to send the marker as
well.
Unknown outcome. Each KasseneckApiError and KasseneckHttpError has an
outcome: ErrorOutcome.rejected (the call was refused, nothing was
signed, charged or refunded) or ErrorOutcome.unknown (the operation may
have happened). isOutcomeUnknown(error) answers it for any error. Unknown
are:
- the codes
dialect_mismatch,receipt_outcome_unknown,cancellation_outcome_unknown,response_unreadable(the call reported success, but the answer lacks the receipt, the reference or the payment;details['receiptId']carries the id when it was readable) andresponse_translation_failed(unlessdetails['handled'] == false); - on the calls that sign or move money (
createReceipt, i.e.sellReceipt,zeroReceiptandRegisterReceiptClient.sell;cancelReceipt;financeWebService; the card callshobexPay,hobexRefundandstripeCaptureIntent): a network error, a timeout or HTTP 5xx after the request was sent, and an unreadable success body; - on the money calls
hobexPay,hobexRefundandstripeCaptureIntent: every error envelope, including one without a code, unless its code is one of the explicit rejections below. The backend answers from its catch-all without a code even when hobex or Stripe already accepted the charge or refund.
On the money calls only these codes mean rejected, because each is raised
before the backend contacts the provider: the sign-in and request checks
(method_not_allowed, validation, cashregister_token_missing,
cashregister_token_invalid, cashregister_not_found, account_not_found,
live_not_enabled, unauthorized, mfa_required,
user_verification_failed, admin_required, register_user_not_allowed,
register_user_no_business, register_user_not_found, user_disabled,
session_expired, cashregister_not_assigned,
session_other_cashregister), the /v3 edge before the handler
(not_found, internal_translation_error), the module and permission gates
(module_inactive, not_permitted) and the package's own route_missing.
Never retry a call whose outcome is unknown. Read the result back
(getReceipt, getReceipts, the receipt list, hobexGetStatus) and act on
what you find. A retried sale is a second signed receipt in the RKSV chain; a
retried card call can charge or refund twice. For the same reason, do not pass
a RetryClient (or any http.Client that resends by itself) as httpClient
to KasseneckApi, RegisterClient, RegisterTransport or InvoiceTransport,
and do not put a retrying proxy in between. A timeout aborts the request; it
does not mean the call failed.
Selling: items, amounts, vouchers, tips
Amounts are integer cents. KasseneckItem.priceCents is the gross unit
price in cents, quantity a whole number. KasseneckItem.euro(singlePrice: …)
converts a euro double once. Euro doubles appear only where a terminal API
requires them (hobex HPS, HobexCloudPayments.pay, SumUp); the raw cloud calls
hobexPay and hobexRefund take amountCents and tipCents like the npm
package.
payments is mandatory. /v3 knows no single payment method per receipt:
each sale sends a list of KeckPaymentInput (cash, card, voucher, ...; a table
that pays with two cards and cash is one receipt). The payments must add up to
the amount due, which the server computes from its VAT buckets, after promo
discounts and including owner tips. receiptDueCents computes it exactly as
the backend does, rounding included, so the register never has to guess:
import 'package:kasseneck_api/kasseneck_api.dart';
import 'package:kasseneck_api/enums/receipt_type.dart';
import 'package:kasseneck_api/enums/credit_card_provider.dart';
import 'package:kasseneck_api/enums/keck_payment_method.dart';
import 'package:kasseneck_api/enums/vat_rate.dart';
import 'package:kasseneck_api/models/kasseneck_item.dart';
final items = [KasseneckItem(name: 'Pizza', quantity: 2, vat: VatRate.vat10, priceCents: 1190)];
final due = receiptDueCents(items, const [], ReceiptType.standard); // 2380
await kasseneck.sellReceipt(items: items, payments: [
KeckPaymentInput(method: KeckPaymentMethod.creditCard, amountCents: 2000, provider: CreditCardProvider.custom, providerPaymentId: 'term-4711'),
KeckPaymentInput(method: KeckPaymentMethod.cash, amountCents: due - 2000, tenderedCents: 500),
]);
With a tip, pass it along (tip: ReceiptDueTip(200) plus tipRecipient: ReceiptDueTipRecipient.owner or .staff): an owner's tip is turnover and
part of the amount due, a staff tip is booked on its own. If the sums still do
not match, the server answers payments_sum_mismatch, and
paymentsExpectedCents(error) reads the amount it expected. An empty list is
allowed only when the amount due is 0.
When the amount cannot be computed from the input (a tip without goods, a tip
on a zero receipt, an item without a VAT rate, ...), receiptDueCents throws a
ReceiptDueError before anything is sent: code is always
receipt_due_unavailable, reason names the cause (tip_without_goods,
invalid_item, ... see receiptDueErrorReasons), outcome is always
rejected. Decide on reason, never on the message.
sellReceipt takes, besides items and payments:
vouchers:KeckVoucher(action: VoucherAction.sell or .redeem, type: VoucherType.value or .promo, valueCents: …). Promo vouchers can only be redeemed, only one per receipt and not together with other vouchers (checkVoucherCombinationErrorreturns the reason).tip:KeckTip.forRecipient(registerUserId, cents: 200)orKeckTip(cents: …, recipients: …). The backend books it as its own line;listTipRecipients()returns the people a tip can be assigned to. A receipt needs at least one item for a tip.customerDetails,legalMessage: extra lines on the receipt.- card payments carry
provider,providerPaymentIdandproviderDataon theirKeckPaymentInput: see Card payments.
zeroReceipt() issues a zero receipt (Nullbeleg). Start, monthly and annual
receipts are created by the backend.
Cancellations (Storno)
A cancellation (Storno) is a new signed receipt that reverses an existing
one, in full or in part. The backend negates the lines, checks remaining
quantities and permissions, links both receipts (cancellationOf on the
cancellation, cancellations[] on the original) and prints the reference line on
the cancellation receipt. The original stays byte-identical.
Two entry points, same endpoint (cancelReceipt):
// API key (package:kasseneck_api/kasseneck_api.dart)
final result = await kasseneck.cancelReceipt(
cashregisterId: original.cashregisterId,
originalReceiptId: original.receiptId,
reason: 'input_error', // key from cancellationReasons
items: [(index: 0, quantity: 1)], // omit = cancel everything that is left
note: 'Customer wanted one', // internal note, max 200 chars, never printed
);
result.receipt; // the signed cancellation receipt
result.remaining; // remaining quantity per line of the original
// Register session (package:kasseneck_api/pos.dart)
final result2 = await client.cancelReceipt(originalReceiptId: id, reason: 'input_error');
- Reasons (
cancellationReasons):input_error(wrong entry),customer_cancelled(customer cancelled),wrong_payment_method(wrong payment method),duplicate(entered twice),other(other). The German label is printed on the receipt. - An empty
itemslist is an error, so that a broken partial cancellation never silently becomes a full one. paymentsdescribes the refund; a card refund at the terminal carriesprovider,providerPaymentIdandproviderData(only with card as the refund method). A card refund through a provider without its ownproviderPaymentIdneeds the original's payment id: pass the original receipt asoriginal:(it must be the receipt named byoriginalReceiptId), otherwise the call throws before sending.- Pass
original:whenever you have it: the cancellation receipt then also carries the TESTKASSE and TESTSIGNATUR marks of the original for the local print fallback. remainingQuantities(receipt)(frompos.dart) computes remaining quantities locally from the original'scancellations; the server has the final word.
Vouchers. A value voucher is only mirrored on a full cancellation (without
items); it cannot be split. A promo voucher is already part of the
original's turnover, so every cancellation takes it back in proportion to the
cancelled quantity: 3 × EUR 10 with a EUR 6 discount is EUR 8 per piece, so the
cancellation shows "−10,00" plus a line "Gutschein-Ausgleich +2,00" (voucher
adjustment). What a cancellation granted is stored on its entry in
receipt.cancellations as promoAdjustmentCents (cents per VAT bucket).
The old cancellation through createReceipt without a reference to the
original (KasseneckApi.cancelReceipt(receipt:), createCancelReceipt) is gone
in 10.0; /v3 rejects it.
Sending a receipt by email
The guest gives an address at the counter and receives a link to the public receipt page, no PDF attachment. That page offers a PDF. The receipt document itself stays byte-identical (it is part of the DEP); the backend logs the delivery separately.
// Register session (package:kasseneck_api/pos.dart)
final sent = await client.sendReceiptEmail(fullReceiptId: receipt.fullReceiptId, to: 'guest@example.com');
// API key (package:kasseneck_api/kasseneck_api.dart)
final sent2 = await kasseneck.sendReceiptEmail(fullReceiptId: receipt.fullReceiptId, to: 'guest@example.com');
sent.to; // normalised address as logged by the backend
sent.at; // ISO timestamp in Vienna time
sent.via; // 'own' (business mailbox), 'platform' or 'platform_fallback'
With the API key, the cashbox token decides which register is meant. Error codes
(receiptEmailErrorCodes): invalid_address (let the user correct it),
too_many_requests (the backend allows five mails per receipt in 24 hours and 30 per
register per hour; try later), send_failed (nothing was sent, a new
attempt is fine), receipt_not_found (unknown receipt or one of another
register; the backend answers both the same way). Only the backend validates the
address.
Register settings
The register settings are two blocks, business (for every register of the
business) and device (this device), with English keys and values
(theme: 'night', checkoutMode: 'panel', printerType: 'network', ...).
PosSettingsClient loads them merged with the defaults and writes patches:
import 'package:kasseneck_api/pos.dart';
final settings = PosSettingsClient(transport, deviceId: device.deviceId);
final before = await settings.load();
// Values this package version does not know (a newer server) keep their raw
// value and are listed here. Show them as "set on the server", never overwrite.
final foreign = unknownPosSettingValues(before); // e.g. ['business.theme']
final after = before.business.merge({'theme': 'night', 'payCard': false});
final patch = posSettingsChanges(before.business.toJson(), after.toJson());
if (patch.isNotEmpty) await settings.saveBusiness(patch);
- Send only what changed. The server merges deeply; a whole block would
overwrite values a newer server knows.
posSettingsChanges(before, after)produces the patch.vatRatesalways goes as the whole map (at least one rate switched on), andshortcutsas the whole map of all known actions, so that the server sees every key binding when it checks for keys bound twice.saveDevicerejects a partial shortcut map. - Strict before sending. An unknown key, a German key from 9.x
(
stil), a value outside a field's list, avatRatesmap without any rate, an unknown shortcut action or a key bound twice throws aKasseneckValidationError(kind: 'request') and nothing goes out.mergethrows anArgumentErrorfor the same mistakes. - Defaults of 10.0:
checkoutModeispanel(9.x: page),terminalPort8080, and the 16 shortcut actions follow the contract (customAmountMod+D,receiptsMod+J,fullscreenMod+F). setLogo(dataUrl)andremoveLogo()change the register logo.
Error handling
Decide on the error code, never on the message text. The messages are German and may change.
try {
await kasseneck.cancelReceipt(cashregisterId: crId, originalReceiptId: id, reason: 'input_error');
} on KasseneckApiError catch (e) {
if (isOutcomeUnknown(e)) {
// cancellation_outcome_unknown, response_unreadable, ...: the cancellation
// may exist. Reload the original and look at its cancellations; never resend.
return;
}
switch (e.code) {
case 'already_cancelled': // show as cancelled, disable the button
case 'quantity_exceeds_remaining': // reload remaining quantities (someone was faster)
case 'own_receipts_only': // ask the manager
break;
default:
rethrow;
}
} on KasseneckHttpError catch (e) {
if (e.outcome == ErrorOutcome.unknown) {
// timeout or 5xx after sending: same as above, read back, never resend
}
rethrow;
}
The error types are exported from kasseneck_api.dart, register.dart and
invoice.dart. pos.dart exports only KasseneckReceiptFormatError,
ErrorOutcome and isOutcomeUnknown; to catch the others on the register
path, import register.dart as well.
| Type | Meaning |
|---|---|
KasseneckApiError |
The backend refused, or the package detected an edge problem (code, message, details, outcome). |
KasseneckHttpError |
Transport problem (reason: KasseneckHttpError.reasonTimeout, reasonNetwork, 'server-error', 'empty-body', 'not-json'; statusCode, timeout, outcome). |
KasseneckValidationError |
A request was rejected before sending (kind: 'request'), or a response of a reading call lacks a required field ('response'). |
KasseneckReceiptFormatError |
A receipt fetched with getReceipt or RegisterReceiptClient.get could not be parsed (receiptId when readable). On the signing calls the same problem is response_unreadable with an unknown outcome. |
Invalid input to sellReceipt and zeroReceipt (no items, invalid vouchers,
payments missing or malformed) throws a KasseneckValidationError
(kind: 'request') before anything is sent, the same type as
RegisterReceiptClient.sell and the npm package.
What the cashier sees. pos.dart carries the register's sentences, the
same ones the web register shows (posMessages, posLabels, messageText,
labelText, generated from the contract file pos-texts.json), and the rules
that pick one for an error. Classify the error yourself (ErrorKind.api for a
KasseneckApiError, ErrorKind.timeout/ErrorKind.network for the
KasseneckHttpError reasons of the same name, ...), then:
import 'package:kasseneck_api/pos.dart';
import 'package:kasseneck_api/register.dart';
String sentence(Object e, String fallback) {
final kind = switch (e) {
KasseneckApiError() => ErrorKind.api,
KasseneckHttpError(reason: KasseneckHttpError.reasonTimeout) => ErrorKind.timeout,
KasseneckHttpError(reason: KasseneckHttpError.reasonNetwork) => ErrorKind.network,
KasseneckHttpError() => ErrorKind.unexpected,
_ => ErrorKind.other,
};
final rule = findErrorRule(kind, code: e is KasseneckApiError ? e.code : null, outcome: messageOutcome(e));
if (rule.key case final key?) {
return messageText(key, key == 'server.unexpected' ? {'status': e is KasseneckHttpError ? e.statusCode : 0} : const {});
}
return switch (rule.behavior!) {
ErrorRuleBehavior.serverText => (e as KasseneckApiError).message,
ErrorRuleBehavior.ownText || ErrorRuleBehavior.fallback => fallback,
};
}
findErrorRule applies a code rule (errorCodeRules: edge codes get a human
sentence instead of the technical one), then an outcome rule
(errorOutcomeRules), then the one rule of the kind (errorRules, unchanged
since 10.0.0-rc.1). messageOutcome treats a timeout or network error on a call
from callsWithEffect (receipt, cancellation, FinanzOnline, card payments,
print job, receipt email) as outcome unknown: the sentence then says to check
whether the last operation went through, never to try again.
Code catalogues per endpoint group, each one the server's own codes, then
the sign-in and edge codes that can reach it, then the codes the package sets
itself (clientErrorCodes: route_missing, response_unreadable):
receiptErrorCodes, cancellationErrorCodes, paymentErrorCodes (the
payments[] codes of a sale or cancellation, such as payments_sum_mismatch),
receiptEmailErrorCodes, registerErrorCodes (register.dart),
posErrorCodes (pos.dart) and invoiceErrorCodes / invoiceRequestErrorCodes
(invoice.dart). Helpers such as isRegisterError(e, 'cashregister_in_use'),
registerErrorDetails(e) (retryAfterSec, deviceLabel, ...),
isPosError, posFieldErrors and invoiceFieldErrors read the details.
Card payments
A card payment has three possible outcomes, not two: approved, definitely
declined, or unknown (timeout, lost connection, the terminal never
answered). Treating "unknown" as "declined" and retrying is how a customer gets
charged twice. HpsPayments and HobexCloudPayments fix the transaction ID
before the first network call and, if the answer is lost, resolve that same
ID instead of starting a new charge. A lost answer ends in
CardPaymentOutcome.unresolved (keep the ID, resolve later) rather than a guess.
| Provider | What the package does |
|---|---|
| hobex HPS (terminal in the local network) | HpsPayments: pay, refund, cancel with a resolved three-valued outcome; discoverHpsTerminals finds terminals in the LAN |
| hobex Cloud (via the Kasseneck backend) | HobexCloudPayments.pay with the same outcome; refunds via kasseneck.hobexRefund(...) |
| Stripe | payment links for remote and online payments: createStripeLink, stripeCaptureIntent |
| SumUp | thin wrapper around the sumup plugin: SumupService in services/sumup_service.dart |
| any other terminal (for example GP Tom or myPOS) | pass your terminal's result on the card payment as KeckPaymentInput(provider: …, providerPaymentId: …, providerData: …); it is stored and printed on the receipt |
HpsPayments and HobexCloudPayments.pay take euros (amount: 12.50), as
the hobex API expects; the raw calls kasseneck.hobexPay(...) and
hobexRefund(...) take integer cents (amountCents: 1250), like the npm
package.
Example: hobex terminal (HPS) to signed receipt
import 'package:kasseneck_api/hobex_hps.dart'; // HpsClient, HpsPayments, CardPaymentOutcome, HobexReceipt
// Default base URL is http://127.0.0.1:8080 (app runs on the terminal).
// Terminal in the LAN: HpsClient(baseUrl: Uri.parse('http://192.168.1.50:8080'), tid: …)
final hps = HpsPayments(HpsClient(tid: '3600335')); // TID without leading zero
// The ID is fixed BEFORE the request goes out. Persist it right away, so that a
// lost answer can be resolved later instead of being retried blindly.
final transactionId = HpsClient.newTransactionId();
final result = await hps.pay(amount: 12.50, transactionId: transactionId);
switch (result.outcome) {
case CardPaymentOutcome.approved:
break; // continue below
case CardPaymentOutcome.declined:
return; // definitely no money moved; a new attempt is safe
case CardPaymentOutcome.unresolved:
// Not resolved within the resolve budget (90 s, configurable).
//
// Do NOT retry here. Measured on a real terminal (2026-08-26): sending the
// same transactionId again starts a SECOND card transaction; the terminal
// does not recognise it as the same one. A retry is a real second charge.
//
// Keep transactionId and resolve the outcome first
// (HpsClient.transactionStatus(...) once the terminal answers again), then
// act on a known outcome. doc/kartenzahlung.md (German) explains the
// individual response codes.
return;
}
// Take over the terminal result, then create the signed receipt.
final card = HobexReceipt.fromHps(result.response!);
await kasseneck.sellReceipt(
payments: [
KeckPaymentInput(
method: KeckPaymentMethod.creditCard,
amountCents: 1250,
provider: card.creditCardProvider, // hobexHps
providerPaymentId: card.transactionId,
providerData: card.toCardPaymentData(),
),
],
items: [KasseneckItem(name: 'Lunch', quantity: 1, vat: VatRate.vat10, priceCents: 1250)],
);
hps.refund(...) and hps.cancel(...) return the same resolved outcome. An
HpsObserver passed to HpsPayments (observer:) logs requests, failures and
how an outcome was resolved.
To find a terminal in the local network:
final scan = await discoverHpsTerminals(stopAtFirst: true);
final found = scan.first;
if (found != null) {
final client = HpsClient(baseUrl: Uri.parse('http://${found.host}:${found.port}'), tid: found.tids.first);
}
Example: hobex Cloud to signed receipt
import 'package:kasseneck_api/kasseneck_api.dart'; // HobexCloudPayments, CardPaymentOutcome
final cloud = HobexCloudPayments(kasseneck);
// Same rule as with HPS: the caller fixes the ID before the request goes out.
final transactionId = KasseneckApi.newHobexTransactionId();
final result = await cloud.pay(transactionId: transactionId, amount: 12.50);
switch (result.outcome) {
case CardPaymentOutcome.approved:
break; // continue below
case CardPaymentOutcome.declined:
return; // definitely no money moved; a new attempt is safe
case CardPaymentOutcome.unresolved:
// Not resolved within the budget. Do NOT retry blindly: keep transactionId
// and resolve later, see the HPS example above.
return;
}
final card = result.receipt!;
await kasseneck.sellReceipt(
payments: [
KeckPaymentInput(
method: KeckPaymentMethod.creditCard,
amountCents: 1250,
provider: card.creditCardProvider,
providerPaymentId: card.transactionId,
providerData: card.toCardPaymentData(),
),
],
items: [KasseneckItem(name: 'Lunch', quantity: 1, vat: VatRate.vat10, priceCents: 1250)],
);
HobexCloudPayments has no refund() or cancel(). A cloud refund still goes
through the raw call kasseneck.hobexRefund(...): it returns true or throws
a KasseneckApiError, never false, and it does not resolve its outcome. Only
the rejection codes listed under "Unknown outcome" mean nothing was refunded;
any other failure, including an error without a code, is an unknown outcome
(isOutcomeUnknown): check with hobexGetStatus before refunding again.
Raw access: HpsClient and kasseneck.hobexPay(...)
The local HpsClient and the cloud calls kasseneck.hobexPay(...),
hobexRefund(...) and hobexGetStatus(...) remain available for full control.
Neither resolves the outcome: a raw call that never gets an answer stays
unresolved forever. Building a payment flow on it means solving again the
problem that HpsPayments and HobexCloudPayments already solve, with the real
risk of answering "was the card charged?" wrongly under exactly the conditions
(timeout, dropped connection) where a wrong answer is expensive. Use the raw
client only for what the wrapper does not expose, for example
hps.diagnosis() or hps.transactionStatus(...).
Example: Stripe payment link
import 'package:kasseneck_api/enums/stripe_link_mode.dart';
final session = await kasseneck.createStripeLink(
items: [KasseneckItem(name: 'Gift card', quantity: 1, vat: VatRate.vat20, priceCents: 5000)],
createReceiptAfterPayment: true,
mode: StripeLinkMode.payment, // or .authorization, captured later with stripeCaptureIntent
customerEmail: 'guest@example.com',
);
print(session?.url);
Printing and displaying receipts
import 'package:kasseneck_api/printing.dart'; // KeckPrinter, KeckPaperSize, QrPrintMode, QrModuleSize, …
// Recommended: one printer object per device. Returns a KeckPrintResult instead
// of throwing, and reports a missing QR code (qrError).
final printer = KeckPrinter.wifi(ip: '192.168.0.50', size: KeckPaperSize.mm80);
// or: KeckPrinter.bluetooth(address: 'AA:BB:CC:DD:EE:FF', size: KeckPaperSize.mm58)
final printed = await printer.printReceipt(receipt);
if (printed.qrError != null) {
// The receipt went out without its QR code: tell the customer.
}
await printer.openDrawer();
await printer.dispose();
QR code garbled or missing? Printers understand different commands:
await printer.printReceipt(receipt, qrMode: QrPrintMode.imageBitImage); // or .native
// Some cheap printers only know the older QR model 1:
await printer.printReceipt(receipt, qrMode: QrPrintMode.nativeModel1);
// The native command sizes its modules to fit the paper (quiet zone included).
// qrModuleSize is a cap, never an enlargement beyond what fits:
await printer.printReceipt(receipt, qrModuleSize: QrModuleSize.large);
The older static path still works: kasseneck.initWifiPrinter(ip, size) or
kasseneck.initBluetoothPrinter(printerAddress: …), then
receipt.printReceiptWifi() or receipt.printReceiptBluetooth(qrMode: …, qrModuleSize: …).
Note that KasseneckApi.openCashDrawer() and printReceiptWifi() only send to
the configured Wi-Fi printer and silently do nothing if none is set. On myPOS
devices, receipt.printReceiptMyPos() uses the built-in printer. For your own
layouts, printing.dart exports the ESC/POS builder (EscPosGenerator,
PosStyles, CustomPrintJob).
The same receipt on screen and paper
import 'package:kasseneck_api/services/logo_service.dart';
// Once at app start: store logos on disk, so the first receipt after a restart
// does not wait for the network.
await LogoService.enablePersistentStorage();
// Screen (widget from kasseneck_api.dart). The server's layout wins whenever
// the response carries one; otherwise draw the local fallback (the printer
// service does that for you) in fallbackPaperSize (default mm58).
final shown = receiptLayoutFromResult(receipt, fallbackPaperSize: KeckPaperSize.mm80);
if (shown.layout != null) {
KeckReceiptSheetWidget(
layout: shown.layout!,
logoUrl: receipt.logoUrl,
logoSize: receipt.logoScale,
brandMark: receipt.showKreiseckLogo,
);
}
// Paper, with the same logo (printing.dart)
final logo = await loadPrintLogo(receipt.logoUrl, receipt.logoScale, KeckPaperSize.mm80);
final paper = await KeckPrinterService.getPaperFromReceipt(receipt, KeckPaperSize.mm80,
logo: logo, brandMark: receipt.showKreiseckLogo);
Server layout first. receiptLayoutFromResult(receipt, fallbackPaperSize:)
returns the server's line model (80 mm) whenever the response carries one, and
layout == null with the fallback width otherwise. On the public channel only
the server's layout carries the card block, so screen, paper and PDF show the
same receipt. KeckPrinterService.getPaperFromReceipt follows the same rule:
a server layout is always printed; the local fallback prints the TESTKASSE and
TESTSIGNATUR frames and the receipt type block (STORNOBELEG, TRAININGSBELEG,
NULLBELEG, ...) itself.
Reports, receipt history, FinanzOnline status
import 'package:kasseneck_api/models/report_month.dart';
final monthly = await kasseneck.downloadMonthlyReport(ReportMonth.now()); // PDF bytes, Vienna month
final daily = await kasseneck.downloadDailyReport(DateTime.now()); // PDF bytes
final history = await kasseneck.getReceipts(start, end); // List<KasseneckReceipt>
final one = await kasseneck.getReceipt(receiptId);
final status = await kasseneck.getCashboxStatus(); // register status at FinanzOnline
getReceipts skips single receipts it cannot read instead of failing the whole
range. getSignatureStatus(certificateHex) asks FinanzOnline for the status of a
signature certificate.
Invoices (invoice API)
Invoices (Rechnungen) are not receipts: no cashbox token, no signature, but
a sequential invoice number under § 11 UStG. The key is the account's
api_key, and it belongs on a server. For live use, Kasseneck has to enable
the invoice API for the account, so check the setup first.
import 'package:kasseneck_api/invoice.dart';
final invoices = InvoiceApi(apiKey: 'kr_live_…');
final setup = await invoices.getInvoiceSetupStatus();
if (!setup.ready) {
for (final gap in setup.missing) {
print('${gap.requirement}: ${gap.message}');
}
return;
}
final customer = await invoices.createCustomer(
const CustomerInput(type: 'company', name: 'Café Muster GmbH', country: 'AT', externalId: 'shop-4711'),
);
try {
final issued = await invoices.issueInvoice(IssueInvoiceRequest(
idempotencyKey: 'order-4711', // the same key never creates a second invoice
customerId: customer.id,
priceMode: 'net',
serviceStart: '2026-09-15',
items: const [InvoiceItemInput(description: 'Consulting', quantity: 2, unitPriceCents: 5000, vatRate: 20)],
));
final pdf = await invoices.getInvoicePdf(issued.invoice.id); // Uint8List
final xml = await invoices.getInvoiceXml(issued.invoice.id); // UBL; format: 'cii' for CII
// xml.xml, xml.format ('ubl'), xml.filename ('invoice-<number>.xml')
} on KasseneckApiError catch (e) {
switch (invoiceErrorCode(e)) {
case 'validation':
for (final f in invoiceFieldErrors(e)) {
print('${f.field}: ${f.message}');
}
case 'invoice_setup_incomplete':
print(e.details['missing']);
default:
rethrow;
}
}
After a timeout (KasseneckHttpError.reasonTimeout), issue again with the same
idempotencyKey: the answer then carries replayed: true and the same
invoice. The same key with different data gives idempotency_conflict.
Cancel with cancelInvoice, partial credit with createCreditNote; all error
codes are listed in invoiceErrorCodes.
Prices. Each item has either unitPriceCents (integer cents) or
unitPriceMicros (millionths of a euro, for prices below one cent), never both.
Micro prices are accepted only once they are enabled for the account; otherwise
the server answers with validation. On a request, vatRate is one of
vatRates (0, 10, 13, 20). unit takes a key from invoiceUnits (piece,
hour, …; default piece), not free text.
Language and brand. An invoice has one number and one language (de or
en): the one in the request, otherwise the customer's, otherwise German.
Public authorities always get German. brandId picks a brand from
listBrands(). The same invoice in the other language exists only as a marked
translation copy, getInvoicePdf(id, language: 'de'), never as a second invoice.
The tax case is derived. taxScheme is optional: the server derives it from
customer country, customer type, VAT ID and the kind (goods or service) of
the items. A value you send is checked; if it does not match, you get
tax_scheme_mismatch with the expected case instead of a wrong invoice. For
domestic reverse charge there is reverseChargeReason from
reverseChargeReasons (construction services, scrap, certain devices and more).
Other cases include oss and outsideScope (service to a business outside the
EU, not taxable in Austria).
Already paid. If you collect online and invoice afterwards, pass the payment
along: IssueInvoiceRequest(payment: PaymentInput(method: 'card')). It is
recorded in the same transaction that finalises the invoice, and the PDF then
has no payment box and no Girocode QR. If the money arrives later, use
recordInvoicePayment(RecordPaymentRequest(...)) with its own
idempotencyKey, otherwise a retry books twice. With method: 'cash' (and with
onSite: true) the payment is booked and the notice list carries
cash_receipt_required: a cash sale needs a receipt from the fiscal cash
register (§ 132a BAO); the note on the invoice does not replace it.
Computing totals in advance. computeInvoiceTotals computes the totals exactly as
the server does, offline. previewInvoice asks the server: a dry run that checks
like issuing (customer, tax case, required fields) but finalises nothing and does
not consume the idempotencyKey.
const items = [
InvoiceItemInput(description: 'Manicure', quantity: 1, unitPriceCents: 1479, vatRate: 20),
InvoiceItemInput(description: 'Polish', quantity: 1, unitPriceCents: 1500, vatRate: 20),
];
final totals = computeInvoiceTotals(items, 'gross');
// totals.grossCents == 2979, netCents == 2483, vatCents == 496
final request = IssueInvoiceRequest(
idempotencyKey: 'order-$orderNumber',
customerId: customer.id,
priceMode: 'gross',
serviceStart: '2026-09-16',
items: items,
);
final preview = await invoices.previewInvoice(request);
// preview.preview.totals, preview.preview.taxScheme, preview.preview.taxSchemeReason
final issued = await invoices.issueInvoice(request); // same key, one invoice
In gross mode the gross amount per rate is the agreed price:
net = round(G × 100 / (100 + rate)), VAT = G − net. In net mode the VAT per
rate is rounded from the net sum. Rounding is commercial (half a cent away from
zero), per rate, then summed. If the server derives a tax-exempt case
(zeroRatedTaxSchemes, e.g. intraCommunitySupply), pass it as the third argument of
computeInvoiceTotals, otherwise the function computes tax the invoice does not show;
previewInvoice names the case. Issuing is binding: customer or account may
change between preview and invoice. Totals are positive for credit notes too;
the sign is in the document type (docType: 'credit_note').
Notices are always a list. notice on issueInvoice, previewInvoice and
recordInvoicePayment is a List<InvoiceNotice>, empty if there is nothing to
say. An intra-EU supply carries recapitulative_statement_due, a cash-paid
invoice cash_receipt_required. Decide on the code, not the text:
if (issued.notice.any((n) => n.code == 'cash_receipt_required')) {
// Cash sale: issue a receipt through the fiscal cash register
}
RKSV details
Every receipt is chained and signed (ES256, JWS) and comes with its
machine-readable QR payload, as the RKSV requires. A failed signature unit is
detected (receipt.signatureSuccess, receipt.isSigFailed) and marked on the
receipt; the backend handles the follow-up (see the obligations table above).
Glossary
German RKSV terms used in the API and on the receipts:
| German | English |
|---|---|
| Beleg | receipt |
| Startbeleg | start receipt |
| Nullbeleg | zero receipt |
| Monatsbeleg | monthly receipt |
| Jahresbeleg | annual receipt |
| Schlussbeleg | final receipt |
| Storno | cancellation |
| Signaturerstellungseinheit | signature creation unit |
| DEP (Datenerfassungsprotokoll) | data capture log (DEP) |
| Kassennachschau | cash register audit |
| Belegerteilungspflicht | obligation to issue receipts |
| Registrierkasse | fiscal cash register |
| Umsatzzähler | turnover counter |
| Außerbetriebnahme | decommissioning |
| Ausfall der Signatureinheit | signature unit failure |
| Rechnung | invoice |
| USt | VAT |
FinanzOnline (the tax authority's online portal) and BMF (Federal Ministry of Finance) are names and stay as they are.
Versioning
The package follows semantic versioning. What changed and when is in the
CHANGELOG; entries from 10.0.0 on are in English, older ones in
German. The frozen lines 8.x and 9.x live on the branches release/8.x and
release/9.x.
License
MIT, see LICENSE.
Kasseneck is a product of Kreiseck Software Solutions from Salzburg, Austria: apps, POS systems and automation. Questions about the API, custom integrations or a partnership: kasseneck.at/kontakt.
Libraries
- enums/cashbox_status
- enums/credit_card_provider
- enums/keck_invoice_payment_method
- 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). - invoice
- Rechnungen (§ 11 UStG, keine Belege) und Kunden über die Rechnungs-API —
Zwilling von
@kreiseck/kasseneck-api/rechnungim JS-Paket. - kasseneck_api
- models/brand_mark
- Die Marke als Rasterbild -- Zwilling von
brandMarkImage/unpackRasterBitsin@kreiseck/kasseneck-apiab 0.26.0. Zur Laufzeit wird nichts gerastert und nichts skaliert: die beiden Masse (352x51 auf 80 mm, 234x34 auf 58 mm) stehen fest, damit JS und Dart fuer denselben Beleg dieselben Bytes erzeugen (siehedocs/specs/2026-09-21-marke-einheitlich-design.md, § 3.3). - models/brand_mark_data
- Die Marke als Rohdaten (Zwilling von
brandMark-daten.tsin@kreiseck/kasseneck-apiab 0.26.0): ein 1-Bit-Raster je Druckbreite -- MSB zuerst, jede Zeile auf volle Bytes aufgefuellt, base64-kodiert. - 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_payment
- models/keck_print_result
- models/keck_sepa_info
- models/keck_tip
- models/keck_tip_person
- models/keck_user
- models/keck_voucher
- models/logo_raster
- RGBA-Pixel -> einfarbiges Rasterbild in der Groesse, die das Blatt dem Logo
gibt (Zwilling von
logoRasterin@kreiseck/kasseneck-apiab 0.14.0). Flaechenmittel je Druckpunkt, Durchsichtiges auf Papierweiss, BT.601, Floyd-Steinberg mit Schwelle 128. Golden:expected/logo-sample.raster32.txt. Die Reihenfolge der Rechenschritte nicht aendern -- nur so kommen JS und Dart auf dieselben Punkte. - models/print_paper
- models/receipt_grid
- 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/receipt_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/receipt_sheet
- Das Beleg-Blatt (Zwilling von
receiptSheetin@kreiseck/kasseneck-apiab 0.14.0): die vollstaendige Folge dessen, was auf dem Papier steht -- Rasterzeilen, Firmenlogo, QR und Marke, Groessen als Anteil der Blattbreite und in Zeilen. Jeder Zeichner (Bon, Bildschirm) setzt nur noch das Blatt. - models/registration_info
- models/report_month
- models/stripe_url_session
- models/sumup_checkout_response
- pos
- Die Kasse am Tresen: Einstellungen, Warenkorb, Kassieren und Belege — gemeinsam von Browser-Kasse und App.
- 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/print_logo
- Firmenlogo fuer den Bondruck: Adresse -> Pixel -> Rasterbild in der Groesse
des Blatts (Zwilling von
loadPrintLogoim Druck-Kit der Browser-Kasse). Ein Logo, das nicht laedt, ist kein Druckfehler: der Bon kommt ohne Logo. - services/printer_service
- services/rksv_service
- services/sumup_service
- services/vienna_time
- widgets/keck_receipt_lines_widget
- widgets/keck_receipt_sheet_widget
- widgets/keck_receipt_widget