@kreiseck/kasseneck-api 0.18.0 → 0.20.0
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 +58 -0
- package/README.md +17 -4
- package/dist/cjs/payments/hobex-hps/index.d.ts +1 -1
- package/dist/cjs/payments/hobex-hps/index.js +3 -1
- package/dist/cjs/payments/hobex-hps/payments.d.ts +11 -0
- package/dist/cjs/payments/hobex-hps/payments.js +73 -4
- package/dist/cjs/payments/hobex-hps/tecs-codes.d.ts +16 -0
- package/dist/cjs/payments/hobex-hps/tecs-codes.js +366 -0
- package/dist/cjs/payments/hobex-hps/transaction-response.d.ts +56 -8
- package/dist/cjs/payments/hobex-hps/transaction-response.js +135 -65
- package/dist/cjs/rechnung/texte.d.ts +5 -4
- package/dist/cjs/rechnung/texte.js +16 -8
- package/dist/cjs/rechnung/typen.d.ts +7 -0
- package/dist/cjs/rechnung/vertrag.d.ts +7 -4
- package/dist/cjs/rechnung/vertrag.js +14 -4
- package/dist/esm/payments/hobex-hps/index.d.ts +1 -1
- package/dist/esm/payments/hobex-hps/index.js +1 -1
- package/dist/esm/payments/hobex-hps/payments.d.ts +11 -0
- package/dist/esm/payments/hobex-hps/payments.js +74 -5
- package/dist/esm/payments/hobex-hps/tecs-codes.d.ts +16 -0
- package/dist/esm/payments/hobex-hps/tecs-codes.js +363 -0
- package/dist/esm/payments/hobex-hps/transaction-response.d.ts +56 -8
- package/dist/esm/payments/hobex-hps/transaction-response.js +133 -65
- package/dist/esm/rechnung/texte.d.ts +5 -4
- package/dist/esm/rechnung/texte.js +16 -8
- package/dist/esm/rechnung/typen.d.ts +7 -0
- package/dist/esm/rechnung/vertrag.d.ts +7 -4
- package/dist/esm/rechnung/vertrag.js +14 -4
- package/fixtures/hobex-hps-codes.json +4075 -52
- package/fixtures/kasse-texte.json +1 -1
- package/fixtures/oberflaeche.json +1 -1
- package/fixtures/rechnung-api-beispiele/zahlung-fehler-vor-ort.json +15 -0
- package/fixtures/rechnung-api.schema.json +7 -1
- package/fixtures/rechnung-texte.json +11 -9
- 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
|
+
## 0.20.0
|
|
8
|
+
|
|
9
|
+
### Rechnung: richtige Steuerhinweise, Barumsatz auch mit Karte
|
|
10
|
+
|
|
11
|
+
**Anlass:** drei Befunde aus einer Durchsicht an den Primärquellen.
|
|
12
|
+
|
|
13
|
+
- **Reverse Charge ist keine Steuerbefreiung.** Der Aufdruck hiess
|
|
14
|
+
„Steuerfreie Leistung – Reverse Charge" und behauptete damit eine Befreiung,
|
|
15
|
+
die es nicht gibt: der Umsatz bleibt steuerpflichtig, nur die Steuer schuldet
|
|
16
|
+
der Empfänger. Das Gesetz trennt beides (§ 11 Abs. 1 Z 3 lit. e = Hinweis auf
|
|
17
|
+
eine Befreiung, § 11 Abs. 1a = Hinweis auf die Steuerschuldnerschaft); Art. 226
|
|
18
|
+
Nr. 11a MwSt-RL nennt den Begriff wörtlich. Jetzt:
|
|
19
|
+
„Steuerschuldnerschaft des Leistungsempfängers."
|
|
20
|
+
- **Ig. Lieferung** bekommt denselben Kasten wie Reverse Charge, mit **beiden**
|
|
21
|
+
UID-Nummern — Art. 11 Abs. 2 UStG verlangt sie auf der Rechnung. Der
|
|
22
|
+
Textschlüssel `steuer.reverseCharge.uid` heisst darum jetzt `steuer.uidZeile`,
|
|
23
|
+
neu sind `steuer.igLieferung.titel` und `steuer.igLieferung.text`
|
|
24
|
+
(`steuer.igLieferung` entfällt).
|
|
25
|
+
- **`onSite` an der Zahlung.** Ein Barumsatz ist nicht nur Bargeld: als
|
|
26
|
+
Barzahlung gilt auch die Karte **vor Ort** (§ 131b Abs. 1 Z 3 UStG), nicht
|
|
27
|
+
aber dieselbe Karte im Internet. Weil `card`/`online` beides sein können,
|
|
28
|
+
sagt es das Fremdsystem jetzt selbst — der Hinweis `cash_receipt_required`
|
|
29
|
+
kommt bei `cash` immer und sonst nur mit `onSite: true`. `transfer` mit
|
|
30
|
+
`onSite` wird abgewiesen.
|
|
31
|
+
|
|
32
|
+
## 0.19.0
|
|
33
|
+
|
|
34
|
+
### hobex HPS: TECS-Antwortcodes eingeordnet, Storno nach ausbleibender Host-Antwort
|
|
35
|
+
|
|
36
|
+
**Anlass:** hobex hat am 16.09.2026 bestätigt, dass das HPS die Codes der
|
|
37
|
+
TECS-Plattform durchreicht, und deren Liste geschickt. Zu `9908` (im Betrieb am
|
|
38
|
+
11.09.2026 gesehen, bisher unbekannt und damit offen): „ein Timeout wie jeder
|
|
39
|
+
andere. Richtigerweise sollte in dem Fall ein Storno nachgeschickt werden.“
|
|
40
|
+
|
|
41
|
+
- `HPS_CODES` führt jetzt 365 Codes; die TECS-Liste steht in
|
|
42
|
+
`src/payments/hobex-hps/tecs-codes.ts`, eine Zeile je Code.
|
|
43
|
+
- `sendReversal` je Code und `needsReversal()`: bei ausbleibender oder
|
|
44
|
+
unbrauchbarer Host-Antwort schickt `pay` genau einmal ein Storno
|
|
45
|
+
(`/v1/terminal/cancel`) nach. Quittiert → `declined` mit
|
|
46
|
+
`'voidedAfterHostFault'`; sonst Klärung wie bisher, ein späteres `9011`
|
|
47
|
+
zählt ebenfalls als storniert. Gutschriften bekommen kein Storno (TECS:
|
|
48
|
+
`9031`).
|
|
49
|
+
- `tecsTitle` je Code: der Titel in der TECS-Liste.
|
|
50
|
+
- `normalizeHpsCode()`: `0055` und `55` sind derselbe Code; `responseCode`
|
|
51
|
+
kommt normalisiert an (`0000` → `0`), `raw` bleibt unverändert. Familien mit
|
|
52
|
+
Platzhalter (`81xx`) werden gefunden.
|
|
53
|
+
- 16 neue Gründe in `HpsCodeReason` und `HPS_REASON_HINTS`, darunter
|
|
54
|
+
`issuerDeclined`, `insufficientFunds`, `cardExpired`, `hostTimeout`,
|
|
55
|
+
`approvedWithCondition`. **Kassen mit eigener Übersetzung brauchen dafür
|
|
56
|
+
Texte.**
|
|
57
|
+
- Eine Genehmigung, die nicht `0` ist (`8`, `10`, `11`, `16`, `32`), bleibt
|
|
58
|
+
offen und wird nie zur Ablehnung.
|
|
59
|
+
- `9900` ist jetzt `hostUncertain` (`internalError`) statt `noStatement`:
|
|
60
|
+
gemessen kam er nach verarbeiteter Karte, und die Zwei-9027-Regel hätte
|
|
61
|
+
daraus „nichts belastet“ gemacht.
|
|
62
|
+
- `fixtures/hobex-hps-codes.json` führt je Code zusätzlich `sendReversal` und
|
|
63
|
+
`tecsTitle`.
|
|
64
|
+
|
|
7
65
|
## 0.18.0
|
|
8
66
|
|
|
9
67
|
### Rechnungs-API: bezahlte Rechnung und Zahlungen nachtragen
|
package/README.md
CHANGED
|
@@ -545,10 +545,23 @@ await rechnungen.recordInvoicePayment({
|
|
|
545
545
|
```
|
|
546
546
|
|
|
547
547
|
`reference` wird gespeichert, aber **nicht gedruckt**; Kartendaten gehören
|
|
548
|
-
ohnehin nicht auf eine Rechnung.
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
548
|
+
ohnehin nicht auf eine Rechnung.
|
|
549
|
+
|
|
550
|
+
**Barumsatz ist nicht nur Bargeld.** Als Barzahlung gilt auch die Karte **vor
|
|
551
|
+
Ort** an der Kasse (§ 131b Abs. 1 Z 3 UStG) — dieselbe Karte im Internet
|
|
552
|
+
dagegen nicht. Weil `card` und `online` beides sein können, sagt es das
|
|
553
|
+
Fremdsystem selbst:
|
|
554
|
+
|
|
555
|
+
```ts
|
|
556
|
+
payment: { method: 'card', onSite: true } // Terminal an der Kasse
|
|
557
|
+
payment: { method: 'card' } // Kartenzahlung im Shop
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
Bei `cash` (immer) und bei `onSite: true` trägt die Antwort
|
|
561
|
+
`notice.code = 'cash_receipt_required'`: ein Barumsatz braucht einen Beleg
|
|
562
|
+
(§ 132a BAO), bei Registrierkassenpflicht über die Registrierkasse — der
|
|
563
|
+
Vermerk an der Rechnung ersetzt ihn nicht. `transfer` mit `onSite` ist ein
|
|
564
|
+
Feldfehler, eine Überweisung erfolgt nicht vor Ort.
|
|
552
565
|
|
|
553
566
|
**Nach einem Zeitlimit mit demselben `idempotencyKey` wiederholen**, nie mit
|
|
554
567
|
einem neuen: dann kommt die schon ausgestellte Rechnung zurück
|
|
@@ -23,5 +23,5 @@ export { type HpsPaymentEvent, type HpsPaymentEventKind, type HpsPaymentObserver
|
|
|
23
23
|
export { type CardPaymentOutcome, type HpsPaymentResult, isHostUncertainResult, mayRetrySafely, } from './outcome.js';
|
|
24
24
|
export { type HpsCancelOptions, type HpsPaymentOptions, type HpsPayments, type HpsPaymentsOptions, type HpsRefundOptions, createHpsPayments, } from './payments.js';
|
|
25
25
|
export { MAX_TRANSACTION_ID_LENGTH, createHpsTransactionIdGenerator, isValidHpsTransactionId, newHpsTransactionId, type HpsTransactionIdGeneratorOptions, } from './transaction-id.js';
|
|
26
|
-
export { ABORTED_CODE, APP_SELECT_FAILED_CODE, APPROVED_CODE, BAD_REQUEST_CODE, CARD_DECLINED_CODE, CARD_INFO_NOT_ENTERED_CODE, CARD_NOT_PRESENT_CODE, CARD_NOT_SUPPORTED_CODE, CARD_READ_FAILED_CODE, CHIP_DATA_MISMATCH_CODE, COMPLETION_FAILED_CODE, DIAGNOSIS_FAILED_CODE, HOST_COMMUNICATION_FAILED_CODE, HOST_STEP_FAILED_CODE, HOST_TIMEOUT_REVERSED_CODE, HPS_CODES, HPS_MEASURED_CODES, HPS_REASON_HINTS, INTERNAL_ERROR_CODE, INVALID_MESSAGE_TYPE_CODE, INVALID_TID_DOCUMENTED_CODE, INVALID_TX_TYPE_CODE, MAX_RETRIES_EXCEEDED_CODE, NOT_FOUND_CODE, PASSWORD_NOT_ENTERED_CODE, REFUND_DISABLED_CODE, REFUND_PASSWORD_INVALID_CODE, SCEP_ENROLLMENT_FAILED_CODE, TERMINAL_BLOCKED_CODE, TERMINAL_BUSY_CODE, TIP_SELECTION_FAILED_CODE, UNSUPPORTED_USER_DATA_CODE, hpsCodeInfo, hpsCodeReason, isHostUncertain, isHostUncertainReason, INVALID_TRANSACTION_CODE, NOT_ABORTABLE_CODE, INVALID_AMOUNT_CODE, AMOUNT_OUT_OF_RANGE_CODE, INVALID_TID_CODE, WRONG_PIN_CODE, NO_STATEMENT_CODE, TECHNICAL_ERROR_CODE, NOT_FOUND_HTTP_STATUS, TERMINAL_BUSY_HTTP_STATUS, TRANSACTION_CANCELED_CODE, isApproved, isCanceled, isConclusive, isConclusiveAsStatus, isInProgress, isNoStatement, isNotAbortable, isTechnicalError, isUnknownCode, parseHpsTransactionResponse, type HpsCode, type HpsCodeEffect, type HpsCodeReason, type HpsCodeSource, type HpsMeasuredCode, type HpsTransactionResponse, } from './transaction-response.js';
|
|
26
|
+
export { ABORTED_CODE, APP_SELECT_FAILED_CODE, APPROVED_CODE, BAD_REQUEST_CODE, CARD_DECLINED_CODE, CARD_INFO_NOT_ENTERED_CODE, CARD_NOT_PRESENT_CODE, CARD_NOT_SUPPORTED_CODE, CARD_READ_FAILED_CODE, CHIP_DATA_MISMATCH_CODE, COMPLETION_FAILED_CODE, DIAGNOSIS_FAILED_CODE, HOST_COMMUNICATION_FAILED_CODE, HOST_STEP_FAILED_CODE, HOST_TIMEOUT_REVERSED_CODE, HPS_CODES, HPS_MEASURED_CODES, HPS_REASON_HINTS, INTERNAL_ERROR_CODE, INVALID_MESSAGE_TYPE_CODE, INVALID_TID_DOCUMENTED_CODE, INVALID_TX_TYPE_CODE, MAX_RETRIES_EXCEEDED_CODE, NOT_FOUND_CODE, PASSWORD_NOT_ENTERED_CODE, REFUND_DISABLED_CODE, REFUND_PASSWORD_INVALID_CODE, SCEP_ENROLLMENT_FAILED_CODE, TERMINAL_BLOCKED_CODE, TERMINAL_BUSY_CODE, TIP_SELECTION_FAILED_CODE, UNSUPPORTED_USER_DATA_CODE, hpsCodeInfo, hpsCodeReason, isHostUncertain, isHostUncertainReason, INVALID_TRANSACTION_CODE, NOT_ABORTABLE_CODE, INVALID_AMOUNT_CODE, AMOUNT_OUT_OF_RANGE_CODE, INVALID_TID_CODE, WRONG_PIN_CODE, NO_STATEMENT_CODE, TECHNICAL_ERROR_CODE, NOT_FOUND_HTTP_STATUS, TERMINAL_BUSY_HTTP_STATUS, TRANSACTION_CANCELED_CODE, isApproved, isCanceled, isConclusive, isConclusiveAsStatus, isInProgress, isNoStatement, isNotAbortable, needsReversal, normalizeHpsCode, isTechnicalError, isUnknownCode, parseHpsTransactionResponse, type HpsCode, type HpsCodeEffect, type HpsCodeReason, type HpsCodeSource, type HpsMeasuredCode, type HpsTransactionResponse, } from './transaction-response.js';
|
|
27
27
|
export { hobexReceiptFromHps } from './receipt.js';
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
22
|
exports.isHostUncertain = exports.hpsCodeReason = exports.hpsCodeInfo = exports.UNSUPPORTED_USER_DATA_CODE = exports.TIP_SELECTION_FAILED_CODE = exports.TERMINAL_BUSY_CODE = exports.TERMINAL_BLOCKED_CODE = exports.SCEP_ENROLLMENT_FAILED_CODE = exports.REFUND_PASSWORD_INVALID_CODE = exports.REFUND_DISABLED_CODE = exports.PASSWORD_NOT_ENTERED_CODE = exports.NOT_FOUND_CODE = exports.MAX_RETRIES_EXCEEDED_CODE = exports.INVALID_TX_TYPE_CODE = exports.INVALID_TID_DOCUMENTED_CODE = exports.INVALID_MESSAGE_TYPE_CODE = exports.INTERNAL_ERROR_CODE = exports.HPS_REASON_HINTS = exports.HPS_MEASURED_CODES = exports.HPS_CODES = exports.HOST_TIMEOUT_REVERSED_CODE = exports.HOST_STEP_FAILED_CODE = exports.HOST_COMMUNICATION_FAILED_CODE = exports.DIAGNOSIS_FAILED_CODE = exports.COMPLETION_FAILED_CODE = exports.CHIP_DATA_MISMATCH_CODE = exports.CARD_READ_FAILED_CODE = exports.CARD_NOT_SUPPORTED_CODE = exports.CARD_NOT_PRESENT_CODE = exports.CARD_INFO_NOT_ENTERED_CODE = exports.CARD_DECLINED_CODE = exports.BAD_REQUEST_CODE = exports.APPROVED_CODE = exports.APP_SELECT_FAILED_CODE = exports.ABORTED_CODE = exports.newHpsTransactionId = exports.isValidHpsTransactionId = exports.createHpsTransactionIdGenerator = exports.MAX_TRANSACTION_ID_LENGTH = exports.createHpsPayments = exports.mayRetrySafely = exports.isHostUncertainResult = exports.PREFLIGHT_CONNECT_CODES = exports.HpsTransactionIdError = exports.HpsPreflightError = exports.HpsConnectTransportError = exports.HpsConnectTerminalError = exports.HpsConnectException = exports.HpsClarifyTimeoutError = exports.createHpsConnectClient = void 0;
|
|
23
|
-
exports.hobexReceiptFromHps = exports.parseHpsTransactionResponse = exports.isUnknownCode = exports.isTechnicalError = exports.isNotAbortable = exports.isNoStatement = exports.isInProgress = exports.isConclusiveAsStatus = exports.isConclusive = exports.isCanceled = exports.isApproved = exports.TRANSACTION_CANCELED_CODE = exports.TERMINAL_BUSY_HTTP_STATUS = exports.NOT_FOUND_HTTP_STATUS = exports.TECHNICAL_ERROR_CODE = exports.NO_STATEMENT_CODE = exports.WRONG_PIN_CODE = exports.INVALID_TID_CODE = exports.AMOUNT_OUT_OF_RANGE_CODE = exports.INVALID_AMOUNT_CODE = exports.NOT_ABORTABLE_CODE = exports.INVALID_TRANSACTION_CODE = exports.isHostUncertainReason = void 0;
|
|
23
|
+
exports.hobexReceiptFromHps = exports.parseHpsTransactionResponse = exports.isUnknownCode = exports.isTechnicalError = exports.normalizeHpsCode = exports.needsReversal = exports.isNotAbortable = exports.isNoStatement = exports.isInProgress = exports.isConclusiveAsStatus = exports.isConclusive = exports.isCanceled = exports.isApproved = exports.TRANSACTION_CANCELED_CODE = exports.TERMINAL_BUSY_HTTP_STATUS = exports.NOT_FOUND_HTTP_STATUS = exports.TECHNICAL_ERROR_CODE = exports.NO_STATEMENT_CODE = exports.WRONG_PIN_CODE = exports.INVALID_TID_CODE = exports.AMOUNT_OUT_OF_RANGE_CODE = exports.INVALID_AMOUNT_CODE = exports.NOT_ABORTABLE_CODE = exports.INVALID_TRANSACTION_CODE = exports.isHostUncertainReason = void 0;
|
|
24
24
|
var connect_client_js_1 = require("./connect-client.js");
|
|
25
25
|
Object.defineProperty(exports, "createHpsConnectClient", { enumerable: true, get: function () { return connect_client_js_1.createHpsConnectClient; } });
|
|
26
26
|
var errors_js_1 = require("./errors.js");
|
|
@@ -96,6 +96,8 @@ Object.defineProperty(exports, "isConclusiveAsStatus", { enumerable: true, get:
|
|
|
96
96
|
Object.defineProperty(exports, "isInProgress", { enumerable: true, get: function () { return transaction_response_js_1.isInProgress; } });
|
|
97
97
|
Object.defineProperty(exports, "isNoStatement", { enumerable: true, get: function () { return transaction_response_js_1.isNoStatement; } });
|
|
98
98
|
Object.defineProperty(exports, "isNotAbortable", { enumerable: true, get: function () { return transaction_response_js_1.isNotAbortable; } });
|
|
99
|
+
Object.defineProperty(exports, "needsReversal", { enumerable: true, get: function () { return transaction_response_js_1.needsReversal; } });
|
|
100
|
+
Object.defineProperty(exports, "normalizeHpsCode", { enumerable: true, get: function () { return transaction_response_js_1.normalizeHpsCode; } });
|
|
99
101
|
Object.defineProperty(exports, "isTechnicalError", { enumerable: true, get: function () { return transaction_response_js_1.isTechnicalError; } });
|
|
100
102
|
Object.defineProperty(exports, "isUnknownCode", { enumerable: true, get: function () { return transaction_response_js_1.isUnknownCode; } });
|
|
101
103
|
Object.defineProperty(exports, "parseHpsTransactionResponse", { enumerable: true, get: function () { return transaction_response_js_1.parseHpsTransactionResponse; } });
|
|
@@ -108,6 +108,17 @@ import type { HpsPaymentResult } from './outcome.js';
|
|
|
108
108
|
* solcher Code), endet sie sofort als `unresolved` -- das Terminal wird es
|
|
109
109
|
* auch in 90 Sekunden nicht wissen. Meldet sie dagegen `'0'`, ist die
|
|
110
110
|
* Zahlung genehmigt, ganz normal.
|
|
111
|
+
*
|
|
112
|
+
* ## Storno nach ausbleibender Host-Antwort (16.09.2026)
|
|
113
|
+
*
|
|
114
|
+
* Fuer einen Teil dieser Codes -- der Host hat nicht oder nicht brauchbar
|
|
115
|
+
* geantwortet, etwa `9908` -- gibt hobex vor, ein Storno nachzuschicken
|
|
116
|
+
* (`sendReversal`). [pay] tut das genau einmal, mit Kennung und Betrag der
|
|
117
|
+
* Zahlung, bevor die Statusabfrage laeuft: quittiert mit `'0'` -> `declined`
|
|
118
|
+
* mit `'voidedAfterHostFault'`; sonst weiter wie oben, und ein `9011` in der
|
|
119
|
+
* Statusabfrage heisst dann ebenfalls `declined`. Am Geraet ungemessen, siehe
|
|
120
|
+
* Dart-Zwilling. [refund] schickt kein Storno nach (TECS: `9031`), [cancel]
|
|
121
|
+
* ist selbst eines.
|
|
111
122
|
*/
|
|
112
123
|
export interface HpsPaymentsOptions {
|
|
113
124
|
/** Wie lange insgesamt geklaert wird, bevor der Ausgang offen bleibt. Vorgabe 90 s. */
|
|
@@ -6,6 +6,11 @@ const transaction_id_js_1 = require("./transaction-id.js");
|
|
|
6
6
|
const transaction_response_js_1 = require("./transaction-response.js");
|
|
7
7
|
/** Teiler, mit dem das Abbruchbudget aus [HpsPaymentsOptions.resolveBudgetMs] entsteht. */
|
|
8
8
|
const ABORT_BUDGET_DIVISOR = 6;
|
|
9
|
+
/**
|
|
10
|
+
* Teiler fuer das Budget des nachgeschickten Stornos: ein Drittel, bei der
|
|
11
|
+
* Vorgabe 30 s. Ein Storno geht ueber den Host und darf Sekunden brauchen.
|
|
12
|
+
*/
|
|
13
|
+
const REVERSAL_BUDGET_DIVISOR = 3;
|
|
9
14
|
function createHpsPayments(client, target, options = {}) {
|
|
10
15
|
const resolveBudgetMs = options.resolveBudgetMs ?? 90_000;
|
|
11
16
|
const maxBackoffMs = options.maxBackoffMs ?? 10_000;
|
|
@@ -14,6 +19,7 @@ function createHpsPayments(client, target, options = {}) {
|
|
|
14
19
|
const now = options.now ?? Date.now;
|
|
15
20
|
const observer = options.observer;
|
|
16
21
|
const abortBudgetMs = Math.trunc(resolveBudgetMs / ABORT_BUDGET_DIVISOR);
|
|
22
|
+
const reversalBudgetMs = Math.trunc(resolveBudgetMs / REVERSAL_BUDGET_DIVISOR);
|
|
17
23
|
function emit(kind, message, transactionId) {
|
|
18
24
|
if (!observer)
|
|
19
25
|
return;
|
|
@@ -70,8 +76,12 @@ function createHpsPayments(client, target, options = {}) {
|
|
|
70
76
|
const info = (0, transaction_response_js_1.hpsCodeInfo)(res.responseCode);
|
|
71
77
|
const was = info.reason === 'internalError'
|
|
72
78
|
? 'interner Fehler des Terminals'
|
|
73
|
-
:
|
|
74
|
-
|
|
79
|
+
: info.reason === 'approvedWithCondition'
|
|
80
|
+
? 'Genehmigung mit Vorbehalt'
|
|
81
|
+
: info.reason === 'hostTimeout'
|
|
82
|
+
? 'keine rechtzeitige Antwort des hobex-Hosts, das Terminal storniert nicht selbst'
|
|
83
|
+
: 'Stoerung zwischen Terminal und hobex-Host, das Terminal storniert nicht selbst';
|
|
84
|
+
return `Terminal meldet ${was} (${res.responseCode} "${info.title}")`;
|
|
75
85
|
}
|
|
76
86
|
/**
|
|
77
87
|
* Der Grund eines offenen Ausgangs. [antwort] ist die Antwort auf die
|
|
@@ -344,6 +354,37 @@ function createHpsPayments(client, target, options = {}) {
|
|
|
344
354
|
emit('resolved', steps[steps.length - 1], id);
|
|
345
355
|
return { outcome: 'declined', transactionId: id, response: res, reason: 'aborted', steps: [...steps] };
|
|
346
356
|
}
|
|
357
|
+
/**
|
|
358
|
+
* Schickt zu einer Zahlung, deren Host-Antwort ausblieb ([anlass] traegt
|
|
359
|
+
* `sendReversal`), GENAU EINMAL ein Storno nach. Ergebnis nur, wenn das
|
|
360
|
+
* Terminal es mit `'0'` quittiert; sonst `null`. Ohne [amountCents]
|
|
361
|
+
* (Gutschrift) wird nichts gesendet.
|
|
362
|
+
*/
|
|
363
|
+
async function sendReversal(id, steps, elapsedMs, amountCents, anlass) {
|
|
364
|
+
if (amountCents === undefined) {
|
|
365
|
+
steps.push('Kein Storno nachgeschickt -- die Aufhebung einer Gutschrift laesst TECS nicht zu');
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
emit('resolving', `Storno wird nachgeschickt (${anlass.responseCode})`, id);
|
|
369
|
+
let res;
|
|
370
|
+
try {
|
|
371
|
+
res = await withinBudget(elapsedMs, () => client.cancel({ ...target, transactionId: id, amountCents }), reversalBudgetMs);
|
|
372
|
+
}
|
|
373
|
+
catch (e) {
|
|
374
|
+
steps.push(`Storno nachgeschickt, aber nicht bestaetigt (${describe(e)}) -- ob es wirkte, ist offen, Ausgang wird abgefragt`);
|
|
375
|
+
noteUnexpected(e, id);
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
if (!(0, transaction_response_js_1.isApproved)(res)) {
|
|
379
|
+
const titel = (0, transaction_response_js_1.hpsCodeInfo)(res.responseCode)?.title;
|
|
380
|
+
steps.push(`Storno nachgeschickt, nicht bestaetigt (${res.responseCode ?? 'ohne Ergebniscode'}`
|
|
381
|
+
+ `${titel === undefined ? '' : ` "${titel}"`}) -- Ausgang wird abgefragt`);
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
steps.push('Storno nachgeschickt und bestaetigt -- die Zahlung ist aufgehoben, es ist nichts belastet');
|
|
385
|
+
emit('resolved', steps[steps.length - 1], id);
|
|
386
|
+
return { outcome: 'declined', transactionId: id, response: res, reason: 'voidedAfterHostFault', steps: [...steps] };
|
|
387
|
+
}
|
|
347
388
|
/**
|
|
348
389
|
* Klaert einen offenen Ausgang: erst abbrechen, dann abfragen -- bis das
|
|
349
390
|
* Terminal etwas sagt oder das Budget aufgebraucht ist.
|
|
@@ -356,7 +397,7 @@ function createHpsPayments(client, target, options = {}) {
|
|
|
356
397
|
* ([isHostUncertain]), bekommt `9027` diesen Aussagewert gerade NICHT --
|
|
357
398
|
* siehe Klassendoku, "Stoerung beim Host".
|
|
358
399
|
*/
|
|
359
|
-
async function resolve(id, steps, antwort) {
|
|
400
|
+
async function resolve(id, steps, antwort, stornoCents) {
|
|
360
401
|
emit('resolving', 'Ausgang offen, Klaerung laeuft', id);
|
|
361
402
|
const start = now();
|
|
362
403
|
const elapsedMs = () => now() - start;
|
|
@@ -365,6 +406,9 @@ function createHpsPayments(client, target, options = {}) {
|
|
|
365
406
|
// Statusabfrage sie nennt und danach 9027 kommt.
|
|
366
407
|
let stoerungsAntwort = antwort && (0, transaction_response_js_1.isHostUncertain)(antwort) ? antwort : undefined;
|
|
367
408
|
let letzteAntwort = antwort;
|
|
409
|
+
// Das Storno wird hoechstens einmal erwogen; gesendet ist es nur mit Betrag.
|
|
410
|
+
let stornoErwogen = false;
|
|
411
|
+
let stornoGesendet = false;
|
|
368
412
|
if (stoerungsAntwort === undefined) {
|
|
369
413
|
const aborted = await tryAbort(id, steps, elapsedMs);
|
|
370
414
|
if (aborted)
|
|
@@ -375,6 +419,13 @@ function createHpsPayments(client, target, options = {}) {
|
|
|
375
419
|
// Host. Ein quittierter Abbruch bewiese nur, dass am Terminal nichts mehr
|
|
376
420
|
// laeuft, nicht, dass der Host nichts belastet hat.
|
|
377
421
|
steps.push('Kein Abbruchversuch -- das Terminal hat den Vorgang mit einer Stoerung beim hobex-Host beendet');
|
|
422
|
+
if ((0, transaction_response_js_1.needsReversal)(stoerungsAntwort)) {
|
|
423
|
+
stornoErwogen = true;
|
|
424
|
+
stornoGesendet = stornoCents !== undefined;
|
|
425
|
+
const storniert = await sendReversal(id, steps, elapsedMs, stornoCents, stoerungsAntwort);
|
|
426
|
+
if (storniert)
|
|
427
|
+
return storniert;
|
|
428
|
+
}
|
|
378
429
|
}
|
|
379
430
|
let wait = 0;
|
|
380
431
|
let transportFailures = 0;
|
|
@@ -409,6 +460,24 @@ function createHpsPayments(client, target, options = {}) {
|
|
|
409
460
|
if ((0, transaction_response_js_1.isHostUncertain)(status))
|
|
410
461
|
stoerungsAntwort ??= status;
|
|
411
462
|
const hostUngewiss = stoerungsAntwort !== undefined;
|
|
463
|
+
if (stornoGesendet && (0, transaction_response_js_1.isCanceled)(status)) {
|
|
464
|
+
steps.push(`Status: Zahlung aufgehoben (${transaction_response_js_1.TRANSACTION_CANCELED_CODE}) -- das nachgeschickte Storno hat gegriffen, `
|
|
465
|
+
+ 'es ist nichts belastet');
|
|
466
|
+
emit('resolved', steps[steps.length - 1], id);
|
|
467
|
+
return { outcome: 'declined', transactionId: id, response: status, reason: 'voidedAfterHostFault', steps: [...steps] };
|
|
468
|
+
}
|
|
469
|
+
if (!stornoErwogen && (0, transaction_response_js_1.needsReversal)(status)) {
|
|
470
|
+
// Die Zahlung selbst kam ohne Antwort zurueck, erst die Statusabfrage
|
|
471
|
+
// nennt die ausgebliebene Host-Antwort -- dasselbe Storno wie oben.
|
|
472
|
+
steps.push(statusOhneErgebnis(status));
|
|
473
|
+
stornoErwogen = true;
|
|
474
|
+
stornoGesendet = stornoCents !== undefined;
|
|
475
|
+
const storniert = await sendReversal(id, steps, elapsedMs, stornoCents, status);
|
|
476
|
+
if (storniert)
|
|
477
|
+
return storniert;
|
|
478
|
+
wait = nextWait(wait);
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
412
481
|
// Auf die Statusabfrage entscheidet nur ein Code, der den gesuchten
|
|
413
482
|
// Vorgang beschreibt -- nicht einer, der diese Abfrage abweist
|
|
414
483
|
// ([isConclusiveAsStatus]). Nach einer Stoerung beim Host entscheidet nur
|
|
@@ -610,7 +679,7 @@ function createHpsPayments(client, target, options = {}) {
|
|
|
610
679
|
return settled;
|
|
611
680
|
steps.push(offeneAntwort(res));
|
|
612
681
|
}
|
|
613
|
-
return resolve(id, steps, res);
|
|
682
|
+
return resolve(id, steps, res, paymentOptions.amountCents);
|
|
614
683
|
}
|
|
615
684
|
/**
|
|
616
685
|
* Gutschrift mit geklaertem Ausgang -- EXAKT derselbe Klaerweg wie [pay]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Die Antwortcodeliste von TECS, erhalten ueber hobex am 16.09.2026.
|
|
3
|
+
*
|
|
4
|
+
* **Zwilling:** `kasseneck_api/lib/src/hobex_hps/tecs_codes.dart` -- dort
|
|
5
|
+
* steht die Einordnung ausfuehrlich. Kurz: eine Antwort des Hosts ohne
|
|
6
|
+
* Genehmigung lehnt ab; eine Genehmigung, die nicht `0` ist, bleibt offen;
|
|
7
|
+
* keine oder keine brauchbare Host-Antwort bleibt offen und bekommt ein
|
|
8
|
+
* Storno; Codes anderer TECS-Produkte sagen nichts ueber die Zahlung.
|
|
9
|
+
*
|
|
10
|
+
* TECS fuehrt die Host-Antworten vierstellig (`0055`), das Terminal sendet
|
|
11
|
+
* sie ohne Nullen (`55`). Die Tabelle fuehrt die Form des Terminals.
|
|
12
|
+
* Gemessene Codes (`0`, `9002`, `9003`, `9011`, `9027`, `9900`, `55`)
|
|
13
|
+
* stehen in `transaction-response.ts`, hier nur die uebrigen.
|
|
14
|
+
*/
|
|
15
|
+
import type { HpsCode } from './transaction-response.js';
|
|
16
|
+
export declare const TECS_CODES: readonly HpsCode[];
|