@kreiseck/kasseneck-api 0.9.3 → 0.10.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 +62 -0
- package/dist/cjs/kasse/settings.d.ts +10 -2
- package/dist/cjs/kasse/settings.js +10 -2
- package/dist/cjs/payments/hobex-hps/index.d.ts +2 -2
- package/dist/cjs/payments/hobex-hps/index.js +36 -1
- package/dist/cjs/payments/hobex-hps/outcome.d.ts +20 -1
- package/dist/cjs/payments/hobex-hps/outcome.js +11 -0
- package/dist/cjs/payments/hobex-hps/payments.d.ts +19 -0
- package/dist/cjs/payments/hobex-hps/payments.js +160 -33
- package/dist/cjs/payments/hobex-hps/transaction-response.d.ts +180 -31
- package/dist/cjs/payments/hobex-hps/transaction-response.js +308 -88
- package/dist/cjs/payments/index.d.ts +1 -1
- package/dist/cjs/payments/index.js +8 -1
- package/dist/esm/kasse/settings.d.ts +10 -2
- package/dist/esm/kasse/settings.js +10 -2
- package/dist/esm/payments/hobex-hps/index.d.ts +2 -2
- package/dist/esm/payments/hobex-hps/index.js +2 -2
- package/dist/esm/payments/hobex-hps/outcome.d.ts +20 -1
- package/dist/esm/payments/hobex-hps/outcome.js +10 -0
- package/dist/esm/payments/hobex-hps/payments.d.ts +19 -0
- package/dist/esm/payments/hobex-hps/payments.js +161 -34
- package/dist/esm/payments/hobex-hps/transaction-response.d.ts +180 -31
- package/dist/esm/payments/hobex-hps/transaction-response.js +302 -87
- package/dist/esm/payments/index.d.ts +1 -1
- package/dist/esm/payments/index.js +1 -1
- package/fixtures/hobex-hps-codes.json +365 -15
- package/fixtures/kasse-settings-standard.json +1 -1
- package/fixtures/kasse-texte.json +1 -1
- package/fixtures/oberflaeche.json +2 -1
- package/package.json +1 -1
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
*/
|
|
20
20
|
export { createHpsConnectClient, } from './connect-client.js';
|
|
21
21
|
export { HpsClarifyTimeoutError, HpsConnectException, HpsConnectTerminalError, HpsConnectTransportError, HpsPreflightError, HpsTransactionIdError, PREFLIGHT_CONNECT_CODES, } from './errors.js';
|
|
22
|
-
export { mayRetrySafely, } from './outcome.js';
|
|
22
|
+
export { isHostUncertainResult, mayRetrySafely, } from './outcome.js';
|
|
23
23
|
export { createHpsPayments, } from './payments.js';
|
|
24
24
|
export { MAX_TRANSACTION_ID_LENGTH, createHpsTransactionIdGenerator, isValidHpsTransactionId, newHpsTransactionId, } from './transaction-id.js';
|
|
25
|
-
export { ABORTED_CODE, APPROVED_CODE, CARD_NOT_PRESENT_CODE, HPS_MEASURED_CODES, 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, TERMINAL_BUSY_HTTP_STATUS, TRANSACTION_CANCELED_CODE, isApproved, isCanceled, isConclusive, isInProgress, isNoStatement, isNotAbortable, isTechnicalError, isUnknownCode, parseHpsTransactionResponse, } from './transaction-response.js';
|
|
25
|
+
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, TERMINAL_BUSY_HTTP_STATUS, TRANSACTION_CANCELED_CODE, isApproved, isCanceled, isConclusive, isConclusiveAsStatus, isInProgress, isNoStatement, isNotAbortable, isTechnicalError, isUnknownCode, parseHpsTransactionResponse, } from './transaction-response.js';
|
|
26
26
|
export { hobexReceiptFromHps } from './receipt.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type HpsCodeReason, type HpsTransactionResponse } from './transaction-response.js';
|
|
2
2
|
/**
|
|
3
3
|
* Ausgang eines Kartenzahlvorgangs — die einzige Frage, die ein Aufrufer
|
|
4
4
|
* wirklich hat: darf ich es nochmal versuchen?
|
|
@@ -35,6 +35,18 @@ export interface HpsPaymentResult {
|
|
|
35
35
|
* Entscheidung getragen. Bei schluessigem Ausgang nicht gesetzt.
|
|
36
36
|
*/
|
|
37
37
|
readonly lastResponse?: HpsTransactionResponse;
|
|
38
|
+
/**
|
|
39
|
+
* Worauf die Kasse reagiert -- der Grund hinter dem Ausgang, mit dem Satz
|
|
40
|
+
* fuer den Bediener in `HPS_REASON_HINTS`.
|
|
41
|
+
*
|
|
42
|
+
* Gesetzt an der Stelle, die den Ausgang entschieden hat, nicht aus
|
|
43
|
+
* [response] abgeleitet: ein bestaetigter Abbruch antwortet `'0'` und ist
|
|
44
|
+
* trotzdem `'aborted'`, und wenn die Zwei-9027-Regel eine Zahlung als
|
|
45
|
+
* abgelehnt klaert, zaehlt der Code der ZAHLUNG, nicht das `9027` danach.
|
|
46
|
+
* Fehlt, wenn kein Code etwas erklaert (die Leitung riss ab, bevor das
|
|
47
|
+
* Terminal etwas sagte) und bei einer Aufhebung, die nicht gegriffen hat.
|
|
48
|
+
*/
|
|
49
|
+
readonly reason?: HpsCodeReason;
|
|
38
50
|
/**
|
|
39
51
|
* Verlauf der Klaerung, in Reihenfolge — der Nachweis, der im
|
|
40
52
|
* Belastungsstreit gelesen wird. Behauptet nie eine Ursache, die nicht
|
|
@@ -44,3 +56,10 @@ export interface HpsPaymentResult {
|
|
|
44
56
|
}
|
|
45
57
|
/** Nur bei `'declined'` steht fest, dass nichts belastet wurde. */
|
|
46
58
|
export declare function mayRetrySafely(result: Pick<HpsPaymentResult, 'outcome'>): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* `true`, wenn der Ausgang offen ist, weil das Terminal eine Stoerung beim
|
|
61
|
+
* oder nach dem hobex-Host meldete (`effect: 'hostUncertain'`). Wichtig fuer
|
|
62
|
+
* jede SPAETERE Nachfrage: antwortet die Statusabfrage dann `9027`, heisst das
|
|
63
|
+
* hier NICHT "nichts belastet".
|
|
64
|
+
*/
|
|
65
|
+
export declare function isHostUncertainResult(result: Pick<HpsPaymentResult, 'outcome' | 'reason'>): boolean;
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
+
import { isHostUncertainReason } from './transaction-response.js';
|
|
1
2
|
/** Nur bei `'declined'` steht fest, dass nichts belastet wurde. */
|
|
2
3
|
export function mayRetrySafely(result) {
|
|
3
4
|
return result.outcome === 'declined';
|
|
4
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* `true`, wenn der Ausgang offen ist, weil das Terminal eine Stoerung beim
|
|
8
|
+
* oder nach dem hobex-Host meldete (`effect: 'hostUncertain'`). Wichtig fuer
|
|
9
|
+
* jede SPAETERE Nachfrage: antwortet die Statusabfrage dann `9027`, heisst das
|
|
10
|
+
* hier NICHT "nichts belastet".
|
|
11
|
+
*/
|
|
12
|
+
export function isHostUncertainResult(result) {
|
|
13
|
+
return result.outcome === 'unresolved' && isHostUncertainReason(result.reason);
|
|
14
|
+
}
|
|
@@ -89,6 +89,25 @@ import type { HpsPaymentResult } from './outcome.js';
|
|
|
89
89
|
* Kein [tryAbort]-Versuch bei [cancel]: die Originalzahlung ist laengst
|
|
90
90
|
* abgeschlossen und antwortet gemessen mit `100010` -- ein Abbruch darauf
|
|
91
91
|
* waere sinnlos.
|
|
92
|
+
*
|
|
93
|
+
* ## Stoerung beim Host: die Klaerung darf nichts schliessen (11.09.2026)
|
|
94
|
+
*
|
|
95
|
+
* Die Antwortcodeliste von hobex benennt Codes, bei denen der Host beteiligt
|
|
96
|
+
* war und das Terminal NICHT selbst storniert (`effect: 'hostUncertain'`,
|
|
97
|
+
* etwa `100007`). Fuer sie gilt zweierlei:
|
|
98
|
+
*
|
|
99
|
+
* 1. Die Zwei-9027-Regel ([ausGeschlossenerAntwort]) greift nicht. Sie
|
|
100
|
+
* schliesst aus "das Terminal hat geantwortet und nichts gespeichert" auf
|
|
101
|
+
* "nichts belastet" -- das stimmt fuer einen Vorgang, der am Terminal
|
|
102
|
+
* endete, aber nicht fuer einen, dessen Ausgang beim Host liegt. Dasselbe
|
|
103
|
+
* gilt sinngemaess fuer [cancel]: ein unveraendertes `'0'` auf die
|
|
104
|
+
* Originalzahlung beweist dann nicht, dass die Aufhebung beim Host nicht
|
|
105
|
+
* ankam.
|
|
106
|
+
* 2. Die Klaerung wartet trotzdem nicht das ganze Budget ab. Sagt die
|
|
107
|
+
* Statusabfrage zweimal in Folge nichts Neues (`9027` oder erneut ein
|
|
108
|
+
* solcher Code), endet sie sofort als `unresolved` -- das Terminal wird es
|
|
109
|
+
* auch in 90 Sekunden nicht wissen. Meldet sie dagegen `'0'`, ist die
|
|
110
|
+
* Zahlung genehmigt, ganz normal.
|
|
92
111
|
*/
|
|
93
112
|
export interface HpsPaymentsOptions {
|
|
94
113
|
/** Wie lange insgesamt geklaert wird, bevor der Ausgang offen bleibt. Vorgabe 90 s. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HpsClarifyTimeoutError, HpsConnectException, HpsConnectTerminalError, HpsPreflightError, } from './errors.js';
|
|
2
2
|
import { newHpsTransactionId } from './transaction-id.js';
|
|
3
|
-
import { isApproved, isCanceled, isConclusive, isNoStatement, isNotAbortable, isTechnicalError, isUnknownCode, NOT_ABORTABLE_CODE, TECHNICAL_ERROR_CODE, TRANSACTION_CANCELED_CODE, } from './transaction-response.js';
|
|
3
|
+
import { hpsCodeInfo, hpsCodeReason, isApproved, isCanceled, isConclusive, isConclusiveAsStatus, isHostUncertain, isNoStatement, isNotAbortable, isTechnicalError, isUnknownCode, NOT_ABORTABLE_CODE, NOT_FOUND_CODE, TECHNICAL_ERROR_CODE, TRANSACTION_CANCELED_CODE, } from './transaction-response.js';
|
|
4
4
|
/** Teiler, mit dem das Abbruchbudget aus [HpsPaymentsOptions.resolveBudgetMs] entsteht. */
|
|
5
5
|
const ABORT_BUDGET_DIVISOR = 6;
|
|
6
6
|
export function createHpsPayments(client, target, options = {}) {
|
|
@@ -56,7 +56,34 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
56
56
|
return null;
|
|
57
57
|
steps.push('Terminal beschaeftigt (HTTP 409) -- die Anfrage wurde nicht angenommen, es ist nichts geschehen');
|
|
58
58
|
emit('resolved', steps[steps.length - 1], id);
|
|
59
|
-
return { outcome: 'declined', transactionId: id, steps: [...steps] };
|
|
59
|
+
return { outcome: 'declined', transactionId: id, reason: 'terminalBusy', steps: [...steps] };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Benennt einen Code mit `effect: 'hostUncertain'` fuer den Nachweis: was
|
|
63
|
+
* das Terminal meldet, mit Code und hobex-Titel. Die Folgerung setzt der
|
|
64
|
+
* Aufrufer dazu.
|
|
65
|
+
*/
|
|
66
|
+
function stoerung(res) {
|
|
67
|
+
const info = hpsCodeInfo(res.responseCode);
|
|
68
|
+
const was = info.reason === 'internalError'
|
|
69
|
+
? 'interner Fehler des Terminals'
|
|
70
|
+
: 'Stoerung zwischen Terminal und hobex-Host, das Terminal storniert nicht selbst';
|
|
71
|
+
return `Terminal meldet ${was} (${info.code} "${info.title}")`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Der Grund eines offenen Ausgangs. [antwort] ist die Antwort auf die
|
|
75
|
+
* ERZEUGENDE Anfrage (oder die zuerst gemeldete Stoerung), [letzte] die
|
|
76
|
+
* zuletzt gelesene Statusabfrage. Eine Stoerung beim Host geht vor; sonst
|
|
77
|
+
* erklaert der Code der erzeugenden Anfrage mehr als ein `9027` danach.
|
|
78
|
+
*/
|
|
79
|
+
function offenerGrund(antwort, letzte) {
|
|
80
|
+
if (antwort && isHostUncertain(antwort))
|
|
81
|
+
return hpsCodeReason(antwort.responseCode);
|
|
82
|
+
if (letzte && isHostUncertain(letzte))
|
|
83
|
+
return hpsCodeReason(letzte.responseCode);
|
|
84
|
+
if (antwort?.responseCode !== undefined)
|
|
85
|
+
return hpsCodeReason(antwort.responseCode);
|
|
86
|
+
return hpsCodeReason(letzte?.responseCode);
|
|
60
87
|
}
|
|
61
88
|
/** Verlaufseintrag fuer eine direkte Antwort, die den Ausgang NICHT festschreibt. */
|
|
62
89
|
function offeneAntwort(res) {
|
|
@@ -66,6 +93,12 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
66
93
|
if (isTechnicalError(res)) {
|
|
67
94
|
return `Antwort mit technischem Fehler (${TECHNICAL_ERROR_CODE}) -- keine Aussage ueber den Vorgang, Ausgang wird geklaert`;
|
|
68
95
|
}
|
|
96
|
+
if (isHostUncertain(res)) {
|
|
97
|
+
return `${stoerung(res)} -- ob belastet wurde, weiss das Terminal nicht, Ausgang wird geklaert`;
|
|
98
|
+
}
|
|
99
|
+
if (res.responseCode === NOT_FOUND_CODE) {
|
|
100
|
+
return `Terminal kennt den Vorgang nicht (${res.responseCode} "Not Found") -- keine Aussage, Ausgang wird geklaert`;
|
|
101
|
+
}
|
|
69
102
|
if (isUnknownCode(res)) {
|
|
70
103
|
return `Terminal nennt einen unbekannten Code (${res.responseCode})${klartext(res)} -- Ausgang wird geklaert`;
|
|
71
104
|
}
|
|
@@ -79,6 +112,22 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
79
112
|
if (isTechnicalError(status)) {
|
|
80
113
|
return `Status: technischer Fehler (${TECHNICAL_ERROR_CODE}) -- keine Aussage ueber den Vorgang`;
|
|
81
114
|
}
|
|
115
|
+
if (isHostUncertain(status)) {
|
|
116
|
+
return `Status: ${stoerung(status)} -- keine Aussage`;
|
|
117
|
+
}
|
|
118
|
+
// Bewusst NICHT "keine Auskunft": das Wort steht fuer das gemessene 9027,
|
|
119
|
+
// und Aufrufer lesen es als solches (sastre, OpenCardPaymentService).
|
|
120
|
+
if (status.responseCode === NOT_FOUND_CODE) {
|
|
121
|
+
return `Status: Vorgang nicht gefunden (${status.responseCode}) -- keine Aussage`;
|
|
122
|
+
}
|
|
123
|
+
const info = hpsCodeInfo(status.responseCode);
|
|
124
|
+
if (info?.rejectsRequest) {
|
|
125
|
+
return `Status: Abfrage abgewiesen (${info.code} "${info.title}") -- keine Aussage ueber den Vorgang`;
|
|
126
|
+
}
|
|
127
|
+
if (info?.conclusive && !isApproved(status)) {
|
|
128
|
+
// Nur erreichbar nach einer Stoerung beim Host, siehe [resolve].
|
|
129
|
+
return `Status: abgelehnt (${info.code} "${info.title}") -- nach der Stoerung beim hobex-Host entscheidet nur eine Genehmigung`;
|
|
130
|
+
}
|
|
82
131
|
if (isUnknownCode(status)) {
|
|
83
132
|
return `Status: unbekannter Code (${status.responseCode})${klartext(status)} -- keine Aussage`;
|
|
84
133
|
}
|
|
@@ -95,14 +144,27 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
95
144
|
const text = res.responseText?.trim();
|
|
96
145
|
return text ? ` "${text}"` : '';
|
|
97
146
|
}
|
|
98
|
-
/**
|
|
99
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Ordnet eine Terminal-Antwort ein. `null`, wenn sie nichts entscheidet.
|
|
149
|
+
*
|
|
150
|
+
* [aufhebung]: die Antwort gehoert zu [cancel]. Ein `'0'` heisst dort
|
|
151
|
+
* "aufgehoben", und der Grund ist `'canceled'` statt `'approved'`.
|
|
152
|
+
*/
|
|
153
|
+
function fromResponse(res, id, steps, aufhebung = false) {
|
|
100
154
|
if (!isConclusive(res))
|
|
101
155
|
return null;
|
|
102
156
|
const approved = res.responseCode === '0';
|
|
103
|
-
steps.push(approved
|
|
157
|
+
steps.push(approved
|
|
158
|
+
? 'Terminal: genehmigt'
|
|
159
|
+
: `Terminal: abgelehnt (${res.responseCode} "${hpsCodeInfo(res.responseCode).title}")`);
|
|
104
160
|
emit('resolved', steps[steps.length - 1], id);
|
|
105
|
-
return {
|
|
161
|
+
return {
|
|
162
|
+
outcome: approved ? 'approved' : 'declined',
|
|
163
|
+
transactionId: id,
|
|
164
|
+
response: res,
|
|
165
|
+
reason: approved && aufhebung ? 'canceled' : hpsCodeReason(res.responseCode),
|
|
166
|
+
steps: [...steps],
|
|
167
|
+
};
|
|
106
168
|
}
|
|
107
169
|
/**
|
|
108
170
|
* Ordnet die DIREKTE Antwort auf einen Aufhebungs-Request ein. Fast dasselbe
|
|
@@ -128,7 +190,7 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
128
190
|
steps.push(`Aufhebung mit ${TRANSACTION_CANCELED_CODE} beantwortet -- mehrdeutig, der Zustand der Originalzahlung wird abgefragt`);
|
|
129
191
|
return null;
|
|
130
192
|
}
|
|
131
|
-
return fromResponse(res, id, steps);
|
|
193
|
+
return fromResponse(res, id, steps, true);
|
|
132
194
|
}
|
|
133
195
|
/**
|
|
134
196
|
* Ordnet die Statusabfrage einer OFFENEN AUFHEBUNG ein. `null`, wenn sie
|
|
@@ -183,7 +245,7 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
183
245
|
if (voided) {
|
|
184
246
|
steps.push(`Terminal: Aufhebung bestaetigt (${status.responseCode ?? status.state})`);
|
|
185
247
|
emit('resolved', steps[steps.length - 1], id);
|
|
186
|
-
return { outcome: 'approved', transactionId: id, response: status, steps: [...steps] };
|
|
248
|
+
return { outcome: 'approved', transactionId: id, response: status, reason: 'canceled', steps: [...steps] };
|
|
187
249
|
}
|
|
188
250
|
if (isApproved(status)) {
|
|
189
251
|
if (firstQuery) {
|
|
@@ -199,14 +261,15 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
199
261
|
/**
|
|
200
262
|
* [letzteAntwort] ist die letzte Antwort, die das Terminal in dieser
|
|
201
263
|
* Klaerung gab -- als `lastResponse` fuer Anzeige und Katalog,
|
|
202
|
-
* ausdruecklich NICHT als `response`.
|
|
264
|
+
* ausdruecklich NICHT als `response`. [grund] siehe [offenerGrund].
|
|
203
265
|
*/
|
|
204
|
-
function open(id, steps, letzteAntwort) {
|
|
266
|
+
function open(id, steps, letzteAntwort, grund) {
|
|
205
267
|
emit('resolved', steps[steps.length - 1], id);
|
|
206
268
|
return {
|
|
207
269
|
outcome: 'unresolved',
|
|
208
270
|
transactionId: id,
|
|
209
271
|
...(letzteAntwort ? { lastResponse: letzteAntwort } : {}),
|
|
272
|
+
...(grund ? { reason: grund } : {}),
|
|
210
273
|
steps: [...steps],
|
|
211
274
|
};
|
|
212
275
|
}
|
|
@@ -271,26 +334,46 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
271
334
|
}
|
|
272
335
|
steps.push('Abbruch bestaetigt -- der Vorgang war noch abbrechbar, es ist nichts belastet');
|
|
273
336
|
emit('resolved', steps[steps.length - 1], id);
|
|
274
|
-
return { outcome: 'declined', transactionId: id, response: res, steps: [...steps] };
|
|
337
|
+
return { outcome: 'declined', transactionId: id, response: res, reason: 'aborted', steps: [...steps] };
|
|
275
338
|
}
|
|
276
|
-
/** Klaert einen offenen Ausgang: erst abbrechen, dann abfragen -- bis das Terminal etwas sagt oder das Budget aufgebraucht ist. */
|
|
277
339
|
/**
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
* [
|
|
282
|
-
* nicht
|
|
340
|
+
* Klaert einen offenen Ausgang: erst abbrechen, dann abfragen -- bis das
|
|
341
|
+
* Terminal etwas sagt oder das Budget aufgebraucht ist.
|
|
342
|
+
*
|
|
343
|
+
* [antwort] ist die Antwort auf die ERZEUGENDE Anfrage, sofern eine kam.
|
|
344
|
+
* Traegt sie einen Ergebniscode, dessen Bedeutung wir nur nicht kennen, ist
|
|
345
|
+
* der Vorgang am Geraet abgeschlossen -- und erst dadurch bekommt
|
|
346
|
+
* `9027` beim Pollen einen Aussagewert, den es sonst nicht hat. Siehe
|
|
347
|
+
* [ausGeschlossenerAntwort]. Meldet sie eine Stoerung beim Host
|
|
348
|
+
* ([isHostUncertain]), bekommt `9027` diesen Aussagewert gerade NICHT --
|
|
349
|
+
* siehe Klassendoku, "Stoerung beim Host".
|
|
283
350
|
*/
|
|
284
|
-
async function resolve(id, steps,
|
|
351
|
+
async function resolve(id, steps, antwort) {
|
|
285
352
|
emit('resolving', 'Ausgang offen, Klaerung laeuft', id);
|
|
286
353
|
const start = now();
|
|
287
354
|
const elapsedMs = () => now() - start;
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
355
|
+
const antwortMitCode = antwort?.responseCode !== undefined;
|
|
356
|
+
// Einmal gemeldet, bleibt die Stoerung stehen -- auch wenn erst die
|
|
357
|
+
// Statusabfrage sie nennt und danach 9027 kommt.
|
|
358
|
+
let stoerungsAntwort = antwort && isHostUncertain(antwort) ? antwort : undefined;
|
|
359
|
+
let letzteAntwort = antwort;
|
|
360
|
+
if (stoerungsAntwort === undefined) {
|
|
361
|
+
const aborted = await tryAbort(id, steps, elapsedMs);
|
|
362
|
+
if (aborted)
|
|
363
|
+
return aborted;
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
// Der Vorgang ist am Terminal schon beendet -- mit einer Stoerung beim
|
|
367
|
+
// Host. Ein quittierter Abbruch bewiese nur, dass am Terminal nichts mehr
|
|
368
|
+
// laeuft, nicht, dass der Host nichts belastet hat.
|
|
369
|
+
steps.push('Kein Abbruchversuch -- das Terminal hat den Vorgang mit einer Stoerung beim hobex-Host beendet');
|
|
370
|
+
}
|
|
291
371
|
let wait = 0;
|
|
292
372
|
let transportFailures = 0;
|
|
293
373
|
let ohneAuskunft = 0;
|
|
374
|
+
// Beantwortete Statusabfragen in Folge, die zu einem Vorgang mit
|
|
375
|
+
// Host-Stoerung nichts Neues sagen.
|
|
376
|
+
let stoerungOhneNeues = 0;
|
|
294
377
|
while (elapsedMs() < resolveBudgetMs) {
|
|
295
378
|
if (wait > 0) {
|
|
296
379
|
const left = resolveBudgetMs - elapsedMs();
|
|
@@ -315,23 +398,45 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
315
398
|
wait = nextWait(wait);
|
|
316
399
|
continue;
|
|
317
400
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
401
|
+
if (isHostUncertain(status))
|
|
402
|
+
stoerungsAntwort ??= status;
|
|
403
|
+
const hostUngewiss = stoerungsAntwort !== undefined;
|
|
404
|
+
// Auf die Statusabfrage entscheidet nur ein Code, der den gesuchten
|
|
405
|
+
// Vorgang beschreibt -- nicht einer, der diese Abfrage abweist
|
|
406
|
+
// ([isConclusiveAsStatus]). Nach einer Stoerung beim Host entscheidet nur
|
|
407
|
+
// noch eine Genehmigung: jede andere Aussage des Terminals betrifft seinen
|
|
408
|
+
// Speicher, nicht den des Hosts.
|
|
409
|
+
if (hostUngewiss ? isApproved(status) : isConclusiveAsStatus(status)) {
|
|
410
|
+
const settled = fromResponse(status, id, steps);
|
|
411
|
+
if (settled)
|
|
412
|
+
return settled;
|
|
413
|
+
}
|
|
321
414
|
if (isNoStatement(status)) {
|
|
322
415
|
ohneAuskunft += 1;
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
416
|
+
if (!hostUngewiss) {
|
|
417
|
+
const geklaert = ausGeschlossenerAntwort(id, steps, status, antwortMitCode ? antwort : undefined, ohneAuskunft);
|
|
418
|
+
if (geklaert)
|
|
419
|
+
return geklaert;
|
|
420
|
+
}
|
|
326
421
|
}
|
|
327
422
|
else {
|
|
328
423
|
ohneAuskunft = 0;
|
|
329
424
|
}
|
|
425
|
+
// Nach einer Stoerung ist jede Antwort ausser '0' (die oben schon
|
|
426
|
+
// entschieden hat) "nichts Neues".
|
|
427
|
+
stoerungOhneNeues = hostUngewiss ? stoerungOhneNeues + 1 : 0;
|
|
330
428
|
steps.push(statusOhneErgebnis(status) ?? 'Status: noch kein Ergebniscode');
|
|
429
|
+
if (stoerungOhneNeues >= 2) {
|
|
430
|
+
// Siehe Klassendoku, "Stoerung beim Host": das Terminal weiss es
|
|
431
|
+
// nicht, und es wird es auch nicht wissen, wenn wir weiterfragen.
|
|
432
|
+
steps.push('Statusabfrage zweimal ohne Neues nach einer Stoerung beim hobex-Host -- '
|
|
433
|
+
+ 'das Terminal kann nicht sagen, ob belastet wurde, Ausgang bleibt offen');
|
|
434
|
+
return open(id, steps, letzteAntwort, offenerGrund(stoerungsAntwort ?? antwort, letzteAntwort));
|
|
435
|
+
}
|
|
331
436
|
wait = nextWait(wait);
|
|
332
437
|
}
|
|
333
438
|
steps.push('Ausgang bleibt offen');
|
|
334
|
-
return open(id, steps, letzteAntwort);
|
|
439
|
+
return open(id, steps, letzteAntwort, offenerGrund(stoerungsAntwort ?? antwort, letzteAntwort));
|
|
335
440
|
}
|
|
336
441
|
/**
|
|
337
442
|
* Liest `9027` beim Pollen als "nicht genehmigt" -- aber NUR, wenn das
|
|
@@ -356,10 +461,15 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
356
461
|
* entstuende die Doppelbelastung vom 24.08.2026 an einer neuen Stelle.
|
|
357
462
|
* Dieselbe Absicherung traegt bereits [fromCancelStatus].
|
|
358
463
|
*
|
|
464
|
+
* [antwort] ist die Antwort MIT Code auf die erzeugende Anfrage -- oder
|
|
465
|
+
* `undefined`, wenn keine kam; dann greift die Regel nicht. Ihr Code ergibt
|
|
466
|
+
* den `reason`: er erklaert den Ausgang, das `9027` danach nicht. Der
|
|
467
|
+
* Aufrufer ruft diese Regel NICHT fuer eine Stoerung beim Host.
|
|
468
|
+
*
|
|
359
469
|
* `undefined` heisst: nicht entschieden, weiter pollen.
|
|
360
470
|
*/
|
|
361
|
-
function ausGeschlossenerAntwort(id, steps, status,
|
|
362
|
-
if (!
|
|
471
|
+
function ausGeschlossenerAntwort(id, steps, status, antwort, ohneAuskunft) {
|
|
472
|
+
if (!antwort)
|
|
363
473
|
return undefined;
|
|
364
474
|
if (ohneAuskunft < 2)
|
|
365
475
|
return undefined;
|
|
@@ -371,6 +481,7 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
371
481
|
outcome: 'declined',
|
|
372
482
|
transactionId: id,
|
|
373
483
|
response: status,
|
|
484
|
+
reason: hpsCodeReason(antwort.responseCode),
|
|
374
485
|
steps: [...steps],
|
|
375
486
|
};
|
|
376
487
|
}
|
|
@@ -390,11 +501,17 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
390
501
|
*
|
|
391
502
|
* Budget, Backoff und Transportfehler-Deckelung sind unveraendert aus
|
|
392
503
|
* [resolve] uebernommen.
|
|
504
|
+
*
|
|
505
|
+
* [antwort] ist die direkte Antwort auf den Aufhebungs-Request, sofern eine
|
|
506
|
+
* kam. Meldete sie eine Stoerung beim Host, entscheidet ein unveraendertes
|
|
507
|
+
* `'0'` nichts -- siehe Klassendoku, "Stoerung beim Host".
|
|
393
508
|
*/
|
|
394
|
-
async function resolveCancel(id, steps,
|
|
509
|
+
async function resolveCancel(id, steps, antwort) {
|
|
395
510
|
emit('resolving', 'Ausgang offen, Klaerung laeuft', id);
|
|
396
511
|
const start = now();
|
|
397
512
|
const elapsedMs = () => now() - start;
|
|
513
|
+
const hostUngewiss = antwort !== undefined && isHostUncertain(antwort);
|
|
514
|
+
let letzteAntwort = antwort;
|
|
398
515
|
let wait = 0;
|
|
399
516
|
let transportFailures = 0;
|
|
400
517
|
// Zaehlt nur BEANTWORTETE Statusabfragen -- Grundlage der Karenz fuer den
|
|
@@ -425,6 +542,16 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
425
542
|
wait = nextWait(wait);
|
|
426
543
|
continue;
|
|
427
544
|
}
|
|
545
|
+
if (hostUngewiss && isApproved(status) && answeredQueries >= 2) {
|
|
546
|
+
// Siehe Klassendoku, "Stoerung beim Host": das unveraenderte '0'
|
|
547
|
+
// spiegelt nur den Speicher des Terminals. "Hat nicht gegriffen"
|
|
548
|
+
// fuehrte nach dem Tagesabschluss zu einer Rueckerstattung, die der
|
|
549
|
+
// Kunde doppelt bekaeme, falls der Host die Aufhebung doch verbucht hat.
|
|
550
|
+
steps.push('Terminal: Originalzahlung steht unveraendert (0), aber die Aufhebung endete mit einer '
|
|
551
|
+
+ 'Stoerung beim hobex-Host -- ob sie dort gewirkt hat, kann das Terminal nicht sagen, '
|
|
552
|
+
+ 'Ausgang bleibt offen');
|
|
553
|
+
return open(id, steps, letzteAntwort, offenerGrund(antwort, letzteAntwort));
|
|
554
|
+
}
|
|
428
555
|
const settled = fromCancelStatus(status, id, steps, answeredQueries === 1);
|
|
429
556
|
if (settled)
|
|
430
557
|
return settled;
|
|
@@ -436,7 +563,7 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
436
563
|
wait = nextWait(wait);
|
|
437
564
|
}
|
|
438
565
|
steps.push('Ausgang bleibt offen');
|
|
439
|
-
return open(id, steps, letzteAntwort);
|
|
566
|
+
return open(id, steps, letzteAntwort, offenerGrund(antwort, letzteAntwort));
|
|
440
567
|
}
|
|
441
568
|
async function pay(paymentOptions) {
|
|
442
569
|
const id = paymentOptions.transactionId ?? newHpsTransactionId();
|
|
@@ -475,7 +602,7 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
475
602
|
return settled;
|
|
476
603
|
steps.push(offeneAntwort(res));
|
|
477
604
|
}
|
|
478
|
-
return resolve(id, steps, res
|
|
605
|
+
return resolve(id, steps, res);
|
|
479
606
|
}
|
|
480
607
|
/**
|
|
481
608
|
* Gutschrift mit geklaertem Ausgang -- EXAKT derselbe Klaerweg wie [pay]
|
|
@@ -519,7 +646,7 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
519
646
|
// Dieselbe Klaerfunktion wie [pay]: die Kennung ist die des NEUEN
|
|
520
647
|
// Vorgangs, eine Statusabfrage darauf liefert also genau dessen Ausgang,
|
|
521
648
|
// und der Abbruch ist derselbe Diskriminator wie bei einer Zahlung.
|
|
522
|
-
return resolve(id, steps, res
|
|
649
|
+
return resolve(id, steps, res);
|
|
523
650
|
}
|
|
524
651
|
/**
|
|
525
652
|
* Aufhebung (Storno/Void) einer bestehenden Zahlung mit geklaertem Ausgang.
|