@kreiseck/kasseneck-api 1.0.0-rc.1 → 1.0.0-rc.3
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.
- package/CHANGELOG.md +44 -8
- package/README.md +38 -11
- package/dist/cjs/client/errors.d.ts +26 -10
- package/dist/cjs/client/errors.js +35 -10
- package/dist/cjs/client/receipts.d.ts +10 -3
- package/dist/cjs/client/receipts.js +14 -29
- package/dist/cjs/client/transport.js +25 -13
- package/dist/cjs/models/cashregister.d.ts +14 -8
- package/dist/cjs/models/cashregister.js +4 -4
- package/dist/cjs/payments/hobex.d.ts +9 -1
- package/dist/cjs/payments/hobex.js +14 -2
- package/dist/cjs/payments/stripe.d.ts +3 -0
- package/dist/cjs/payments/stripe.js +6 -1
- package/dist/cjs/pos/client.d.ts +6 -1
- package/dist/cjs/pos/client.js +15 -3
- package/dist/cjs/pos/drucker.js +22 -3
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/client/errors.d.ts +26 -10
- package/dist/esm/client/errors.js +34 -10
- package/dist/esm/client/receipts.d.ts +10 -3
- package/dist/esm/client/receipts.js +12 -27
- package/dist/esm/client/transport.js +25 -13
- package/dist/esm/models/cashregister.d.ts +14 -8
- package/dist/esm/models/cashregister.js +4 -4
- package/dist/esm/payments/hobex.d.ts +9 -1
- package/dist/esm/payments/hobex.js +15 -3
- package/dist/esm/payments/stripe.d.ts +3 -0
- package/dist/esm/payments/stripe.js +7 -2
- package/dist/esm/pos/client.d.ts +6 -1
- package/dist/esm/pos/client.js +15 -3
- package/dist/esm/pos/drucker.js +22 -3
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/fixtures/hobex-hps-codes.json +1 -1
- package/fixtures/invoice-api.schema.json +1 -1
- package/fixtures/invoice-texts.json +1 -1
- package/fixtures/pos-texts.json +1 -1
- package/fixtures/surface.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -104,13 +104,49 @@ an upgrade as long as `/v1` is served.
|
|
|
104
104
|
means the operation may have happened: `dialect_mismatch`,
|
|
105
105
|
`receipt_outcome_unknown`, `cancellation_outcome_unknown`,
|
|
106
106
|
`response_translation_failed` (unless `details.handled === false`),
|
|
107
|
-
`response_unreadable` (a signing call reported success but the
|
|
108
|
-
lacks the receipt, the reference
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
107
|
+
`response_unreadable` (a signing or money call reported success but the
|
|
108
|
+
response lacks the receipt, the reference, the remaining quantities, the
|
|
109
|
+
Hobex receipt or the captured payment intent), and on `createReceipt`,
|
|
110
|
+
`cancelReceipt`, `financeWebService`, `hobexPayApi`, `hobexRefundApi` and
|
|
111
|
+
`stripeCaptureIntent` a network error, timeout or HTTP 5xx after sending as
|
|
112
|
+
well as HTTP 200 with the `/v3` marker but an empty, non-JSON (also HTML) or
|
|
113
|
+
status-less body. Never retry those; read the result back. Reason: a
|
|
114
|
+
retried receipt is a second signed receipt in the chain.
|
|
115
|
+
- **Money calls have an unknown outcome too (1.0.0-rc.2).** `hobexPay`,
|
|
116
|
+
`hobexRefund` and `stripeCaptureIntent` joined the calls above. In rc.1 a
|
|
117
|
+
timeout, network error or HTTP 5xx on them was `'rejected'`, and a success
|
|
118
|
+
reply without a readable Hobex receipt or payment intent was a
|
|
119
|
+
`KasseneckValidationError` (`scope: 'response'`); now both are `'unknown'`
|
|
120
|
+
(the latter as `KasseneckApiError` `response_unreadable`), and HTML with the
|
|
121
|
+
`/v3` marker is `not-json` with `'unknown'` instead of `route_missing`. An
|
|
122
|
+
error envelope throws a `KasseneckApiError` with its `code`; `hobexRefund`
|
|
123
|
+
never returns `false`. **Callers must never retry these calls, and must not
|
|
124
|
+
pass a `fetch` that retries by itself or wrap the package's requests in a
|
|
125
|
+
retrying layer** (proxy, service worker, HTTP client with a retry policy).
|
|
126
|
+
Reason: an app that read `'rejected'` could charge or refund a card twice;
|
|
127
|
+
a silent resend below the package does the same without any error. Same
|
|
128
|
+
change as in the Dart twin `kasseneck_api` (no `RetryClient`).
|
|
129
|
+
- **Findings of the Dart twin (1.0.0-rc.3).** `listMyCashregisters` reads
|
|
130
|
+
the start receipt under `onboarding.start_receipt_created`/`_transmitted`
|
|
131
|
+
(`_at`) as `/v3` sends it; rc.1 and rc.2 read the internal
|
|
132
|
+
`startbeleg_*` names and reported every register as having no start
|
|
133
|
+
receipt (`CashregisterOnboardingPayload` changes accordingly). The fields
|
|
134
|
+
of `CashregisterOnboarding` are English now as well: `startbelegCreated`
|
|
135
|
+
is `startReceiptCreated`, `startbelegTransmitted` is
|
|
136
|
+
`startReceiptTransmitted`, `startbelegCreatedAt` is `startReceiptCreatedAt`,
|
|
137
|
+
`startbelegTransmittedAt` is `startReceiptTransmittedAt`; the German-name
|
|
138
|
+
guard no longer exempts `startbeleg`.
|
|
139
|
+
`sendReceiptEmail` reads its success reply leniently: without `to` it
|
|
140
|
+
returns the address you sent, without `at` it returns `at: null`
|
|
141
|
+
(`SendReceiptEmailResult.at` is `string | null`); throwing there would
|
|
142
|
+
invite a second email. `listMyPrinters` without `data.printers` and
|
|
143
|
+
`createPrintJob`/`getPrintJob` without `data.jobId` throw a
|
|
144
|
+
`KasseneckValidationError` (`scope: 'response'`) instead of returning an
|
|
145
|
+
empty list or an empty id: "no printer yet" must not look like a broken
|
|
146
|
+
reply, and a job without an id cannot be polled, so the cashier would print
|
|
147
|
+
again. `setMyRegisterDeviceSettings` takes `shortcuts` only as the whole map
|
|
148
|
+
of known actions (as `posSettingsChanges` produces it): the server checks
|
|
149
|
+
keys bound twice only within the map it receives.
|
|
114
150
|
- **Strict validation before sending.** The 0.x payment fields
|
|
115
151
|
(`paymentMethod`, `paymentMethodFromServer`, `creditCardProvider`,
|
|
116
152
|
`cardPaymentId`, `cardPaymentData`) throw, also from plain JavaScript.
|
|
@@ -237,7 +273,7 @@ texts are English: `storno.ergebnis_unklar` is `cancellation.outcome_unknown`,
|
|
|
237
273
|
`tax.intra_community_supply.title`. Every part is lower case with underscores,
|
|
238
274
|
only country codes stay upper case (`country.AT`). Placeholders are English
|
|
239
275
|
too: `{betrag}` is `{amount}`, `{grund}` is `{reason}`, `{sekunden}` is
|
|
240
|
-
`{seconds}`, `{uid}` is `{vatId}`, and so on for
|
|
276
|
+
`{seconds}`, `{uid}` is `{vatId}`, and so on for 25 of 30 names; pass the
|
|
241
277
|
values under the new names (`messageText('checkout.locked', { reason })`). An
|
|
242
278
|
old name throws as a missing value. `ERROR_RULES` entries use
|
|
243
279
|
`kind`/`behavior`/`key` with the kinds `api`, `plain_text`, `timeout`,
|
package/README.md
CHANGED
|
@@ -427,9 +427,10 @@ silently. For a layout option of 0.x the message names its English successor.
|
|
|
427
427
|
### `outcome: 'unknown'`: never retry, look it up
|
|
428
428
|
|
|
429
429
|
`KasseneckApiError`, `KasseneckHttpError` and `KasseneckNetworkError` carry
|
|
430
|
-
`outcome`. `'rejected'` means no
|
|
431
|
-
signed
|
|
432
|
-
|
|
430
|
+
`outcome`. `'rejected'` means no operation with an effect is left open: no
|
|
431
|
+
receipt was signed, nothing went to FinanzOnline and no money moved, so
|
|
432
|
+
retrying the same request does not create a second receipt or charge (whether
|
|
433
|
+
it helps depends on the code). `'unknown'` means the operation **may have been carried out**, for
|
|
433
434
|
`createReceipt` a signed receipt in the chain. Then never send it again: read
|
|
434
435
|
the result back (`getReceipt`, `listMyReceipts`, the original of a
|
|
435
436
|
cancellation) and continue from there. `isOutcomeUnknown(error)` covers all
|
|
@@ -437,18 +438,35 @@ three classes. The outcome is unknown for:
|
|
|
437
438
|
|
|
438
439
|
- `dialect_mismatch`, `receipt_outcome_unknown`, `cancellation_outcome_unknown`;
|
|
439
440
|
- `response_translation_failed`, unless `details.handled === false`;
|
|
440
|
-
- `response_unreadable`: a signing call reported success,
|
|
441
|
-
lacks what the call promises (no receipt, no reference, no
|
|
442
|
-
quantities);
|
|
443
|
-
- on the signing calls `createReceipt`,
|
|
444
|
-
`
|
|
441
|
+
- `response_unreadable`: a signing or money-moving call reported success,
|
|
442
|
+
but the response lacks what the call promises (no receipt, no reference, no
|
|
443
|
+
remaining quantities, no Hobex receipt, no captured payment intent);
|
|
444
|
+
- on the calls with an effect: the signing calls `createReceipt`,
|
|
445
|
+
`cancelReceipt` and `financeWebService`, and the money calls `hobexPay`
|
|
446
|
+
(`hobexPayApi`, charges a card), `hobexRefund` (`hobexRefundApi`) and
|
|
447
|
+
`stripeCaptureIntent`: a network error or timeout after sending began, HTTP
|
|
445
448
|
5xx, and HTTP 200 with the `Kasseneck-Api-Version: v3` marker but an empty,
|
|
446
449
|
non-JSON (also `text/html`) or status-less body (`KasseneckHttpError`,
|
|
447
450
|
`reason` `empty-body`, `not-json` or `missing-status`). HTML without the
|
|
448
451
|
marker stays `route_missing` with `'rejected'`: no function saw the call.
|
|
449
452
|
|
|
450
|
-
|
|
451
|
-
|
|
453
|
+
On the money calls an unknown outcome means the card may have been charged,
|
|
454
|
+
the refund may have gone through, the payment may have been captured. A
|
|
455
|
+
rejection of `hobexRefund` throws a `KasseneckApiError` with its `code`; it
|
|
456
|
+
never returns `false`, because a `false` on an unclear outcome invites a
|
|
457
|
+
second refund.
|
|
458
|
+
|
|
459
|
+
**Never retry these six calls, and never put a retrying layer under the
|
|
460
|
+
package.** A `fetch` passed in `options.fetch` (or a proxy or service worker in
|
|
461
|
+
front of it) must not resend a request by itself after a network error, a
|
|
462
|
+
timeout or a 5xx: a silent second send is a second signed receipt, a second
|
|
463
|
+
charge or a second refund, and the package cannot see it. Look the result up
|
|
464
|
+
instead (`getReceipt`, `listMyReceipts`, the Hobex transaction by its
|
|
465
|
+
`transactionId`, the Stripe session).
|
|
466
|
+
|
|
467
|
+
`outcome` only covers signing and the money calls. After a network error on
|
|
468
|
+
`issueInvoice` an invoice may still have been issued; retry it with the same
|
|
469
|
+
`idempotencyKey`.
|
|
452
470
|
|
|
453
471
|
```ts
|
|
454
472
|
import { isOutcomeUnknown, paymentsExpectedCents } from '@kreiseck/kasseneck-api';
|
|
@@ -597,10 +615,14 @@ const confirmation = await api.sendReceiptEmail({
|
|
|
597
615
|
language: 'de', // optional; the backend currently only uses 'de'
|
|
598
616
|
});
|
|
599
617
|
confirmation.to; // address as the backend logged it (trimmed, lower case)
|
|
600
|
-
confirmation.at; // time, ISO with Vienna offset
|
|
618
|
+
confirmation.at; // time, ISO with Vienna offset, or null
|
|
601
619
|
confirmation.via; // 'own' | 'platform' | 'platform_fallback' | null
|
|
602
620
|
```
|
|
603
621
|
|
|
622
|
+
A success response means the email has gone out. If it lacks `to` or `at`,
|
|
623
|
+
the call still succeeds: `to` falls back to the address you sent and `at` is
|
|
624
|
+
`null`. Throwing there would invite sending the email a second time.
|
|
625
|
+
|
|
604
626
|
The email contains a **link to the public receipt page**, not a PDF
|
|
605
627
|
attachment: the receipt page uses the same line model as screen and printed
|
|
606
628
|
receipt and offers a PDF there. The receipt itself stays untouched (BAO § 131,
|
|
@@ -792,6 +814,11 @@ process:
|
|
|
792
814
|
- **Hobex cloud** (`hobexPay`, `hobexRefund`): a terminal registered with
|
|
793
815
|
Hobex, controlled over the network through the backend. Amounts are passed in
|
|
794
816
|
cents; the package converts to euros for Hobex.
|
|
817
|
+
|
|
818
|
+
`hobexPay`, `hobexRefund` and `stripeCaptureIntent` move money. They are never
|
|
819
|
+
retried, neither by the app nor by a retrying `fetch` under the package: on
|
|
820
|
+
`isOutcomeUnknown(error)` look the payment up (see
|
|
821
|
+
[`outcome: 'unknown'`](#outcome-unknown-never-retry-look-it-up)).
|
|
795
822
|
- **Hobex HPS via Kasseneck Connect**, described below.
|
|
796
823
|
|
|
797
824
|
### Hobex HPS via Kasseneck Connect
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* Single-Page-App hat geantwortet, der Aufruf kam nie an),
|
|
21
21
|
* `dialect_mismatch` (Antwort ohne Kennzeichen `Kasseneck-Api-Version: v3`:
|
|
22
22
|
* ein Rand ohne `/v3` hat geantwortet, **Ausgang unklar**) und
|
|
23
|
-
* `response_unreadable` (ein
|
|
23
|
+
* `response_unreadable` (ein Aufruf mit Wirkung meldete Erfolg, die
|
|
24
24
|
* Antwort traegt aber nicht, was er zusagt: **Ausgang unklar**).
|
|
25
25
|
* **Entscheidend ist `outcome`:** `'rejected'` heisst abgelehnt, nichts
|
|
26
26
|
* geschehen, Wiederholen hilft nicht. `'unknown'` heisst: der Vorgang kann
|
|
@@ -29,14 +29,16 @@
|
|
|
29
29
|
* - `KasseneckHttpError` — die Antwort war **keine** verwertbare Huelle:
|
|
30
30
|
* HTTP 500/404 ohne Huelle, leerer Rumpf oder Text statt JSON. Beim
|
|
31
31
|
* Bericht-Download gelten dieselben Gruende fuer alles, was kein PDF ist.
|
|
32
|
-
* `reason` trennt die Faelle maschinenlesbar. Auf einem
|
|
33
|
-
*
|
|
34
|
-
* Kennzeichen, aber leerem oder
|
|
32
|
+
* `reason` trennt die Faelle maschinenlesbar. Auf einem Aufruf mit
|
|
33
|
+
* Wirkung (signierend oder geldbewegend) hat HTTP 5xx
|
|
34
|
+
* `outcome: 'unknown'`, ebenso HTTP 200 mit Kennzeichen, aber leerem oder
|
|
35
|
+
* unlesbarem Rumpf.
|
|
35
36
|
* - `KasseneckNetworkError` — die Antwort kam gar nicht: Netz weg, DNS,
|
|
36
37
|
* abgebrochene Verbindung oder Zeitueberschreitung (`timedOut`). Auch hier
|
|
37
38
|
* gilt `outcome`: war die Anfrage schon unterwegs und ist der Aufruf einer
|
|
38
|
-
*
|
|
39
|
-
*
|
|
39
|
+
* mit Wirkung (signierend: `createReceipt`, `cancelReceipt`,
|
|
40
|
+
* `financeWebService`; geldbewegend: `hobexPayApi`, `hobexRefundApi`,
|
|
41
|
+
* `stripeCaptureIntent`), ist er `'unknown'`.
|
|
40
42
|
* - `KasseneckAuthError` — es kam nicht einmal zur Anfrage, weil die Anmeldung
|
|
41
43
|
* scheiterte (fehlende Zugangsdaten, oder der Token-/Sitzungsgeber warf).
|
|
42
44
|
* In der Browser-Kasse mit ihrer 90-Sekunden-Sitzung ist das Alltag, kein
|
|
@@ -111,7 +113,7 @@ export type ErrorOutcome = 'unknown' | 'rejected';
|
|
|
111
113
|
/**
|
|
112
114
|
* Codes, die das Paket selbst vergibt, nicht der Server: `route_missing`
|
|
113
115
|
* (HTML statt Backend, der Aufruf kam nie an) und `response_unreadable`
|
|
114
|
-
* (ein
|
|
116
|
+
* (ein Aufruf mit Wirkung meldete Erfolg, die Antwort ist aber unlesbar;
|
|
115
117
|
* Ausgang unklar). `dialect_mismatch` vergibt das Paket ebenfalls, der Code
|
|
116
118
|
* gehoert aber schon zum Rand des Servers (`errorCodes.edge`).
|
|
117
119
|
*/
|
|
@@ -142,7 +144,7 @@ export declare class KasseneckApiError extends Error {
|
|
|
142
144
|
/**
|
|
143
145
|
* `'unknown'` bei `dialect_mismatch`, `receipt_outcome_unknown`,
|
|
144
146
|
* `cancellation_outcome_unknown`, `response_unreadable` (Erfolg gemeldet,
|
|
145
|
-
* Antwort eines
|
|
147
|
+
* Antwort eines Aufrufs mit Wirkung aber unlesbar) und
|
|
146
148
|
* `response_translation_failed` (ausser mit `details.handled === false`);
|
|
147
149
|
* sonst `'rejected'`. Bei `'unknown'` nie wiederholen, sondern nachlesen.
|
|
148
150
|
*/
|
|
@@ -162,8 +164,9 @@ export declare class KasseneckHttpError extends Error {
|
|
|
162
164
|
/** Maschinenlesbarer Grund — trennt den Rewrite-Fall vom 500er ohne Textparsen. */
|
|
163
165
|
readonly reason: HttpFailureReason;
|
|
164
166
|
/**
|
|
165
|
-
* `'unknown'` auf einem
|
|
166
|
-
* `cancelReceipt`, `financeWebService`
|
|
167
|
+
* `'unknown'` auf einem Aufruf mit Wirkung (`createReceipt`,
|
|
168
|
+
* `cancelReceipt`, `financeWebService`, `hobexPayApi`, `hobexRefundApi`,
|
|
169
|
+
* `stripeCaptureIntent`) bei HTTP 5xx und bei HTTP 200 mit
|
|
167
170
|
* Kennzeichen, aber unlesbarem Rumpf (`empty-body`, `not-json` auch bei
|
|
168
171
|
* `text/html`, `missing-status`): der Handler kann gelaufen sein, nie wiederholen,
|
|
169
172
|
* sondern nachlesen. Sonst `'rejected'` (auch 4xx).
|
|
@@ -249,3 +252,16 @@ export declare function isKasseneckHttpError(error: unknown): error is Kasseneck
|
|
|
249
252
|
export declare function isKasseneckNetworkError(error: unknown): error is KasseneckNetworkError;
|
|
250
253
|
export declare function isKasseneckAuthError(error: unknown): error is KasseneckAuthError;
|
|
251
254
|
export declare function isKasseneckValidationError(error: unknown): error is KasseneckValidationError;
|
|
255
|
+
/**
|
|
256
|
+
* Liest die Erfolgsantwort eines Aufrufs **mit Wirkung** (signierend oder
|
|
257
|
+
* geldbewegend). Scheitert das Lesen (fehlender Beleg, fehlender Bezug,
|
|
258
|
+
* unbrauchbares Feld oder ein Laufzeitfehler beim Umwandeln), hat der Server
|
|
259
|
+
* trotzdem Erfolg gemeldet: der Beleg ist signiert und im DEP, die Karte
|
|
260
|
+
* belastet bzw. der Einzug gelaufen. Das darf nie als gewoehnlicher Fehler
|
|
261
|
+
* enden, sonst kassiert die Kasse ein zweites Mal. Darum wird daraus
|
|
262
|
+
* `KasseneckApiError` mit Code `response_unreadable` und `outcome: 'unknown'`.
|
|
263
|
+
* Der Grund stammt vom Paket; aus der Antwort selbst wird nichts uebernommen.
|
|
264
|
+
*
|
|
265
|
+
* Paketintern (receipts.ts, payments/); nicht Teil der Paketoberflaeche.
|
|
266
|
+
*/
|
|
267
|
+
export declare function signiertGelesen<T>(functionName: string, lesen: () => T): T;
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* Single-Page-App hat geantwortet, der Aufruf kam nie an),
|
|
22
22
|
* `dialect_mismatch` (Antwort ohne Kennzeichen `Kasseneck-Api-Version: v3`:
|
|
23
23
|
* ein Rand ohne `/v3` hat geantwortet, **Ausgang unklar**) und
|
|
24
|
-
* `response_unreadable` (ein
|
|
24
|
+
* `response_unreadable` (ein Aufruf mit Wirkung meldete Erfolg, die
|
|
25
25
|
* Antwort traegt aber nicht, was er zusagt: **Ausgang unklar**).
|
|
26
26
|
* **Entscheidend ist `outcome`:** `'rejected'` heisst abgelehnt, nichts
|
|
27
27
|
* geschehen, Wiederholen hilft nicht. `'unknown'` heisst: der Vorgang kann
|
|
@@ -30,14 +30,16 @@
|
|
|
30
30
|
* - `KasseneckHttpError` — die Antwort war **keine** verwertbare Huelle:
|
|
31
31
|
* HTTP 500/404 ohne Huelle, leerer Rumpf oder Text statt JSON. Beim
|
|
32
32
|
* Bericht-Download gelten dieselben Gruende fuer alles, was kein PDF ist.
|
|
33
|
-
* `reason` trennt die Faelle maschinenlesbar. Auf einem
|
|
34
|
-
*
|
|
35
|
-
* Kennzeichen, aber leerem oder
|
|
33
|
+
* `reason` trennt die Faelle maschinenlesbar. Auf einem Aufruf mit
|
|
34
|
+
* Wirkung (signierend oder geldbewegend) hat HTTP 5xx
|
|
35
|
+
* `outcome: 'unknown'`, ebenso HTTP 200 mit Kennzeichen, aber leerem oder
|
|
36
|
+
* unlesbarem Rumpf.
|
|
36
37
|
* - `KasseneckNetworkError` — die Antwort kam gar nicht: Netz weg, DNS,
|
|
37
38
|
* abgebrochene Verbindung oder Zeitueberschreitung (`timedOut`). Auch hier
|
|
38
39
|
* gilt `outcome`: war die Anfrage schon unterwegs und ist der Aufruf einer
|
|
39
|
-
*
|
|
40
|
-
*
|
|
40
|
+
* mit Wirkung (signierend: `createReceipt`, `cancelReceipt`,
|
|
41
|
+
* `financeWebService`; geldbewegend: `hobexPayApi`, `hobexRefundApi`,
|
|
42
|
+
* `stripeCaptureIntent`), ist er `'unknown'`.
|
|
41
43
|
* - `KasseneckAuthError` — es kam nicht einmal zur Anfrage, weil die Anmeldung
|
|
42
44
|
* scheiterte (fehlende Zugangsdaten, oder der Token-/Sitzungsgeber warf).
|
|
43
45
|
* In der Browser-Kasse mit ihrer 90-Sekunden-Sitzung ist das Alltag, kein
|
|
@@ -80,6 +82,7 @@ exports.isKasseneckHttpError = isKasseneckHttpError;
|
|
|
80
82
|
exports.isKasseneckNetworkError = isKasseneckNetworkError;
|
|
81
83
|
exports.isKasseneckAuthError = isKasseneckAuthError;
|
|
82
84
|
exports.isKasseneckValidationError = isKasseneckValidationError;
|
|
85
|
+
exports.signiertGelesen = signiertGelesen;
|
|
83
86
|
// Bezeichner-artig: Buchstabe vorn, danach nur Bezeichnerzeichen, hoechstens
|
|
84
87
|
// 64 Zeichen. Das laesst `TypeError`, `ECONNREFUSED` und `auth/internal-error`
|
|
85
88
|
// durch, aber keinen Freitext und kein ID-Token (~900 Zeichen).
|
|
@@ -236,7 +239,7 @@ function ausgangAusCode(code, details) {
|
|
|
236
239
|
/**
|
|
237
240
|
* Codes, die das Paket selbst vergibt, nicht der Server: `route_missing`
|
|
238
241
|
* (HTML statt Backend, der Aufruf kam nie an) und `response_unreadable`
|
|
239
|
-
* (ein
|
|
242
|
+
* (ein Aufruf mit Wirkung meldete Erfolg, die Antwort ist aber unlesbar;
|
|
240
243
|
* Ausgang unklar). `dialect_mismatch` vergibt das Paket ebenfalls, der Code
|
|
241
244
|
* gehoert aber schon zum Rand des Servers (`errorCodes.edge`).
|
|
242
245
|
*/
|
|
@@ -266,7 +269,7 @@ class KasseneckApiError extends Error {
|
|
|
266
269
|
/**
|
|
267
270
|
* `'unknown'` bei `dialect_mismatch`, `receipt_outcome_unknown`,
|
|
268
271
|
* `cancellation_outcome_unknown`, `response_unreadable` (Erfolg gemeldet,
|
|
269
|
-
* Antwort eines
|
|
272
|
+
* Antwort eines Aufrufs mit Wirkung aber unlesbar) und
|
|
270
273
|
* `response_translation_failed` (ausser mit `details.handled === false`);
|
|
271
274
|
* sonst `'rejected'`. Bei `'unknown'` nie wiederholen, sondern nachlesen.
|
|
272
275
|
*/
|
|
@@ -301,8 +304,9 @@ class KasseneckHttpError extends Error {
|
|
|
301
304
|
/** Maschinenlesbarer Grund — trennt den Rewrite-Fall vom 500er ohne Textparsen. */
|
|
302
305
|
reason;
|
|
303
306
|
/**
|
|
304
|
-
* `'unknown'` auf einem
|
|
305
|
-
* `cancelReceipt`, `financeWebService`
|
|
307
|
+
* `'unknown'` auf einem Aufruf mit Wirkung (`createReceipt`,
|
|
308
|
+
* `cancelReceipt`, `financeWebService`, `hobexPayApi`, `hobexRefundApi`,
|
|
309
|
+
* `stripeCaptureIntent`) bei HTTP 5xx und bei HTTP 200 mit
|
|
306
310
|
* Kennzeichen, aber unlesbarem Rumpf (`empty-body`, `not-json` auch bei
|
|
307
311
|
* `text/html`, `missing-status`): der Handler kann gelaufen sein, nie wiederholen,
|
|
308
312
|
* sondern nachlesen. Sonst `'rejected'` (auch 4xx).
|
|
@@ -426,3 +430,24 @@ function isKasseneckAuthError(error) {
|
|
|
426
430
|
function isKasseneckValidationError(error) {
|
|
427
431
|
return error instanceof KasseneckValidationError;
|
|
428
432
|
}
|
|
433
|
+
/**
|
|
434
|
+
* Liest die Erfolgsantwort eines Aufrufs **mit Wirkung** (signierend oder
|
|
435
|
+
* geldbewegend). Scheitert das Lesen (fehlender Beleg, fehlender Bezug,
|
|
436
|
+
* unbrauchbares Feld oder ein Laufzeitfehler beim Umwandeln), hat der Server
|
|
437
|
+
* trotzdem Erfolg gemeldet: der Beleg ist signiert und im DEP, die Karte
|
|
438
|
+
* belastet bzw. der Einzug gelaufen. Das darf nie als gewoehnlicher Fehler
|
|
439
|
+
* enden, sonst kassiert die Kasse ein zweites Mal. Darum wird daraus
|
|
440
|
+
* `KasseneckApiError` mit Code `response_unreadable` und `outcome: 'unknown'`.
|
|
441
|
+
* Der Grund stammt vom Paket; aus der Antwort selbst wird nichts uebernommen.
|
|
442
|
+
*
|
|
443
|
+
* Paketintern (receipts.ts, payments/); nicht Teil der Paketoberflaeche.
|
|
444
|
+
*/
|
|
445
|
+
function signiertGelesen(functionName, lesen) {
|
|
446
|
+
try {
|
|
447
|
+
return lesen();
|
|
448
|
+
}
|
|
449
|
+
catch (ursache) {
|
|
450
|
+
const grund = ursache instanceof KasseneckValidationError ? ursache.reason : 'Antwort nicht lesbar';
|
|
451
|
+
throw new KasseneckApiError(functionName, `Erfolg gemeldet, Antwort aber unlesbar (${grund}). Der Vorgang kann ausgefuehrt sein: nicht wiederholen, sondern nachlesen.`, {}, 'response_unreadable');
|
|
452
|
+
}
|
|
453
|
+
}
|
|
@@ -315,10 +315,17 @@ export interface SendReceiptEmailOptions {
|
|
|
315
315
|
}
|
|
316
316
|
/** Was der Versand bestaetigt (Backend: beleg-mail-endpoints.js). */
|
|
317
317
|
export interface SendReceiptEmailResult {
|
|
318
|
-
/**
|
|
318
|
+
/**
|
|
319
|
+
* Adresse in der Form, in der das Backend sie protokolliert hat (getrimmt,
|
|
320
|
+
* klein). Nennt die Antwort keine, die gesendete (getrimmte) Adresse.
|
|
321
|
+
*/
|
|
319
322
|
to: string;
|
|
320
|
-
/**
|
|
321
|
-
|
|
323
|
+
/**
|
|
324
|
+
* Zeitpunkt des Versands, ISO mit Wiener Zonenoffset
|
|
325
|
+
* (`2026-09-11T14:05:00+02:00`). `null`, wenn die Antwort keinen nennt: der
|
|
326
|
+
* Versand ist trotzdem bestaetigt.
|
|
327
|
+
*/
|
|
328
|
+
at: string | null;
|
|
322
329
|
/**
|
|
323
330
|
* Versandweg: `own` (Postfach des Betriebs), `platform` oder
|
|
324
331
|
* `platform_fallback` ([RECEIPT_EMAIL_VIAS]). `null`, wenn die Antwort ihn
|
|
@@ -58,12 +58,12 @@ function receiptLayoutFromResult(result, options = {}) {
|
|
|
58
58
|
*/
|
|
59
59
|
async function createReceipt(rufen, options) {
|
|
60
60
|
const daten = await rufen('createReceipt', createReceiptParams(options));
|
|
61
|
-
return signiertGelesen('createReceipt', () => belegAusHuelle(daten, 'createReceipt'));
|
|
61
|
+
return (0, errors_js_1.signiertGelesen)('createReceipt', () => belegAusHuelle(daten, 'createReceipt'));
|
|
62
62
|
}
|
|
63
63
|
/** Wie [createReceipt], liest aus derselben Antwort zusaetzlich die Firmendaten. */
|
|
64
64
|
async function createReceiptWithCompany(rufen, options) {
|
|
65
65
|
const daten = await rufen('createReceipt', createReceiptParams(options));
|
|
66
|
-
return signiertGelesen('createReceipt', () => belegMitFirmaAusHuelle(daten, 'createReceipt'));
|
|
66
|
+
return (0, errors_js_1.signiertGelesen)('createReceipt', () => belegMitFirmaAusHuelle(daten, 'createReceipt'));
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
69
|
* Baut die Nutzlast von `createReceipt` und prueft die Eingabe. Wirft, bevor
|
|
@@ -199,7 +199,7 @@ async function cancelReceipt(transport, options) {
|
|
|
199
199
|
if (zahlungen !== undefined)
|
|
200
200
|
params.payments = zahlungen;
|
|
201
201
|
const daten = await transport('cancelReceipt', params);
|
|
202
|
-
return signiertGelesen('cancelReceipt', () => stornoAusHuelle(daten));
|
|
202
|
+
return (0, errors_js_1.signiertGelesen)('cancelReceipt', () => stornoAusHuelle(daten));
|
|
203
203
|
}
|
|
204
204
|
/** Liest die Storno-Antwort `{ receipt, cancellationOf, remaining }`. */
|
|
205
205
|
function stornoAusHuelle(daten) {
|
|
@@ -395,14 +395,17 @@ async function sendReceiptEmail(transport, options) {
|
|
|
395
395
|
if (options.language !== undefined && options.language !== '')
|
|
396
396
|
params.language = options.language;
|
|
397
397
|
const daten = await transport('sendReceiptEmail', params);
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
398
|
+
// Nachsichtig gelesen (wie der Dart-Zwilling): eine Erfolgsantwort heisst,
|
|
399
|
+
// die Mail ist schon verschickt. Ein Fehler wegen eines fehlenden
|
|
400
|
+
// Antwortfelds luede zum zweiten Versand ein; also zurueck, was da ist.
|
|
401
|
+
const gemeldet = daten?.to;
|
|
402
|
+
const zeit = daten?.at;
|
|
403
|
+
const via = index_js_2.RECEIPT_EMAIL_VIAS.includes(daten?.via) ? daten.via : null;
|
|
404
|
+
return {
|
|
405
|
+
to: typeof gemeldet === 'string' && gemeldet.trim() !== '' ? gemeldet : to,
|
|
406
|
+
at: typeof zeit === 'string' && zeit !== '' ? zeit : null,
|
|
407
|
+
via,
|
|
408
|
+
};
|
|
406
409
|
}
|
|
407
410
|
/**
|
|
408
411
|
* Prueft die Gutschein-Kombination eines Belegs und liefert den ersten
|
|
@@ -685,24 +688,6 @@ function kartenanbieter(wert) {
|
|
|
685
688
|
function eingabefehler(grund) {
|
|
686
689
|
return new errors_js_1.KasseneckValidationError('createReceipt', grund, 'request');
|
|
687
690
|
}
|
|
688
|
-
/**
|
|
689
|
-
* Liest die Erfolgsantwort eines **signierenden** Aufrufs. Scheitert das
|
|
690
|
-
* Lesen (fehlender Beleg, fehlender Bezug, unbrauchbares Feld oder ein
|
|
691
|
-
* Laufzeitfehler beim Umwandeln), hat der Server trotzdem Erfolg gemeldet:
|
|
692
|
-
* der Beleg ist signiert und im DEP. Das darf nie als gewoehnlicher Fehler
|
|
693
|
-
* enden, sonst kassiert die Kasse ein zweites Mal. Darum wird daraus
|
|
694
|
-
* `KasseneckApiError` mit Code `response_unreadable` und `outcome: 'unknown'`.
|
|
695
|
-
* Der Grund stammt vom Paket; aus der Antwort selbst wird nichts uebernommen.
|
|
696
|
-
*/
|
|
697
|
-
function signiertGelesen(functionName, lesen) {
|
|
698
|
-
try {
|
|
699
|
-
return lesen();
|
|
700
|
-
}
|
|
701
|
-
catch (ursache) {
|
|
702
|
-
const grund = ursache instanceof errors_js_1.KasseneckValidationError ? ursache.reason : 'Antwort nicht lesbar';
|
|
703
|
-
throw new errors_js_1.KasseneckApiError(functionName, `Erfolg gemeldet, Antwort aber unlesbar (${grund}). Der Vorgang kann ausgefuehrt sein: nicht wiederholen, sondern nachlesen.`, {}, 'response_unreadable');
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
691
|
/** Die Antwort meldete Erfolg, trug aber nicht, was der Aufruf zusagt. */
|
|
707
692
|
function antwortfehler(functionName, grund) {
|
|
708
693
|
return new errors_js_1.KasseneckValidationError(functionName, grund, 'response');
|
|
@@ -62,10 +62,22 @@ const KASSENECK_HOSTS = new Set(['api.kasseneck.at', 'kasse.kasseneck.at']);
|
|
|
62
62
|
/** Die 1.x-Linie spricht nur `/v3`: jede Basis endet so (`/v3` oder `/api/v3`). */
|
|
63
63
|
const V3_ENDE = /\/v3$/;
|
|
64
64
|
/**
|
|
65
|
-
* Aufrufe, die
|
|
66
|
-
*
|
|
65
|
+
* Aufrufe mit Wirkung, die nie blind wiederholt werden duerfen: sie signieren
|
|
66
|
+
* (`createReceipt`, `cancelReceipt`), loesen bei FinanzOnline etwas aus
|
|
67
|
+
* (`financeWebService`) oder bewegen Geld (`hobexPayApi` belastet eine Karte,
|
|
68
|
+
* `hobexRefundApi` erstattet, `stripeCaptureIntent` zieht eine vorgemerkte
|
|
69
|
+
* Zahlung ein). Scheitert einer, nachdem die Anfrage unterwegs war (Netz,
|
|
70
|
+
* Zeitlimit, HTTP 5xx, unlesbare Erfolgsantwort, HTML mit Kennzeichen), ist
|
|
71
|
+
* sein Ausgang offen.
|
|
67
72
|
*/
|
|
68
|
-
const
|
|
73
|
+
const UNKNOWN_OUTCOME_CALLS = new Set([
|
|
74
|
+
'createReceipt',
|
|
75
|
+
'cancelReceipt',
|
|
76
|
+
'financeWebService',
|
|
77
|
+
'hobexPayApi',
|
|
78
|
+
'hobexRefundApi',
|
|
79
|
+
'stripeCaptureIntent',
|
|
80
|
+
]);
|
|
69
81
|
/**
|
|
70
82
|
* Produkte, die das Backend in `Kasseneck-Client` zaehlt (Positivliste,
|
|
71
83
|
* Nachtrag §6); alles andere zaehlte dort als `ungueltig`. Das Paket weist
|
|
@@ -162,9 +174,9 @@ function createCore(options) {
|
|
|
162
174
|
return ursache;
|
|
163
175
|
}
|
|
164
176
|
// Bis hierher kam keine verwertbare Antwort: Netz weg oder Zeitlimit.
|
|
165
|
-
// Die Anfrage war schon unterwegs: bei einem
|
|
166
|
-
// der Beleg entstanden sein (Ausgang unklar, nachlesen).
|
|
167
|
-
return new errors_js_1.KasseneckNetworkError(fehlerName, abbruch.signal.aborted, zeitlimitMs, (0, errors_js_1.causeDigest)(ursache, geheimnisse),
|
|
177
|
+
// Die Anfrage war schon unterwegs: bei einem Aufruf mit Wirkung kann
|
|
178
|
+
// der Beleg bzw. die Zahlung entstanden sein (Ausgang unklar, nachlesen).
|
|
179
|
+
return new errors_js_1.KasseneckNetworkError(fehlerName, abbruch.signal.aborted, zeitlimitMs, (0, errors_js_1.causeDigest)(ursache, geheimnisse), UNKNOWN_OUTCOME_CALLS.has(functionName) ? 'unknown' : 'rejected');
|
|
168
180
|
};
|
|
169
181
|
const basis = basisFuer(functionName);
|
|
170
182
|
const url = `${basis}/${encodeURIComponent(functionName)}`;
|
|
@@ -217,18 +229,18 @@ function createCore(options) {
|
|
|
217
229
|
if (fehler)
|
|
218
230
|
throw fehler;
|
|
219
231
|
}
|
|
220
|
-
// 5xx auf einem
|
|
221
|
-
const ausgang = antwort.status >= 500 &&
|
|
232
|
+
// 5xx auf einem Aufruf mit Wirkung: der Handler kann gelaufen sein.
|
|
233
|
+
const ausgang = antwort.status >= 500 && UNKNOWN_OUTCOME_CALLS.has(functionName) ? 'unknown' : 'rejected';
|
|
222
234
|
throw new errors_js_1.KasseneckHttpError(fehlerName, antwort.status, inhaltstyp, 'server-error', ausgang);
|
|
223
235
|
}
|
|
224
236
|
// HTTP 200 mit HTML: die Auffangregel der Single-Page-App hat den Aufruf
|
|
225
237
|
// bedient, keine Function hat ihn gesehen (Nachtrag §5.4, R15).
|
|
226
238
|
if (inhaltstyp !== undefined && /^\s*text\/html\b/i.test(inhaltstyp)) {
|
|
227
239
|
// Mit Kennzeichen hat der `/v3`-Rand den Aufruf gesehen (etwa ein
|
|
228
|
-
// Proxy, der nur den Inhaltstyp umschreibt). Bei einem
|
|
229
|
-
//
|
|
240
|
+
// Proxy, der nur den Inhaltstyp umschreibt). Bei einem Aufruf mit
|
|
241
|
+
// Wirkung kann der Beleg bzw. die Zahlung dann entstanden sein: unlesbarer Rumpf,
|
|
230
242
|
// Ausgang unklar, statt `route_missing`.
|
|
231
|
-
if (
|
|
243
|
+
if (UNKNOWN_OUTCOME_CALLS.has(functionName) && traegtKennzeichen(antwort)) {
|
|
232
244
|
throw new errors_js_1.KasseneckHttpError(fehlerName, antwort.status, inhaltstyp, 'not-json', 'unknown');
|
|
233
245
|
}
|
|
234
246
|
throw new errors_js_1.KasseneckApiError(fehlerName, 'Route fehlt: die Antwort ist eine HTML-Seite statt des Backends', {}, 'route_missing');
|
|
@@ -245,8 +257,8 @@ function createCore(options) {
|
|
|
245
257
|
}
|
|
246
258
|
// Ab hier kam HTTP 200 mit Kennzeichen: der `/v3`-Rand hat den Aufruf
|
|
247
259
|
// gesehen. Ist der Rumpf dann unlesbar (gekuerzt von einem Proxy,
|
|
248
|
-
// abgebrochene Verbindung), kann ein
|
|
249
|
-
const unlesbar =
|
|
260
|
+
// abgebrochene Verbindung), kann ein Handler mit Wirkung gelaufen sein.
|
|
261
|
+
const unlesbar = UNKNOWN_OUTCOME_CALLS.has(functionName) ? 'unknown' : 'rejected';
|
|
250
262
|
return auswerten(koerper, fehlerName, antwort.status, inhaltstyp, geheimnisse, unlesbar);
|
|
251
263
|
}
|
|
252
264
|
finally {
|
|
@@ -45,11 +45,11 @@ export interface Cashregister {
|
|
|
45
45
|
*/
|
|
46
46
|
export interface CashregisterOnboarding {
|
|
47
47
|
cashboxRegistered: boolean;
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
startReceiptCreated: boolean;
|
|
49
|
+
startReceiptTransmitted: boolean;
|
|
50
50
|
cashboxRegisteredAt?: Date;
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
startReceiptCreatedAt?: Date;
|
|
52
|
+
startReceiptTransmittedAt?: Date;
|
|
53
53
|
}
|
|
54
54
|
/** Nutzlast-Form, die dieses Paket liest — die Feldnamen von `listMyCashregisters`. */
|
|
55
55
|
export interface CashregisterPayload {
|
|
@@ -61,13 +61,19 @@ export interface CashregisterPayload {
|
|
|
61
61
|
token?: string | null;
|
|
62
62
|
onboarding?: CashregisterOnboardingPayload | null;
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* `onboarding` am Draht `/v3` (fixtures/v3/antworten/kasse.json): der
|
|
66
|
+
* Startbeleg heisst dort `start_receipt_*`. Die inneren Namen
|
|
67
|
+
* `startbeleg_*` sendet nur `/v1`; wer sie hier laese, meldete jede Kasse
|
|
68
|
+
* als „Startbeleg fehlt“.
|
|
69
|
+
*/
|
|
64
70
|
export interface CashregisterOnboardingPayload {
|
|
65
71
|
cashbox_registered?: boolean | null;
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
start_receipt_created?: boolean | null;
|
|
73
|
+
start_receipt_transmitted?: boolean | null;
|
|
68
74
|
cashbox_registered_at?: string | null;
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
start_receipt_created_at?: string | null;
|
|
76
|
+
start_receipt_transmitted_at?: string | null;
|
|
71
77
|
}
|
|
72
78
|
/**
|
|
73
79
|
* Liest eine Kasse aus der Antwort. `id` gewinnt aus der Nutzlast, faellt aber
|
|
@@ -18,11 +18,11 @@ function fromCashregisterPayload(payload, id) {
|
|
|
18
18
|
...(payload.signature_id ? { signatureId: payload.signature_id } : {}),
|
|
19
19
|
onboarding: {
|
|
20
20
|
cashboxRegistered: ob.cashbox_registered === true,
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
startReceiptCreated: ob.start_receipt_created === true,
|
|
22
|
+
startReceiptTransmitted: ob.start_receipt_transmitted === true,
|
|
23
23
|
...zeitfeld('cashboxRegisteredAt', ob.cashbox_registered_at),
|
|
24
|
-
...zeitfeld('
|
|
25
|
-
...zeitfeld('
|
|
24
|
+
...zeitfeld('startReceiptCreatedAt', ob.start_receipt_created_at),
|
|
25
|
+
...zeitfeld('startReceiptTransmittedAt', ob.start_receipt_transmitted_at),
|
|
26
26
|
},
|
|
27
27
|
};
|
|
28
28
|
}
|
|
@@ -45,6 +45,10 @@ export interface HobexTransactionIdOptions {
|
|
|
45
45
|
*
|
|
46
46
|
* **Der Endpunkt heisst `hobexPayApi`** — mit Suffix; ohne ihn gibt es ihn
|
|
47
47
|
* nicht.
|
|
48
|
+
*
|
|
49
|
+
* **Nie wiederholen.** Bei `isOutcomeUnknown(e)` (Netz, Zeitlimit, HTTP 5xx,
|
|
50
|
+
* unlesbare Antwort) kann die Karte belastet sein: den Stand ueber die
|
|
51
|
+
* Transaktionskennung nachlesen, nicht ein zweites Mal belasten.
|
|
48
52
|
*/
|
|
49
53
|
export declare function hobexPay(transport: InternerTransport, options: HobexPayOptions): Promise<HobexReceipt>;
|
|
50
54
|
/**
|
|
@@ -57,7 +61,11 @@ export declare function hobexPay(transport: InternerTransport, options: HobexPay
|
|
|
57
61
|
* schon im Transport, sobald der Status nicht ausdruecklich Erfolg ist. Ein
|
|
58
62
|
* Wahrheitswert waere hier also immer `true` — eine Luege ueber den
|
|
59
63
|
* Informationsgehalt, an der ein Aufrufer ein `if` aufhaengt, das nie greift.
|
|
60
|
-
* Misserfolg kommt als geworfener Fehler
|
|
64
|
+
* Misserfolg kommt als geworfener Fehler: eine Fehlerhuelle als
|
|
65
|
+
* [KasseneckApiError] mit Code, Netz, Zeitlimit, HTTP 5xx und eine unlesbare
|
|
66
|
+
* Erfolgsantwort mit `outcome: 'unknown'` (die Erstattung kann gelaufen sein).
|
|
67
|
+
* Ein `false` an dieser Stelle luede zum zweiten Versuch ein, also zur
|
|
68
|
+
* doppelten Erstattung. **Nie wiederholen**, sondern den Stand nachlesen.
|
|
61
69
|
*
|
|
62
70
|
* **Der Endpunkt heisst `hobexRefundApi`** — mit Suffix.
|
|
63
71
|
*/
|
|
@@ -41,6 +41,10 @@ const ENDPUNKT_REFUND = 'hobexRefundApi';
|
|
|
41
41
|
*
|
|
42
42
|
* **Der Endpunkt heisst `hobexPayApi`** — mit Suffix; ohne ihn gibt es ihn
|
|
43
43
|
* nicht.
|
|
44
|
+
*
|
|
45
|
+
* **Nie wiederholen.** Bei `isOutcomeUnknown(e)` (Netz, Zeitlimit, HTTP 5xx,
|
|
46
|
+
* unlesbare Antwort) kann die Karte belastet sein: den Stand ueber die
|
|
47
|
+
* Transaktionskennung nachlesen, nicht ein zweites Mal belasten.
|
|
44
48
|
*/
|
|
45
49
|
async function hobexPay(transport, options) {
|
|
46
50
|
const params = zahlungsNutzlast(ENDPUNKT_PAY, options);
|
|
@@ -48,7 +52,11 @@ async function hobexPay(transport, options) {
|
|
|
48
52
|
// geht sie als null raus, nicht gar nicht. Der Transport wirft nur
|
|
49
53
|
// `undefined` weg, `null` bleibt erhalten — genau diese Unterscheidung.
|
|
50
54
|
params['reference'] = options.reference ?? null;
|
|
51
|
-
|
|
55
|
+
const daten = await transport(ENDPUNKT_PAY, params);
|
|
56
|
+
// Erfolg gemeldet heisst: die Karte ist belastet. Ist der Beleg dann
|
|
57
|
+
// unlesbar, bleibt der Ausgang unklar (`response_unreadable`), damit keine
|
|
58
|
+
// App ein zweites Mal belastet.
|
|
59
|
+
return (0, errors_js_1.signiertGelesen)(ENDPUNKT_PAY, () => belegAusNutzlast(daten));
|
|
52
60
|
}
|
|
53
61
|
/**
|
|
54
62
|
* Erstattet eine zuvor ueber die Hobex-Cloud getaetigte Zahlung. Das Backend
|
|
@@ -60,7 +68,11 @@ async function hobexPay(transport, options) {
|
|
|
60
68
|
* schon im Transport, sobald der Status nicht ausdruecklich Erfolg ist. Ein
|
|
61
69
|
* Wahrheitswert waere hier also immer `true` — eine Luege ueber den
|
|
62
70
|
* Informationsgehalt, an der ein Aufrufer ein `if` aufhaengt, das nie greift.
|
|
63
|
-
* Misserfolg kommt als geworfener Fehler
|
|
71
|
+
* Misserfolg kommt als geworfener Fehler: eine Fehlerhuelle als
|
|
72
|
+
* [KasseneckApiError] mit Code, Netz, Zeitlimit, HTTP 5xx und eine unlesbare
|
|
73
|
+
* Erfolgsantwort mit `outcome: 'unknown'` (die Erstattung kann gelaufen sein).
|
|
74
|
+
* Ein `false` an dieser Stelle luede zum zweiten Versuch ein, also zur
|
|
75
|
+
* doppelten Erstattung. **Nie wiederholen**, sondern den Stand nachlesen.
|
|
64
76
|
*
|
|
65
77
|
* **Der Endpunkt heisst `hobexRefundApi`** — mit Suffix.
|
|
66
78
|
*/
|
|
@@ -57,5 +57,8 @@ export declare function createStripeLink(transport: InternerTransport, options:
|
|
|
57
57
|
* **Der Parameter heisst `stripe_sessions_id`** — mit "sessions" im Plural, so
|
|
58
58
|
* das Vorbild und so das Backend. Der naheliegende Singular waere ein fehlender
|
|
59
59
|
* Pflichtparameter.
|
|
60
|
+
*
|
|
61
|
+
* **Nie wiederholen.** Bei `isOutcomeUnknown(e)` (Netz, Zeitlimit, HTTP 5xx,
|
|
62
|
+
* unlesbare Antwort) kann der Einzug gelaufen sein: den Stand nachlesen.
|
|
60
63
|
*/
|
|
61
64
|
export declare function stripeCaptureIntent(transport: InternerTransport, stripeSessionId: string): Promise<StripeCaptureResult>;
|