@kreiseck/kasseneck-api 0.17.0 → 0.19.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 +63 -0
- package/README.md +26 -0
- package/dist/cjs/client/aufrufe.d.ts +1 -1
- package/dist/cjs/client/aufrufe.js +1 -0
- 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/api.d.ts +3 -1
- package/dist/cjs/rechnung/api.js +1 -0
- package/dist/cjs/rechnung/endpunkte.d.ts +11 -1
- package/dist/cjs/rechnung/endpunkte.js +22 -0
- package/dist/cjs/rechnung/index.d.ts +3 -3
- package/dist/cjs/rechnung/index.js +4 -1
- package/dist/cjs/rechnung/typen.d.ts +48 -1
- package/dist/cjs/rechnung/vertrag.d.ts +24 -2
- package/dist/cjs/rechnung/vertrag.js +43 -1
- package/dist/esm/client/aufrufe.d.ts +1 -1
- package/dist/esm/client/aufrufe.js +1 -0
- 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/api.d.ts +3 -1
- package/dist/esm/rechnung/api.js +2 -1
- package/dist/esm/rechnung/endpunkte.d.ts +11 -1
- package/dist/esm/rechnung/endpunkte.js +21 -0
- package/dist/esm/rechnung/index.d.ts +3 -3
- package/dist/esm/rechnung/index.js +2 -2
- package/dist/esm/rechnung/typen.d.ts +48 -1
- package/dist/esm/rechnung/vertrag.d.ts +24 -2
- package/dist/esm/rechnung/vertrag.js +42 -0
- package/fixtures/hobex-hps-codes.json +4075 -52
- package/fixtures/kasse-texte.json +1 -1
- package/fixtures/oberflaeche.json +16 -3
- package/fixtures/rechnung-api-beispiele/issue-bezahlt.json +32 -0
- package/fixtures/rechnung-api-beispiele/issue-fehler-zahlungsart.json +26 -0
- package/fixtures/rechnung-api-beispiele/zahlung-fehler-ohne-schluessel.json +14 -0
- package/fixtures/rechnung-api-beispiele/zahlung-nachtrag.json +14 -0
- package/fixtures/rechnung-api.schema.json +82 -2
- package/fixtures/rechnung-texte.json +1 -1
- package/package.json +1 -1
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* Nummer, Summen — ist bewusst **nicht** setzbar.
|
|
20
20
|
*/
|
|
21
21
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
-
exports.RECHNUNG_MINDESTENS_EINS = exports.RECHNUNG_GENAU_EINS = exports.RECHNUNG_ANFRAGEN = exports.POSITION_FELDER = exports.KUNDE_FELDER = exports.INVOICE_SETUP_REQUIREMENTS = exports.RECHNUNG_EINHEITEN_CODES = exports.INVOICE_UNITS = exports.INVOICE_LANGUAGES = exports.EINVOICE_FORMATS = exports.DOC_TYPES = exports.INVOICE_LIST_STATUS = exports.CUSTOMER_TYPES = exports.VAT_RATES = exports.PRICE_MODES = exports.TAX_SCHEMES = exports.CREDIT_NOTE_REASONS = exports.INVOICE_ERROR_CODES = exports.RECHNUNG_AUFRUFE = exports.RECHNUNG_VERTRAG_VERSION = void 0;
|
|
22
|
+
exports.RECHNUNG_MINDESTENS_EINS = exports.RECHNUNG_GENAU_EINS = exports.RECHNUNG_ANFRAGEN = exports.PAYMENT_FELDER = exports.POSITION_FELDER = exports.KUNDE_FELDER = exports.INVOICE_SETUP_REQUIREMENTS = exports.RECHNUNG_EINHEITEN_CODES = exports.INVOICE_UNITS = exports.INVOICE_NOTICE_CODES = exports.INVOICE_PAYMENT_METHODS = exports.INVOICE_LANGUAGES = exports.EINVOICE_FORMATS = exports.DOC_TYPES = exports.INVOICE_LIST_STATUS = exports.CUSTOMER_TYPES = exports.VAT_RATES = exports.PRICE_MODES = exports.TAX_SCHEMES = exports.CREDIT_NOTE_REASONS = exports.INVOICE_ERROR_CODES = exports.RECHNUNG_AUFRUFE = exports.RECHNUNG_VERTRAG_VERSION = void 0;
|
|
23
23
|
exports.RECHNUNG_VERTRAG_VERSION = 1;
|
|
24
24
|
exports.RECHNUNG_AUFRUFE = [
|
|
25
25
|
'createCustomer',
|
|
@@ -35,6 +35,7 @@ exports.RECHNUNG_AUFRUFE = [
|
|
|
35
35
|
'getInvoiceXml',
|
|
36
36
|
'getInvoiceSetupStatus',
|
|
37
37
|
'listBrands',
|
|
38
|
+
'recordInvoicePayment',
|
|
38
39
|
];
|
|
39
40
|
/**
|
|
40
41
|
* Stabile Fehlercodes. Das Fremdsystem verzweigt am Code, der Text darf sich
|
|
@@ -60,6 +61,8 @@ exports.INVOICE_ERROR_CODES = [
|
|
|
60
61
|
'invoice_setup_incomplete',
|
|
61
62
|
'language_not_allowed',
|
|
62
63
|
'brand_not_found',
|
|
64
|
+
'not_payable',
|
|
65
|
+
'payment_exceeds_invoice',
|
|
63
66
|
];
|
|
64
67
|
/** Gruende einer Gutschrift. Der Server druckt den deutschen Anzeigetext. */
|
|
65
68
|
exports.CREDIT_NOTE_REASONS = [
|
|
@@ -83,6 +86,18 @@ exports.EINVOICE_FORMATS = ['ubl', 'cii'];
|
|
|
83
86
|
* oesterreichisch formatiert.
|
|
84
87
|
*/
|
|
85
88
|
exports.INVOICE_LANGUAGES = ['de', 'en'];
|
|
89
|
+
/**
|
|
90
|
+
* Wie eine Rechnung bezahlt wurde. Der Vermerk ist Buchhaltung, kein Beleg:
|
|
91
|
+
* `cash` wird gebucht, die Antwort traegt dann zusaetzlich den Hinweis
|
|
92
|
+
* `cash_receipt_required` — eine Barzahlung ist ein Barumsatz und braucht
|
|
93
|
+
* einen Beleg (§ 132a BAO), bei Registrierkassenpflicht ueber die Kasse.
|
|
94
|
+
*/
|
|
95
|
+
exports.INVOICE_PAYMENT_METHODS = ['transfer', 'card', 'online', 'cash'];
|
|
96
|
+
/**
|
|
97
|
+
* Hinweise, die eine erfolgreiche Antwort zusaetzlich tragen kann (`data.notice`).
|
|
98
|
+
* Sie sind keine Fehler: der Aufruf hat gewirkt, es gibt nur etwas zu wissen.
|
|
99
|
+
*/
|
|
100
|
+
exports.INVOICE_NOTICE_CODES = ['cash_receipt_required'];
|
|
86
101
|
/**
|
|
87
102
|
* Einheiten einer Position. Die API nimmt nur diese Schluessel an; gedruckt
|
|
88
103
|
* wird das Kuerzel in der Sprache der Rechnung (`RECHNUNG_TEXTE`,
|
|
@@ -264,6 +279,19 @@ exports.POSITION_FELDER = Object.freeze({
|
|
|
264
279
|
discountPct: { typ: 'number', pflicht: false, min: 0, max: 100, nachkomma: 2 },
|
|
265
280
|
});
|
|
266
281
|
const positionen = { typ: 'list', pflicht: true, min: 1, max: 500, eintrag: { typ: 'object', pflicht: true, felder: exports.POSITION_FELDER } };
|
|
282
|
+
/**
|
|
283
|
+
* Eine Zahlung zu einer Rechnung. Ohne `amountCents` gilt der volle
|
|
284
|
+
* Bruttobetrag, ohne `paidAt` der heutige Wiener Tag. `reference` ist die
|
|
285
|
+
* Zahlungskennung des Fremdsystems (z. B. `pi_3Q...`) — sie wird gespeichert,
|
|
286
|
+
* aber **nicht gedruckt**: die Rechnung wird aufbewahrt und vervielfaeltigt,
|
|
287
|
+
* und dem Empfaenger nuetzt sie nichts.
|
|
288
|
+
*/
|
|
289
|
+
exports.PAYMENT_FELDER = Object.freeze({
|
|
290
|
+
method: { typ: 'enum', pflicht: true, werte: exports.INVOICE_PAYMENT_METHODS },
|
|
291
|
+
amountCents: { typ: 'integer', pflicht: false, min: 1, max: 100_000_000 },
|
|
292
|
+
paidAt: datum(),
|
|
293
|
+
reference: text(100),
|
|
294
|
+
});
|
|
267
295
|
exports.RECHNUNG_ANFRAGEN = Object.freeze({
|
|
268
296
|
createCustomer: {
|
|
269
297
|
customer: { typ: 'object', pflicht: true, felder: exports.KUNDE_FELDER },
|
|
@@ -305,6 +333,12 @@ exports.RECHNUNG_ANFRAGEN = Object.freeze({
|
|
|
305
333
|
language: { typ: 'enum', pflicht: false, werte: exports.INVOICE_LANGUAGES },
|
|
306
334
|
/** Marke (Kennung aus `listBrands`); sonst die Standardmarke. */
|
|
307
335
|
brandId: id,
|
|
336
|
+
/**
|
|
337
|
+
* Schon bezahlt: die Zahlung entsteht in **derselben** Transaktion wie das
|
|
338
|
+
* Festschreiben. Sonst gaebe es einen Moment, in dem die Rechnung offen ist
|
|
339
|
+
* und ein sofort geholtes PDF Zahlungsinformationen traegt.
|
|
340
|
+
*/
|
|
341
|
+
payment: { typ: 'object', pflicht: false, felder: exports.PAYMENT_FELDER },
|
|
308
342
|
},
|
|
309
343
|
cancelInvoice: {
|
|
310
344
|
idempotencyKey: idempotencyKey(true),
|
|
@@ -343,6 +377,14 @@ exports.RECHNUNG_ANFRAGEN = Object.freeze({
|
|
|
343
377
|
},
|
|
344
378
|
getInvoiceSetupStatus: {},
|
|
345
379
|
listBrands: {},
|
|
380
|
+
recordInvoicePayment: {
|
|
381
|
+
idempotencyKey: idempotencyKey(true),
|
|
382
|
+
invoiceId: idPflicht,
|
|
383
|
+
method: { typ: 'enum', pflicht: true, werte: exports.INVOICE_PAYMENT_METHODS },
|
|
384
|
+
amountCents: { typ: 'integer', pflicht: false, min: 1, max: 100_000_000 },
|
|
385
|
+
paidAt: datum(),
|
|
386
|
+
reference: text(100),
|
|
387
|
+
},
|
|
346
388
|
});
|
|
347
389
|
/** Genau eines dieser Felder muss gesetzt sein (je Aufruf, je Gruppe). */
|
|
348
390
|
exports.RECHNUNG_GENAU_EINS = Object.freeze({
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* umhuellt, muss ihn weiterhin absetzen koennen.
|
|
14
14
|
*/
|
|
15
15
|
import type { TransportBodyFields } from './transport.js';
|
|
16
|
-
export declare const AUFRUFE: readonly ["activateCashregister", "cancelInvoice", "cancelReceipt", "createCreditNote", "createCustomer", "createCustomerCashregister", "createPartnerCustomer", "checkPartnerCustomerEmail", "createPartnerWebhook", "createPaymentLinkStripe", "createPrintJob", "createReceipt", "deletePartnerWebhook", "downloadDailyReport", "downloadReport", "endRegisterSession", "financeWebService", "generateFullReceiptId", "getCustomer", "getCustomerCredentials", "getCustomerSignatureStatus", "getFirstReceiptDate", "getInvoice", "getInvoicePdf", "getInvoiceSetupStatus", "getInvoiceXml", "getKasseSettings", "getPartnerCustomer", "getPartnerInfo", "getPrintJob", "getReceipt", "hobexPayApi", "hobexRefundApi", "issueInvoice", "listCustomerCashregisters", "listInvoices", "listBrands", "listMyArticleGroups", "listMyArticles", "listMyCashregisters", "listMyPrinters", "listMyReceipts", "listMyTipRecipients", "listPartnerCustomers", "listPartnerWebhookDeliveries", "listPartnerWebhooks", "listRegisterUsersForDevice", "listRegisterSessionsForDevice", "pairRegisterDevice", "registerPinLogin", "registerUserLogin", "renewRegisterSession", "requestCustomerSignature", "searchCustomers", "sendPartnerCustomerFonLink", "sendPartnerWebhookTest", "sendReceiptEmail", "setMyKasseSettings", "setMyRegisterDeviceSettings", "stripeCaptureIntent", "unpairRegisterDevice", "rotatePartnerWebhookSecret", "updateCustomer", "updatePartnerWebhook"];
|
|
16
|
+
export declare const AUFRUFE: readonly ["activateCashregister", "cancelInvoice", "cancelReceipt", "createCreditNote", "createCustomer", "createCustomerCashregister", "createPartnerCustomer", "checkPartnerCustomerEmail", "createPartnerWebhook", "createPaymentLinkStripe", "createPrintJob", "createReceipt", "deletePartnerWebhook", "downloadDailyReport", "downloadReport", "endRegisterSession", "financeWebService", "generateFullReceiptId", "getCustomer", "getCustomerCredentials", "getCustomerSignatureStatus", "getFirstReceiptDate", "getInvoice", "getInvoicePdf", "getInvoiceSetupStatus", "getInvoiceXml", "getKasseSettings", "getPartnerCustomer", "getPartnerInfo", "getPrintJob", "getReceipt", "hobexPayApi", "hobexRefundApi", "issueInvoice", "listCustomerCashregisters", "listInvoices", "listBrands", "listMyArticleGroups", "listMyArticles", "listMyCashregisters", "listMyPrinters", "listMyReceipts", "listMyTipRecipients", "listPartnerCustomers", "listPartnerWebhookDeliveries", "listPartnerWebhooks", "listRegisterUsersForDevice", "listRegisterSessionsForDevice", "pairRegisterDevice", "recordInvoicePayment", "registerPinLogin", "registerUserLogin", "renewRegisterSession", "requestCustomerSignature", "searchCustomers", "sendPartnerCustomerFonLink", "sendPartnerWebhookTest", "sendReceiptEmail", "setMyKasseSettings", "setMyRegisterDeviceSettings", "stripeCaptureIntent", "unpairRegisterDevice", "rotatePartnerWebhookSecret", "updateCustomer", "updatePartnerWebhook"];
|
|
17
17
|
export type Aufruf = typeof AUFRUFE[number];
|
|
18
18
|
/** Wie [KasseneckTransport], nur mit bekanntem Aufrufnamen. Nicht exportiert nach aussen. */
|
|
19
19
|
export type InternerTransport = <T = unknown>(functionName: Aufruf, params?: Record<string, unknown>, extraBodyFields?: TransportBodyFields, secretParams?: readonly string[]) => Promise<T>;
|
|
@@ -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';
|
|
@@ -22,5 +22,5 @@ export { HpsClarifyTimeoutError, HpsConnectException, HpsConnectTerminalError, H
|
|
|
22
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, 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, } 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, NOT_FOUND_HTTP_STATUS, TERMINAL_BUSY_HTTP_STATUS, TRANSACTION_CANCELED_CODE, isApproved, isCanceled, isConclusive, isConclusiveAsStatus, isInProgress, isNoStatement, isNotAbortable, needsReversal, normalizeHpsCode, isTechnicalError, isUnknownCode, parseHpsTransactionResponse, } from './transaction-response.js';
|
|
26
26
|
export { hobexReceiptFromHps } from './receipt.js';
|
|
@@ -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. */
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { HpsClarifyTimeoutError, HpsConnectException, HpsConnectTerminalError, HpsPreflightError, } from './errors.js';
|
|
2
2
|
import { newHpsTransactionId } from './transaction-id.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';
|
|
3
|
+
import { hpsCodeInfo, hpsCodeReason, isApproved, isCanceled, isConclusive, isConclusiveAsStatus, isHostUncertain, isNoStatement, isNotAbortable, isTechnicalError, isUnknownCode, needsReversal, 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
|
+
/**
|
|
7
|
+
* Teiler fuer das Budget des nachgeschickten Stornos: ein Drittel, bei der
|
|
8
|
+
* Vorgabe 30 s. Ein Storno geht ueber den Host und darf Sekunden brauchen.
|
|
9
|
+
*/
|
|
10
|
+
const REVERSAL_BUDGET_DIVISOR = 3;
|
|
6
11
|
export function createHpsPayments(client, target, options = {}) {
|
|
7
12
|
const resolveBudgetMs = options.resolveBudgetMs ?? 90_000;
|
|
8
13
|
const maxBackoffMs = options.maxBackoffMs ?? 10_000;
|
|
@@ -11,6 +16,7 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
11
16
|
const now = options.now ?? Date.now;
|
|
12
17
|
const observer = options.observer;
|
|
13
18
|
const abortBudgetMs = Math.trunc(resolveBudgetMs / ABORT_BUDGET_DIVISOR);
|
|
19
|
+
const reversalBudgetMs = Math.trunc(resolveBudgetMs / REVERSAL_BUDGET_DIVISOR);
|
|
14
20
|
function emit(kind, message, transactionId) {
|
|
15
21
|
if (!observer)
|
|
16
22
|
return;
|
|
@@ -67,8 +73,12 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
67
73
|
const info = hpsCodeInfo(res.responseCode);
|
|
68
74
|
const was = info.reason === 'internalError'
|
|
69
75
|
? 'interner Fehler des Terminals'
|
|
70
|
-
:
|
|
71
|
-
|
|
76
|
+
: info.reason === 'approvedWithCondition'
|
|
77
|
+
? 'Genehmigung mit Vorbehalt'
|
|
78
|
+
: info.reason === 'hostTimeout'
|
|
79
|
+
? 'keine rechtzeitige Antwort des hobex-Hosts, das Terminal storniert nicht selbst'
|
|
80
|
+
: 'Stoerung zwischen Terminal und hobex-Host, das Terminal storniert nicht selbst';
|
|
81
|
+
return `Terminal meldet ${was} (${res.responseCode} "${info.title}")`;
|
|
72
82
|
}
|
|
73
83
|
/**
|
|
74
84
|
* Der Grund eines offenen Ausgangs. [antwort] ist die Antwort auf die
|
|
@@ -341,6 +351,37 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
341
351
|
emit('resolved', steps[steps.length - 1], id);
|
|
342
352
|
return { outcome: 'declined', transactionId: id, response: res, reason: 'aborted', steps: [...steps] };
|
|
343
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* Schickt zu einer Zahlung, deren Host-Antwort ausblieb ([anlass] traegt
|
|
356
|
+
* `sendReversal`), GENAU EINMAL ein Storno nach. Ergebnis nur, wenn das
|
|
357
|
+
* Terminal es mit `'0'` quittiert; sonst `null`. Ohne [amountCents]
|
|
358
|
+
* (Gutschrift) wird nichts gesendet.
|
|
359
|
+
*/
|
|
360
|
+
async function sendReversal(id, steps, elapsedMs, amountCents, anlass) {
|
|
361
|
+
if (amountCents === undefined) {
|
|
362
|
+
steps.push('Kein Storno nachgeschickt -- die Aufhebung einer Gutschrift laesst TECS nicht zu');
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
emit('resolving', `Storno wird nachgeschickt (${anlass.responseCode})`, id);
|
|
366
|
+
let res;
|
|
367
|
+
try {
|
|
368
|
+
res = await withinBudget(elapsedMs, () => client.cancel({ ...target, transactionId: id, amountCents }), reversalBudgetMs);
|
|
369
|
+
}
|
|
370
|
+
catch (e) {
|
|
371
|
+
steps.push(`Storno nachgeschickt, aber nicht bestaetigt (${describe(e)}) -- ob es wirkte, ist offen, Ausgang wird abgefragt`);
|
|
372
|
+
noteUnexpected(e, id);
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
if (!isApproved(res)) {
|
|
376
|
+
const titel = hpsCodeInfo(res.responseCode)?.title;
|
|
377
|
+
steps.push(`Storno nachgeschickt, nicht bestaetigt (${res.responseCode ?? 'ohne Ergebniscode'}`
|
|
378
|
+
+ `${titel === undefined ? '' : ` "${titel}"`}) -- Ausgang wird abgefragt`);
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
steps.push('Storno nachgeschickt und bestaetigt -- die Zahlung ist aufgehoben, es ist nichts belastet');
|
|
382
|
+
emit('resolved', steps[steps.length - 1], id);
|
|
383
|
+
return { outcome: 'declined', transactionId: id, response: res, reason: 'voidedAfterHostFault', steps: [...steps] };
|
|
384
|
+
}
|
|
344
385
|
/**
|
|
345
386
|
* Klaert einen offenen Ausgang: erst abbrechen, dann abfragen -- bis das
|
|
346
387
|
* Terminal etwas sagt oder das Budget aufgebraucht ist.
|
|
@@ -353,7 +394,7 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
353
394
|
* ([isHostUncertain]), bekommt `9027` diesen Aussagewert gerade NICHT --
|
|
354
395
|
* siehe Klassendoku, "Stoerung beim Host".
|
|
355
396
|
*/
|
|
356
|
-
async function resolve(id, steps, antwort) {
|
|
397
|
+
async function resolve(id, steps, antwort, stornoCents) {
|
|
357
398
|
emit('resolving', 'Ausgang offen, Klaerung laeuft', id);
|
|
358
399
|
const start = now();
|
|
359
400
|
const elapsedMs = () => now() - start;
|
|
@@ -362,6 +403,9 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
362
403
|
// Statusabfrage sie nennt und danach 9027 kommt.
|
|
363
404
|
let stoerungsAntwort = antwort && isHostUncertain(antwort) ? antwort : undefined;
|
|
364
405
|
let letzteAntwort = antwort;
|
|
406
|
+
// Das Storno wird hoechstens einmal erwogen; gesendet ist es nur mit Betrag.
|
|
407
|
+
let stornoErwogen = false;
|
|
408
|
+
let stornoGesendet = false;
|
|
365
409
|
if (stoerungsAntwort === undefined) {
|
|
366
410
|
const aborted = await tryAbort(id, steps, elapsedMs);
|
|
367
411
|
if (aborted)
|
|
@@ -372,6 +416,13 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
372
416
|
// Host. Ein quittierter Abbruch bewiese nur, dass am Terminal nichts mehr
|
|
373
417
|
// laeuft, nicht, dass der Host nichts belastet hat.
|
|
374
418
|
steps.push('Kein Abbruchversuch -- das Terminal hat den Vorgang mit einer Stoerung beim hobex-Host beendet');
|
|
419
|
+
if (needsReversal(stoerungsAntwort)) {
|
|
420
|
+
stornoErwogen = true;
|
|
421
|
+
stornoGesendet = stornoCents !== undefined;
|
|
422
|
+
const storniert = await sendReversal(id, steps, elapsedMs, stornoCents, stoerungsAntwort);
|
|
423
|
+
if (storniert)
|
|
424
|
+
return storniert;
|
|
425
|
+
}
|
|
375
426
|
}
|
|
376
427
|
let wait = 0;
|
|
377
428
|
let transportFailures = 0;
|
|
@@ -406,6 +457,24 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
406
457
|
if (isHostUncertain(status))
|
|
407
458
|
stoerungsAntwort ??= status;
|
|
408
459
|
const hostUngewiss = stoerungsAntwort !== undefined;
|
|
460
|
+
if (stornoGesendet && isCanceled(status)) {
|
|
461
|
+
steps.push(`Status: Zahlung aufgehoben (${TRANSACTION_CANCELED_CODE}) -- das nachgeschickte Storno hat gegriffen, `
|
|
462
|
+
+ 'es ist nichts belastet');
|
|
463
|
+
emit('resolved', steps[steps.length - 1], id);
|
|
464
|
+
return { outcome: 'declined', transactionId: id, response: status, reason: 'voidedAfterHostFault', steps: [...steps] };
|
|
465
|
+
}
|
|
466
|
+
if (!stornoErwogen && needsReversal(status)) {
|
|
467
|
+
// Die Zahlung selbst kam ohne Antwort zurueck, erst die Statusabfrage
|
|
468
|
+
// nennt die ausgebliebene Host-Antwort -- dasselbe Storno wie oben.
|
|
469
|
+
steps.push(statusOhneErgebnis(status));
|
|
470
|
+
stornoErwogen = true;
|
|
471
|
+
stornoGesendet = stornoCents !== undefined;
|
|
472
|
+
const storniert = await sendReversal(id, steps, elapsedMs, stornoCents, status);
|
|
473
|
+
if (storniert)
|
|
474
|
+
return storniert;
|
|
475
|
+
wait = nextWait(wait);
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
409
478
|
// Auf die Statusabfrage entscheidet nur ein Code, der den gesuchten
|
|
410
479
|
// Vorgang beschreibt -- nicht einer, der diese Abfrage abweist
|
|
411
480
|
// ([isConclusiveAsStatus]). Nach einer Stoerung beim Host entscheidet nur
|
|
@@ -607,7 +676,7 @@ export function createHpsPayments(client, target, options = {}) {
|
|
|
607
676
|
return settled;
|
|
608
677
|
steps.push(offeneAntwort(res));
|
|
609
678
|
}
|
|
610
|
-
return resolve(id, steps, res);
|
|
679
|
+
return resolve(id, steps, res, paymentOptions.amountCents);
|
|
611
680
|
}
|
|
612
681
|
/**
|
|
613
682
|
* 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[];
|