@kreiseck/kasseneck-api 1.5.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +64 -26
  3. package/dist/cjs/client/aufrufe.d.ts +56 -2
  4. package/dist/cjs/client/aufrufe.js +168 -1
  5. package/dist/cjs/client/errors.d.ts +32 -19
  6. package/dist/cjs/client/errors.js +28 -17
  7. package/dist/cjs/client/index.d.ts +2 -2
  8. package/dist/cjs/client/index.js +2 -1
  9. package/dist/cjs/client/transport.d.ts +27 -1
  10. package/dist/cjs/client/transport.js +25 -27
  11. package/dist/cjs/index.d.ts +1 -1
  12. package/dist/cjs/index.js +3 -2
  13. package/dist/cjs/inventory/schreiben.d.ts +3 -0
  14. package/dist/cjs/inventory/schreiben.js +6 -1
  15. package/dist/cjs/invoice/endpunkte.d.ts +6 -0
  16. package/dist/cjs/invoice/endpunkte.js +13 -2
  17. package/dist/cjs/pos/texte.d.ts +11 -6
  18. package/dist/cjs/pos/texte.js +11 -6
  19. package/dist/cjs/version.d.ts +1 -1
  20. package/dist/cjs/version.js +1 -1
  21. package/dist/esm/client/aufrufe.d.ts +56 -2
  22. package/dist/esm/client/aufrufe.js +166 -0
  23. package/dist/esm/client/errors.d.ts +32 -19
  24. package/dist/esm/client/errors.js +28 -17
  25. package/dist/esm/client/index.d.ts +2 -2
  26. package/dist/esm/client/index.js +1 -1
  27. package/dist/esm/client/transport.d.ts +27 -1
  28. package/dist/esm/client/transport.js +26 -28
  29. package/dist/esm/index.d.ts +1 -1
  30. package/dist/esm/index.js +1 -1
  31. package/dist/esm/inventory/schreiben.d.ts +3 -0
  32. package/dist/esm/inventory/schreiben.js +6 -1
  33. package/dist/esm/invoice/endpunkte.d.ts +6 -0
  34. package/dist/esm/invoice/endpunkte.js +13 -2
  35. package/dist/esm/pos/texte.d.ts +11 -6
  36. package/dist/esm/pos/texte.js +11 -6
  37. package/dist/esm/version.d.ts +1 -1
  38. package/dist/esm/version.js +1 -1
  39. package/fixtures/hobex-hps-codes.json +1 -1
  40. package/fixtures/invoice-api.schema.json +1 -1
  41. package/fixtures/invoice-texts.json +1 -1
  42. package/fixtures/pos-texts.json +1 -1
  43. package/fixtures/surface.json +55 -1
  44. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,64 @@ Was vor 0.7.0 geschah, steht in der Commit-Historie (`git log`); ab hier wird
4
4
  es hier geführt. Ein Eintrag nennt die Änderung **und ihren Grund** —
5
5
  nur der Grund überlebt den nächsten Umbau.
6
6
 
7
+ ## 1.5.1
8
+
9
+ Every call with an effect reports `outcome: 'unknown'` when it fails after
10
+ sending began: network error, timeout, HTTP 5xx, HTTP 200 with the `/v3`
11
+ marker but an empty, non-JSON or status-less body, and HTML with the marker.
12
+ Reason: until 1.5.0 only the six signing and money calls did; a goods receipt,
13
+ a reservation or `issueInvoice` came back as `'rejected'` ("did not happen")
14
+ although the server may have booked it. Whoever believed that and resent with
15
+ a **new** `idempotencyKey` booked twice.
16
+
17
+ A change to the safe side, no change on the wire; requests send the same body
18
+ as in 1.5.0 (only the `Kasseneck-Client` header carries the new version).
19
+
20
+ - **Which calls**: `UNKNOWN_OUTCOME_CALLS` (exported, sorted), also in
21
+ `fixtures/surface.json` as `unknownOutcomeCalls` for the Dart twin. New in it:
22
+ inventory writes (`createArticle`, `updateArticle`, `deactivateArticle`,
23
+ `receiveGoods`, `transferStock`, `recordStockLoss`, `changeStockCondition`,
24
+ `reverseStockMovement`), reservations (`createReservation`,
25
+ `extendReservation`, `releaseReservation`), inventory and partner webhooks
26
+ (create, update, delete, rotate the secret, test delivery), the invoice API
27
+ (`issueInvoice`, `cancelInvoice`, `createCreditNote`, `recordInvoicePayment`,
28
+ `createCustomer`, `updateCustomer`), invoice items and SEPA mandates
29
+ (`createInvoiceItem`, `updateInvoiceItem`, `withdrawInvoiceItem`,
30
+ `setCustomerMandate`, `revokeCustomerMandate`), the partner calls with an
31
+ effect (`createPartnerCustomer`, `sendPartnerCustomerFonLink`,
32
+ `requestCustomerSignature`, `createCustomerCashregister`,
33
+ `activateCashregister`, `reportCustomerContract`), and on the register
34
+ `pairRegisterDevice`, `unpairRegisterDevice`, `setMyKasseSettings`,
35
+ `setMyKasseLogo`, `setMyRegisterDeviceSettings`,
36
+ `setMyCashregisterStockLocation`, `createPrintJob`, `sendReceiptEmail`.
37
+ - **Which stay `'rejected'`**: reading calls, dry runs (`previewGoodsReceipt`,
38
+ `previewInvoice`, `issueInvoice` with `dryRun: true`), the register sign-in
39
+ sessions (`registerUserLogin`, `registerPinLogin`, `renewRegisterSession`,
40
+ `endRegisterSession`) and `createPaymentLinkStripe`: a repeat books nothing.
41
+ A timeout already during sign-in stays `'rejected'` as before: nothing was
42
+ sent.
43
+ - **What to do**: with an `idempotencyKey`, send the same request with the
44
+ **same** key; it takes effect once and returns the stored answer.
45
+ `createInvoiceItem` is recognised by `source` + `reference`: the same values
46
+ again are safe. Without a key (receipts, cancellations, money calls,
47
+ settings, webhooks, changing or withdrawing invoice items, mandates), read
48
+ the state first. The rule for receipts and payments is unchanged: look it up,
49
+ never resend.
50
+ - **Per call**: the transport takes a fifth argument `{ hasEffect }`
51
+ (`TransportCallOptions`). `true` marks a call with an effect that this
52
+ package does not know. `false` only counts together with
53
+ `params.dryRun === true` (a dry run under the name of the real call; the
54
+ package's own previews use it); without `dryRun: true` a call from the list
55
+ stays `'unknown'`, so no option can turn a real booking into `'rejected'`.
56
+ Without the option the list decides, and an unknown name counts as having no
57
+ effect.
58
+ - **Guard**: every call the package knows (`ALL_CALLS`, `PUBLIC_CALLS`,
59
+ `POS_CALLS`) is classified as with or without effect; a new call without a
60
+ classification fails the tests.
61
+ - `CALLS_WITH_EFFECT` of the register texts and `fixtures/pos-texts.json` are
62
+ unchanged; `messageOutcome` follows the transport first, so the register
63
+ shows the careful sentence for every call above.
64
+
7
65
  ## 1.5.0
8
66
 
9
67
  Inventory API, write side and reservations: `./inventory` gains the 13
package/README.md CHANGED
@@ -433,30 +433,42 @@ receipt email, and values outside a settings field's value list throw a
433
433
  `KasseneckValidationError` before anything is sent, instead of being dropped
434
434
  silently. For a layout option of 0.x the message names its English successor.
435
435
 
436
- ### `outcome: 'unknown'`: never retry, look it up
436
+ ### `outcome: 'unknown'`: never resend blindly
437
437
 
438
438
  `KasseneckApiError`, `KasseneckHttpError` and `KasseneckNetworkError` carry
439
439
  `outcome`. `'rejected'` means the server turned the request down; whether
440
440
  anything else is safe to do depends on the code, and on the money calls it
441
441
  is only set for the codes listed below. `'unknown'` means the operation **may have been carried out**, for
442
- `createReceipt` a signed receipt in the chain. Then never send it again: read
443
- the result back (`getReceipt`, `listMyReceipts`, the original of a
444
- cancellation) and continue from there. `isOutcomeUnknown(error)` covers all
445
- three classes. The outcome is unknown for:
442
+ `createReceipt` a signed receipt in the chain. Then never send it again blindly:
443
+ read the result back (`getReceipt`, `listMyReceipts`, the original of a
444
+ cancellation) and continue from there. A call with an `idempotencyKey` (or,
445
+ for invoice items, the same `source` and `reference`) is the exception: send
446
+ it again unchanged (see below).
447
+ `isOutcomeUnknown(error)` covers all three classes. The outcome is unknown for:
446
448
 
447
449
  - `dialect_mismatch`, `receipt_outcome_unknown`, `cancellation_outcome_unknown`;
448
450
  - `response_translation_failed`, unless `details.handled === false`;
449
451
  - `response_unreadable`: a signing or money-moving call reported success,
450
452
  but the response lacks what the call promises (no receipt, no reference, no
451
453
  remaining quantities, no Hobex receipt, no captured payment intent);
452
- - on the calls with an effect: the signing calls `createReceipt`,
453
- `cancelReceipt` and `financeWebService`, and the money calls `hobexPay`
454
- (`hobexPayApi`, charges a card), `hobexRefund` (`hobexRefundApi`) and
455
- `stripeCaptureIntent`: a network error or timeout after sending began, HTTP
456
- 5xx, and HTTP 200 with the `Kasseneck-Api-Version: v3` marker but an empty,
454
+ - on every call with an effect (`UNKNOWN_OUTCOME_CALLS`, also in
455
+ `fixtures/surface.json` as `unknownOutcomeCalls`): the signing calls
456
+ `createReceipt`, `cancelReceipt` and `financeWebService`, the money calls
457
+ `hobexPay` (`hobexPayApi`, charges a card), `hobexRefund`
458
+ (`hobexRefundApi`) and `stripeCaptureIntent`, and since 1.5.1 every other
459
+ call that books, issues, creates, changes, deletes or sends something:
460
+ inventory writes and reservations, webhooks, invoices, credit notes,
461
+ recorded payments, customers, invoice items and SEPA mandates, partner
462
+ businesses, register settings, pairing, print jobs and receipt emails.
463
+ There it is a network error or timeout after sending began, HTTP 5xx, and
464
+ HTTP 200 with the `Kasseneck-Api-Version: v3` marker but an empty,
457
465
  non-JSON (also `text/html`) or status-less body (`KasseneckHttpError`,
458
466
  `reason` `empty-body`, `not-json` or `missing-status`). HTML without the
459
- marker stays `route_missing` with `'rejected'`: no function saw the call;
467
+ marker stays `route_missing` with `'rejected'`: no function saw the call.
468
+ Reading calls and dry runs (`previewGoodsReceipt`, `previewInvoice`) stay
469
+ `'rejected'`, and so do the register sign-in sessions and
470
+ `createPaymentLinkStripe`: a repeat books nothing (a second short-lived
471
+ session, a second link nobody else has seen);
460
472
  - on the money calls: every error envelope **without a code**, and every
461
473
  code that is not one of the rejection codes below. The Hobex and Stripe
462
474
  handlers also answer with a plain error message after the provider was
@@ -490,17 +502,33 @@ failed `hobexRefund` throws a `KasseneckApiError`; it never returns `false`,
490
502
  because a `false` on an unclear outcome invites a second refund. Even a
491
503
  `'rejected'` is no invitation to resend blindly: fix the cause first.
492
504
 
493
- **Never retry these six calls, and never put a retrying layer under the
494
- package.** A `fetch` passed in `options.fetch` (or a proxy or service worker in
495
- front of it) must not resend a request by itself after a network error, a
496
- timeout or a 5xx: a silent second send is a second signed receipt, a second
497
- charge or a second refund, and the package cannot see it. Look the result up
498
- instead (`getReceipt`, `listMyReceipts`, the Hobex transaction by its
499
- `transactionId`, the Stripe session).
500
-
501
- `outcome` only covers signing and the money calls. After a network error on
502
- `issueInvoice` an invoice may still have been issued; retry it with the same
503
- `idempotencyKey`.
505
+ **Never retry a call with an effect blindly, and never put a retrying layer
506
+ under the package.** A `fetch` passed in `options.fetch` (or a proxy or service
507
+ worker in front of it) must not resend a request by itself after a network
508
+ error, a timeout or a 5xx: a silent second send is a second signed receipt, a
509
+ second charge, a second refund or a second booking, and the package cannot
510
+ see it. Look the result up instead (`getReceipt`, `listMyReceipts`, the Hobex
511
+ transaction by its `transactionId`, the Stripe session).
512
+
513
+ **With an `idempotencyKey`** (inventory writes and reservations, `issueInvoice`,
514
+ `cancelInvoice`, `createCreditNote`, `recordInvoicePayment`, `createCustomer`
515
+ and `createPartnerCustomer` with a key) the safe retry after `'unknown'` is the
516
+ same request with the **same** key: it takes effect exactly once and returns
517
+ the stored answer (invoices and partner customers mark it with
518
+ `replayed: true`; inventory writes return the stored answer unchanged). Never a
519
+ new key: that books a second time. `createInvoiceItem` has no key; the server recognises a repeat by
520
+ `source` + `reference`, so the same request with the same values is safe
521
+ (`replayed: true`; other data gives `item_reference_conflict`). Without a key
522
+ (receipts, cancellations, money calls, settings, webhooks, `updateCustomer`,
523
+ `createCustomer` and `createPartnerCustomer` without one, `updateInvoiceItem`,
524
+ `withdrawInvoiceItem`, `setCustomerMandate`, `revokeCustomerMandate`) read the
525
+ state first (for invoice items `getInvoiceItem`) and only then decide.
526
+
527
+ A call through the open transport that this package does not know counts as
528
+ having no effect; say so with `transport(name, params, undefined, undefined,
529
+ { hasEffect: true })`. `hasEffect: false` only counts together with
530
+ `params.dryRun === true` (a dry run under the name of the real call); without
531
+ it a call with an effect stays `'unknown'`.
504
532
 
505
533
  ```ts
506
534
  import { isOutcomeUnknown, paymentsExpectedCents } from '@kreiseck/kasseneck-api';
@@ -944,7 +972,7 @@ process:
944
972
  `hobexPay`, `hobexRefund` and `stripeCaptureIntent` move money. They are never
945
973
  retried, neither by the app nor by a retrying `fetch` under the package: on
946
974
  `isOutcomeUnknown(error)` look the payment up (see
947
- [`outcome: 'unknown'`](#outcome-unknown-never-retry-look-it-up)).
975
+ [`outcome: 'unknown'`](#outcome-unknown-never-resend-blindly)).
948
976
  - **Hobex HPS via Kasseneck Connect**, described below.
949
977
 
950
978
  ### Hobex HPS via Kasseneck Connect
@@ -1373,7 +1401,13 @@ try {
1373
1401
 
1374
1402
  **After a timeout, retry with the same `idempotencyKey`**, never with a new one:
1375
1403
  you then get the invoice that was already issued (`replayed: true`). The same
1376
- key with different data gives `idempotency_conflict`.
1404
+ key with different data gives `idempotency_conflict`. Since 1.5.1 every invoice
1405
+ call with an effect (`issueInvoice`, `cancelInvoice`, `createCreditNote`,
1406
+ `recordInvoicePayment`, `createCustomer`, `updateCustomer`) reports
1407
+ `outcome: 'unknown'` after a timeout, a network error, HTTP 5xx or an
1408
+ unreadable answer (before: `'rejected'`); `previewInvoice` and the reading
1409
+ calls stay `'rejected'`. See
1410
+ [`outcome: 'unknown'`](#outcome-unknown-never-resend-blindly).
1377
1411
 
1378
1412
  Validation errors arrive as `validation` with field paths
1379
1413
  (`invoiceFieldErrors(error)` → `[{ field: 'items[0].vatRate', message }]`).
@@ -1566,7 +1600,11 @@ Every write takes an `idempotencyKey` (1 to 120 characters). The same request
1566
1600
  with the same key takes effect exactly once; repeating it returns the stored
1567
1601
  answer of the first call, the same key with different content gives
1568
1602
  `idempotency_conflict`. After a timeout or a network error, repeat with the
1569
- **same** key, never with a new one. The client refuses to send a write without
1603
+ **same** key, never with a new one. Since 1.5.1 every write, reservation and
1604
+ webhook call reports `outcome: 'unknown'` in that case (also after HTTP 5xx or
1605
+ an unreadable answer; before: `'rejected'`), so `isOutcomeUnknown(error)` is
1606
+ the signal to resend with the same key; `previewGoodsReceipt` and the reading
1607
+ calls stay `'rejected'`. The client refuses to send a write without
1570
1608
  a valid key, and checks before sending only what is certainly wrong without the
1571
1609
  network (required ids, integer quantities, amounts and prices, the range of
1572
1610
  `expiresInMinutes`); everything else the server decides and reports with its
@@ -1784,7 +1822,7 @@ in machine form what both sides agreed on, among them:
1784
1822
  |---|---|---|
1785
1823
  | `pos-settings-defaults.json` | field names and defaults of the register settings as sent on `/api/v3` (`business`, `device`), derived from the backend contract in `fixtures/v3/` | `npm run fixtures:kasse` |
1786
1824
  | `stored/pos-settings-defaults.json` | the same defaults in the stored (internal, German) form (`betrieb`, `geraet`) | `npm run fixtures:kasse` |
1787
- | `surface.json` | base URLs (`baseUrls.public`, `baseUrls.pos`), the calls per path (`calls`, and the backend's `routes`), settings value lists (`enums`, keyed by field), other register lists (`pos`), register error codes, permission keys, shortcut actions, partner, invoice and inventory lists | `npm run fixtures:oberflaeche` |
1825
+ | `surface.json` | base URLs (`baseUrls.public`, `baseUrls.pos`), the calls per path (`calls`, and the backend's `routes`), the calls with an effect whose outcome is `unknown` after a timeout, network error, 5xx or unreadable answer (`unknownOutcomeCalls`, sorted), settings value lists (`enums`, keyed by field), other register lists (`pos`), register error codes, permission keys, shortcut actions, partner, invoice and inventory lists | `npm run fixtures:oberflaeche` |
1788
1826
  | `hobex-hps-codes.json` | measured HPS result codes, their meaning and whether they settle an outcome (the contract behind `isConclusive`) | `npm run fixtures:hobex-hps-codes` |
1789
1827
  | `pos-texts.json` | the register's message catalogue | `npm run fixtures:texte` |
1790
1828
  | `invoice-texts.json` | invoice texts in both languages | `npm run fixtures:rechnungstexte` |
@@ -12,7 +12,7 @@
12
12
  * weiterhin jeden `string`: wer einen Aufruf braucht, den dieses Paket nicht
13
13
  * umhuellt, muss ihn weiterhin absetzen koennen.
14
14
  */
15
- import type { TransportBodyFields } from './transport.js';
15
+ import type { TransportBodyFields, TransportCallOptions } from './transport.js';
16
16
  export declare const ALL_CALLS: readonly ["activateCashregister", "cancelInvoice", "cancelReceipt", "createCreditNote", "createCustomer", "createCustomerCashregister", "createPartnerCustomer", "checkPartnerCustomerEmail", "createPartnerWebhook", "createPaymentLinkStripe", "createPrintJob", "createReceipt", "deletePartnerWebhook", "downloadDailyReport", "downloadReport", "endRegisterSession", "financeWebService", "generateFullReceiptId", "getCustomer", "getCustomerCredentials", "getCustomerSignatureStatus", "getFirstReceiptDate", "getInvoice", "getInvoicePdf", "getInvoiceSetupStatus", "getInvoiceXml", "getKasseSettings", "getPartnerCustomer", "getPartnerInfo", "getPrintJob", "getReceipt", "getReportV2", "hobexPayApi", "hobexRefundApi", "issueInvoice", "listCustomerCashregisters", "listInvoices", "listBrands", "listMyArticleGroups", "listMyArticles", "listMyCashregisters", "listMyPrinters", "listMyReceipts", "listMyStock", "listMyStockLocations", "listMyTipRecipients", "listPartnerCustomers", "listPartnerWebhookDeliveries", "listPartnerWebhooks", "listRegisterUsersForDevice", "listRegisterSessionsForDevice", "pairRegisterDevice", "recordInvoicePayment", "registerPinLogin", "registerUserLogin", "renewRegisterSession", "reportCustomerContract", "requestCustomerSignature", "searchCustomers", "sendPartnerCustomerFonLink", "sendPartnerWebhookTest", "sendReceiptEmail", "setMyCashregisterStockLocation", "setMyKasseLogo", "setMyKasseSettings", "setMyRegisterDeviceSettings", "stripeCaptureIntent", "unpairRegisterDevice", "rotatePartnerWebhookSecret", "updateCustomer", "updatePartnerWebhook", "getArticle", "listArticles", "lookupArticleByCode", "listLocations", "getStock", "listStock", "listStockMovements", "createWebhook", "updateWebhook", "deleteWebhook", "listWebhooks", "sendWebhookTest", "rotateWebhookSecret", "listWebhookDeliveries", "createArticle", "updateArticle", "deactivateArticle", "receiveGoods", "transferStock", "recordStockLoss", "changeStockCondition", "reverseStockMovement", "createReservation", "extendReservation", "releaseReservation", "getReservation", "listReservations"];
17
17
  export type ApiCall = typeof ALL_CALLS[number];
18
18
  /**
@@ -31,6 +31,60 @@ export declare const PUBLIC_CALLS: readonly ["createReceipt", "getReceipt", "can
31
31
  export declare const POS_CALLS: readonly ["pairRegisterDevice", "listRegisterUsersForDevice", "listRegisterSessionsForDevice", "registerUserLogin", "registerPinLogin", "renewRegisterSession", "endRegisterSession", "unpairRegisterDevice", "listMyCashregisters", "listMyReceipts", "getReceipt", "createReceipt", "generateFullReceiptId", "cancelReceipt", "sendReceiptEmail", "listMyArticleGroups", "listMyArticles", "getKasseSettings", "setMyKasseSettings", "setMyKasseLogo", "setMyRegisterDeviceSettings", "listMyPrinters", "createPrintJob", "getPrintJob", "listMyTipRecipients", "listMyStockLocations", "listMyStock", "setMyCashregisterStockLocation"];
32
32
  export type PublicCall = typeof PUBLIC_CALLS[number];
33
33
  export type PosCall = typeof POS_CALLS[number];
34
+ /**
35
+ * Die Aufrufe **mit Wirkung**, alphabetisch: sie legen etwas an, aendern oder
36
+ * loeschen es, buchen, signieren, bewegen Geld oder senden etwas hinaus
37
+ * (Mail, Druckjob, Probesendung). Scheitert einer, nachdem die Anfrage
38
+ * unterwegs war (Netzfehler, Zeitlimit, HTTP 5xx, unlesbare Erfolgsantwort,
39
+ * HTML mit Kennzeichen), meldet der Transport `outcome: 'unknown'`: der
40
+ * Vorgang kann ausgefuehrt sein.
41
+ *
42
+ * Bis 1.5.0 galt das nur fuer die sechs signierenden und geldbewegenden
43
+ * Aufrufe; ein Wareneingang oder eine Rechnung nach einem Zeitlimit kam als
44
+ * `'rejected'` an, und wer das glaubte und mit NEUEM Idempotenzschluessel
45
+ * neu sendete, buchte doppelt.
46
+ *
47
+ * Geht als `unknownOutcomeCalls` in den Vertrag (`fixtures/surface.json`);
48
+ * der Dart-Zwilling prueft seine Liste dagegen. Jeder Aufruf aus
49
+ * [ALL_CALLS], [PUBLIC_CALLS] und [POS_CALLS] steht entweder hier oder in
50
+ * [CALLS_WITHOUT_EFFECT] (Waechter `test/ausgang-einordnung.test.ts`).
51
+ */
52
+ export declare const UNKNOWN_OUTCOME_CALLS: readonly ["activateCashregister", "cancelInvoice", "cancelReceipt", "changeStockCondition", "createArticle", "createCreditNote", "createCustomer", "createCustomerCashregister", "createInvoiceItem", "createPartnerCustomer", "createPartnerWebhook", "createPrintJob", "createReceipt", "createReservation", "createWebhook", "deactivateArticle", "deletePartnerWebhook", "deleteWebhook", "extendReservation", "financeWebService", "hobexPayApi", "hobexRefundApi", "issueInvoice", "pairRegisterDevice", "receiveGoods", "recordInvoicePayment", "recordStockLoss", "releaseReservation", "reportCustomerContract", "requestCustomerSignature", "reverseStockMovement", "revokeCustomerMandate", "rotatePartnerWebhookSecret", "rotateWebhookSecret", "sendPartnerCustomerFonLink", "sendPartnerWebhookTest", "sendReceiptEmail", "sendWebhookTest", "setCustomerMandate", "setMyCashregisterStockLocation", "setMyKasseLogo", "setMyKasseSettings", "setMyRegisterDeviceSettings", "stripeCaptureIntent", "transferStock", "unpairRegisterDevice", "updateArticle", "updateCustomer", "updateInvoiceItem", "updatePartnerWebhook", "updateWebhook", "withdrawInvoiceItem"];
53
+ /** Warum ein Aufruf keine Wirkung hat, die ein Zeitlimit offen lassen koennte. */
54
+ export type CallWithoutEffectReason = 'read' | 'repeatable';
55
+ /**
56
+ * Die Aufrufe **ohne Wirkung**, je mit Grund. Bei ihnen bleibt es bei
57
+ * `outcome: 'rejected'`, auch nach Zeitlimit, Netzfehler oder HTTP 5xx; eine
58
+ * Wiederholung bucht nichts doppelt:
59
+ *
60
+ * - `read`: reines Lesen (auch Berichte, Dateien, `generateFullReceiptId`,
61
+ * das nur einen Link-Schluessel ableitet, und `getCustomerCredentials`,
62
+ * dessen Abruf das Backend nur mitschreibt).
63
+ * - `repeatable`: legt etwas an, das ohne die verlorene Antwort niemand
64
+ * erreicht und das von selbst verfaellt, und bucht nichts. Die Sitzung der
65
+ * Kassen-Anmeldung (anmelden, verlaengern, abmelden: eine Wiederholung legt
66
+ * hoechstens eine weitere kurzlebige Sitzung an bzw. verlaengert oder
67
+ * beendet dieselbe noch einmal) und der Stripe-Zahlungslink
68
+ * (`createPaymentLinkStripe`: den Link kennt nur die Antwort, ein nie
69
+ * zugestellter Link wird nie bezahlt; Geld bewegt erst die Zahlung bzw.
70
+ * `stripeCaptureIntent`).
71
+ *
72
+ * Probelaeufe (`previewGoodsReceipt`, `previewInvoice`) teilen den Namen mit
73
+ * dem echten Aufruf und stehen darum nicht hier; die Huellen setzen fuer sie
74
+ * `hasEffect: false` beim Aufruf, das nur zusammen mit `dryRun: true` gilt
75
+ * (siehe `TransportCallOptions`).
76
+ *
77
+ * Paketintern; nicht Teil der Paketoberflaeche.
78
+ */
79
+ export declare const CALLS_WITHOUT_EFFECT: Readonly<Record<string, CallWithoutEffectReason>>;
80
+ /**
81
+ * Hat dieser Aufruf Wirkung, ist sein Ausgang nach Zeitlimit, Netzfehler,
82
+ * HTTP 5xx oder unlesbarer Erfolgsantwort also unklar? `financeWebService`
83
+ * zaehlt mit jedem Vorgang. Ein Name, den dieses Paket nicht kennt (der
84
+ * offene Transport nimmt jeden), ist `false`; wer so einen Aufruf mit Wirkung
85
+ * absetzt, sagt es mit `hasEffect: true`.
86
+ */
87
+ export declare function isUnknownOutcomeCall(name: string): boolean;
34
88
  /** Einer der 28 Aufrufe des Kassenwegs. */
35
89
  export declare function isPosCall(name: string): boolean;
36
90
  /**
@@ -40,6 +94,6 @@ export declare function isPosCall(name: string): boolean;
40
94
  */
41
95
  export declare function isPosOnlyCall(name: string): boolean;
42
96
  /** Wie [KasseneckTransport], nur mit bekanntem Aufrufnamen. Nicht exportiert nach aussen. */
43
- export type InternerTransport = <T = unknown>(functionName: ApiCall, params?: Record<string, unknown>, extraBodyFields?: TransportBodyFields, secretParams?: readonly string[]) => Promise<T>;
97
+ export type InternerTransport = <T = unknown>(functionName: ApiCall, params?: Record<string, unknown>, extraBodyFields?: TransportBodyFields, secretParams?: readonly string[], options?: TransportCallOptions) => Promise<T>;
44
98
  /** Wie [KasseneckBinaryTransport], nur mit bekanntem Aufrufnamen. */
45
99
  export type InternerBinaerTransport = (functionName: ApiCall, params?: Record<string, unknown>) => Promise<Uint8Array>;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.POS_CALLS = exports.PUBLIC_CALLS = exports.ALL_CALLS = void 0;
3
+ exports.CALLS_WITHOUT_EFFECT = exports.UNKNOWN_OUTCOME_CALLS = exports.POS_CALLS = exports.PUBLIC_CALLS = exports.ALL_CALLS = void 0;
4
+ exports.isUnknownOutcomeCall = isUnknownOutcomeCall;
4
5
  exports.isPosCall = isPosCall;
5
6
  exports.isPosOnlyCall = isPosOnlyCall;
6
7
  exports.ALL_CALLS = [
@@ -242,6 +243,172 @@ exports.POS_CALLS = [
242
243
  'listMyStock',
243
244
  'setMyCashregisterStockLocation',
244
245
  ];
246
+ /**
247
+ * Die Aufrufe **mit Wirkung**, alphabetisch: sie legen etwas an, aendern oder
248
+ * loeschen es, buchen, signieren, bewegen Geld oder senden etwas hinaus
249
+ * (Mail, Druckjob, Probesendung). Scheitert einer, nachdem die Anfrage
250
+ * unterwegs war (Netzfehler, Zeitlimit, HTTP 5xx, unlesbare Erfolgsantwort,
251
+ * HTML mit Kennzeichen), meldet der Transport `outcome: 'unknown'`: der
252
+ * Vorgang kann ausgefuehrt sein.
253
+ *
254
+ * Bis 1.5.0 galt das nur fuer die sechs signierenden und geldbewegenden
255
+ * Aufrufe; ein Wareneingang oder eine Rechnung nach einem Zeitlimit kam als
256
+ * `'rejected'` an, und wer das glaubte und mit NEUEM Idempotenzschluessel
257
+ * neu sendete, buchte doppelt.
258
+ *
259
+ * Geht als `unknownOutcomeCalls` in den Vertrag (`fixtures/surface.json`);
260
+ * der Dart-Zwilling prueft seine Liste dagegen. Jeder Aufruf aus
261
+ * [ALL_CALLS], [PUBLIC_CALLS] und [POS_CALLS] steht entweder hier oder in
262
+ * [CALLS_WITHOUT_EFFECT] (Waechter `test/ausgang-einordnung.test.ts`).
263
+ */
264
+ exports.UNKNOWN_OUTCOME_CALLS = [
265
+ 'activateCashregister',
266
+ 'cancelInvoice',
267
+ 'cancelReceipt',
268
+ 'changeStockCondition',
269
+ 'createArticle',
270
+ 'createCreditNote',
271
+ 'createCustomer',
272
+ 'createCustomerCashregister',
273
+ 'createInvoiceItem',
274
+ 'createPartnerCustomer',
275
+ 'createPartnerWebhook',
276
+ 'createPrintJob',
277
+ 'createReceipt',
278
+ 'createReservation',
279
+ 'createWebhook',
280
+ 'deactivateArticle',
281
+ 'deletePartnerWebhook',
282
+ 'deleteWebhook',
283
+ 'extendReservation',
284
+ 'financeWebService',
285
+ 'hobexPayApi',
286
+ 'hobexRefundApi',
287
+ 'issueInvoice',
288
+ 'pairRegisterDevice',
289
+ 'receiveGoods',
290
+ 'recordInvoicePayment',
291
+ 'recordStockLoss',
292
+ 'releaseReservation',
293
+ 'reportCustomerContract',
294
+ 'requestCustomerSignature',
295
+ 'reverseStockMovement',
296
+ 'revokeCustomerMandate',
297
+ 'rotatePartnerWebhookSecret',
298
+ 'rotateWebhookSecret',
299
+ 'sendPartnerCustomerFonLink',
300
+ 'sendPartnerWebhookTest',
301
+ 'sendReceiptEmail',
302
+ 'sendWebhookTest',
303
+ 'setCustomerMandate',
304
+ 'setMyCashregisterStockLocation',
305
+ 'setMyKasseLogo',
306
+ 'setMyKasseSettings',
307
+ 'setMyRegisterDeviceSettings',
308
+ 'stripeCaptureIntent',
309
+ 'transferStock',
310
+ 'unpairRegisterDevice',
311
+ 'updateArticle',
312
+ 'updateCustomer',
313
+ 'updateInvoiceItem',
314
+ 'updatePartnerWebhook',
315
+ 'updateWebhook',
316
+ 'withdrawInvoiceItem',
317
+ ];
318
+ /**
319
+ * Die Aufrufe **ohne Wirkung**, je mit Grund. Bei ihnen bleibt es bei
320
+ * `outcome: 'rejected'`, auch nach Zeitlimit, Netzfehler oder HTTP 5xx; eine
321
+ * Wiederholung bucht nichts doppelt:
322
+ *
323
+ * - `read`: reines Lesen (auch Berichte, Dateien, `generateFullReceiptId`,
324
+ * das nur einen Link-Schluessel ableitet, und `getCustomerCredentials`,
325
+ * dessen Abruf das Backend nur mitschreibt).
326
+ * - `repeatable`: legt etwas an, das ohne die verlorene Antwort niemand
327
+ * erreicht und das von selbst verfaellt, und bucht nichts. Die Sitzung der
328
+ * Kassen-Anmeldung (anmelden, verlaengern, abmelden: eine Wiederholung legt
329
+ * hoechstens eine weitere kurzlebige Sitzung an bzw. verlaengert oder
330
+ * beendet dieselbe noch einmal) und der Stripe-Zahlungslink
331
+ * (`createPaymentLinkStripe`: den Link kennt nur die Antwort, ein nie
332
+ * zugestellter Link wird nie bezahlt; Geld bewegt erst die Zahlung bzw.
333
+ * `stripeCaptureIntent`).
334
+ *
335
+ * Probelaeufe (`previewGoodsReceipt`, `previewInvoice`) teilen den Namen mit
336
+ * dem echten Aufruf und stehen darum nicht hier; die Huellen setzen fuer sie
337
+ * `hasEffect: false` beim Aufruf, das nur zusammen mit `dryRun: true` gilt
338
+ * (siehe `TransportCallOptions`).
339
+ *
340
+ * Paketintern; nicht Teil der Paketoberflaeche.
341
+ */
342
+ exports.CALLS_WITHOUT_EFFECT = Object.freeze({
343
+ checkPartnerCustomerEmail: 'read',
344
+ downloadDailyReport: 'read',
345
+ downloadReceipt: 'read',
346
+ downloadReport: 'read',
347
+ createPaymentLinkStripe: 'repeatable',
348
+ endRegisterSession: 'repeatable',
349
+ generateFullReceiptId: 'read',
350
+ getArticle: 'read',
351
+ getCustomer: 'read',
352
+ getCustomerCredentials: 'read',
353
+ getCustomerSignatureStatus: 'read',
354
+ getFirstReceiptDate: 'read',
355
+ getInvoice: 'read',
356
+ getInvoiceItem: 'read',
357
+ getInvoicePdf: 'read',
358
+ getInvoiceSetupStatus: 'read',
359
+ getInvoiceXml: 'read',
360
+ getKasseSettings: 'read',
361
+ getPartnerBilling: 'read',
362
+ getPartnerBillingMonth: 'read',
363
+ getPartnerCustomer: 'read',
364
+ getPartnerInfo: 'read',
365
+ getPrintJob: 'read',
366
+ getReceipt: 'read',
367
+ getReportV2: 'read',
368
+ getReservation: 'read',
369
+ getStock: 'read',
370
+ hobexGetStatus: 'read',
371
+ listArticles: 'read',
372
+ listBrands: 'read',
373
+ listCustomerCashregisters: 'read',
374
+ listInvoiceItems: 'read',
375
+ listInvoices: 'read',
376
+ listLocations: 'read',
377
+ listMyArticleGroups: 'read',
378
+ listMyArticles: 'read',
379
+ listMyCashregisters: 'read',
380
+ listMyPrinters: 'read',
381
+ listMyReceipts: 'read',
382
+ listMyStock: 'read',
383
+ listMyStockLocations: 'read',
384
+ listMyTipRecipients: 'read',
385
+ listPartnerCustomers: 'read',
386
+ listPartnerWebhookDeliveries: 'read',
387
+ listPartnerWebhooks: 'read',
388
+ listRegisterSessionsForDevice: 'read',
389
+ listRegisterUsersForDevice: 'read',
390
+ listReservations: 'read',
391
+ listStock: 'read',
392
+ listStockMovements: 'read',
393
+ listWebhookDeliveries: 'read',
394
+ listWebhooks: 'read',
395
+ lookupArticleByCode: 'read',
396
+ registerPinLogin: 'repeatable',
397
+ registerUserLogin: 'repeatable',
398
+ renewRegisterSession: 'repeatable',
399
+ searchCustomers: 'read',
400
+ });
401
+ const MIT_WIRKUNG = new Set(exports.UNKNOWN_OUTCOME_CALLS);
402
+ /**
403
+ * Hat dieser Aufruf Wirkung, ist sein Ausgang nach Zeitlimit, Netzfehler,
404
+ * HTTP 5xx oder unlesbarer Erfolgsantwort also unklar? `financeWebService`
405
+ * zaehlt mit jedem Vorgang. Ein Name, den dieses Paket nicht kennt (der
406
+ * offene Transport nimmt jeden), ist `false`; wer so einen Aufruf mit Wirkung
407
+ * absetzt, sagt es mit `hasEffect: true`.
408
+ */
409
+ function isUnknownOutcomeCall(name) {
410
+ return MIT_WIRKUNG.has(name);
411
+ }
245
412
  const OEFFENTLICH = new Set(exports.PUBLIC_CALLS);
246
413
  const KASSENWEG = new Set(exports.POS_CALLS);
247
414
  const NUR_KASSE = new Set(exports.POS_CALLS.filter((name) => !OEFFENTLICH.has(name)));
@@ -25,20 +25,27 @@
25
25
  * **Entscheidend ist `outcome`:** `'rejected'` heisst abgelehnt, nichts
26
26
  * geschehen, Wiederholen hilft nicht. `'unknown'` heisst: der Vorgang kann
27
27
  * ausgefuehrt sein (bei `createReceipt` ein signierter Beleg). Dann **nie
28
- * wiederholen**, sondern das Ergebnis nachlesen (Belegliste, `getReceipt`).
28
+ * blind wiederholen**: Belege, Stornos und Zahlungen nachlesen
29
+ * (Belegliste, `getReceipt`, Terminal-Beleg); ein Aufruf mit
30
+ * `idempotencyKey` (Lager schreiben, Rechnung, Partner-Betrieb mit
31
+ * Schluessel) darf mit **demselben** Schluessel erneut gesendet werden und
32
+ * wirkt genau einmal, nie mit einem neuen. `createInvoiceItem` erkennt die
33
+ * Wiederholung an `source` + `reference` (dieselben Werte erneut senden);
34
+ * Posten aendern oder zurueckziehen und Mandate haben keinen Schluessel:
35
+ * erst nachlesen.
29
36
  * - `KasseneckHttpError` — die Antwort war **keine** verwertbare Huelle:
30
37
  * HTTP 500/404 ohne Huelle, leerer Rumpf oder Text statt JSON. Beim
31
38
  * Bericht-Download gelten dieselben Gruende fuer alles, was kein PDF ist.
32
39
  * `reason` trennt die Faelle maschinenlesbar. Auf einem Aufruf mit
33
- * Wirkung (signierend oder geldbewegend) hat HTTP 5xx
40
+ * Wirkung (`UNKNOWN_OUTCOME_CALLS` in aufrufe.ts) hat HTTP 5xx
34
41
  * `outcome: 'unknown'`, ebenso HTTP 200 mit Kennzeichen, aber leerem oder
35
42
  * unlesbarem Rumpf.
36
43
  * - `KasseneckNetworkError` — die Antwort kam gar nicht: Netz weg, DNS,
37
44
  * abgebrochene Verbindung oder Zeitueberschreitung (`timedOut`). Auch hier
38
45
  * gilt `outcome`: war die Anfrage schon unterwegs und ist der Aufruf einer
39
- * mit Wirkung (signierend: `createReceipt`, `cancelReceipt`,
40
- * `financeWebService`; geldbewegend: `hobexPayApi`, `hobexRefundApi`,
41
- * `stripeCaptureIntent`), ist er `'unknown'`.
46
+ * mit Wirkung (signieren, Geld bewegen, buchen, ausstellen, anlegen,
47
+ * aendern, loeschen, senden; die Liste `UNKNOWN_OUTCOME_CALLS`), ist er
48
+ * `'unknown'`. Lesen und Probelauf bleiben `'rejected'`.
42
49
  * - `KasseneckAuthError` — es kam nicht einmal zur Anfrage, weil die Anmeldung
43
50
  * scheiterte (fehlende Zugangsdaten, oder der Token-/Sitzungsgeber warf).
44
51
  * In der Browser-Kasse mit ihrer 90-Sekunden-Sitzung ist das Alltag, kein
@@ -106,8 +113,10 @@ export declare function causeDigest(ursache: unknown, geheimnisse: readonly stri
106
113
  export declare function fehlerDetails(daten: unknown, geheimnisse: readonly string[]): Record<string, unknown>;
107
114
  /**
108
115
  * Ausgang eines gescheiterten Aufrufs. `'rejected'`: nichts geschehen.
109
- * `'unknown'`: der Vorgang kann ausgefuehrt sein; nie wiederholen, sondern
110
- * das Ergebnis nachlesen.
116
+ * `'unknown'`: der Vorgang kann ausgefuehrt sein; nie blind wiederholen.
117
+ * Belege, Stornos und Zahlungen nachlesen; ein Aufruf mit `idempotencyKey`
118
+ * nur mit **demselben** Schluessel erneut senden (wirkt genau einmal); ohne
119
+ * Schluessel erst nachlesen.
111
120
  */
112
121
  export type ErrorOutcome = 'unknown' | 'rejected';
113
122
  /**
@@ -195,12 +204,12 @@ export declare class KasseneckHttpError extends Error {
195
204
  /** Maschinenlesbarer Grund — trennt den Rewrite-Fall vom 500er ohne Textparsen. */
196
205
  readonly reason: HttpFailureReason;
197
206
  /**
198
- * `'unknown'` auf einem Aufruf mit Wirkung (`createReceipt`,
199
- * `cancelReceipt`, `financeWebService`, `hobexPayApi`, `hobexRefundApi`,
200
- * `stripeCaptureIntent`) bei HTTP 5xx und bei HTTP 200 mit
201
- * Kennzeichen, aber unlesbarem Rumpf (`empty-body`, `not-json` auch bei
202
- * `text/html`, `missing-status`): der Handler kann gelaufen sein, nie wiederholen,
203
- * sondern nachlesen. Sonst `'rejected'` (auch 4xx).
207
+ * `'unknown'` auf einem Aufruf mit Wirkung (`UNKNOWN_OUTCOME_CALLS`:
208
+ * signieren, Geld bewegen, buchen, ausstellen, anlegen, aendern, loeschen,
209
+ * senden) bei HTTP 5xx und bei HTTP 200 mit Kennzeichen, aber unlesbarem
210
+ * Rumpf (`empty-body`, `not-json` auch bei `text/html`, `missing-status`):
211
+ * der Handler kann gelaufen sein, nie blind wiederholen (siehe
212
+ * [ErrorOutcome]). Sonst `'rejected'` (auch 4xx, Lesen und Probelauf).
204
213
  */
205
214
  readonly outcome: ErrorOutcome;
206
215
  constructor(functionName: string, statusCode: number, contentType: string | undefined, reason: HttpFailureReason, outcome?: ErrorOutcome);
@@ -218,9 +227,10 @@ export declare class KasseneckNetworkError extends Error {
218
227
  /** Code der zugrunde liegenden Ursache, sofern unbedenklich (z. B. `ECONNREFUSED`). */
219
228
  readonly causeCode: string | undefined;
220
229
  /**
221
- * `'unknown'`, wenn die Anfrage schon unterwegs war und der Aufruf signiert
222
- * (`createReceipt`, `cancelReceipt`, `financeWebService`): dann nie
223
- * wiederholen, sondern nachlesen. Sonst `'rejected'`.
230
+ * `'unknown'`, wenn die Anfrage schon unterwegs war und der Aufruf Wirkung
231
+ * hat (`UNKNOWN_OUTCOME_CALLS`): dann nie blind wiederholen (siehe
232
+ * [ErrorOutcome]). Sonst `'rejected'`, auch bei Lesen und Probelauf und
233
+ * wenn schon die Anmeldung das Zeitlimit verbrauchte.
224
234
  */
225
235
  readonly outcome: ErrorOutcome;
226
236
  constructor(functionName: string, timedOut: boolean, timeoutMs: number, cause?: CauseDigest, outcome?: ErrorOutcome);
@@ -273,9 +283,12 @@ export declare class KasseneckValidationError extends Error {
273
283
  export type KasseneckError = KasseneckApiError | KasseneckHttpError | KasseneckNetworkError | KasseneckAuthError | KasseneckValidationError;
274
284
  /**
275
285
  * Ist der Ausgang dieses Fehlers unklar (`outcome === 'unknown'`)? Dann den
276
- * Aufruf **nicht wiederholen**, sondern das Ergebnis nachlesen. Gilt fuer
277
- * jede Fehlerart; nur [KasseneckApiError], [KasseneckHttpError] und
278
- * [KasseneckNetworkError] koennen `'unknown'` sein.
286
+ * Aufruf **nicht blind wiederholen**: Belege, Stornos und Zahlungen
287
+ * nachlesen; einen Aufruf mit `idempotencyKey` nur mit **demselben**
288
+ * Schluessel erneut senden (wirkt genau einmal, ein neuer buchte doppelt);
289
+ * ohne Schluessel erst nachlesen. Gilt fuer jede Fehlerart; nur
290
+ * [KasseneckApiError], [KasseneckHttpError] und [KasseneckNetworkError]
291
+ * koennen `'unknown'` sein.
279
292
  */
280
293
  export declare function isOutcomeUnknown(error: unknown): boolean;
281
294
  export declare function isKasseneckApiError(error: unknown): error is KasseneckApiError;