@coderoftime/fints 0.1.0 → 0.3.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 +519 -1
- package/README.md +138 -40
- package/dist/client.d.ts +49 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +242 -0
- package/dist/client.js.map +1 -0
- package/dist/core/account.d.ts +46 -7
- package/dist/core/account.d.ts.map +1 -1
- package/dist/core/account.js +143 -24
- package/dist/core/account.js.map +1 -1
- package/dist/core/amount.d.ts +15 -4
- package/dist/core/amount.d.ts.map +1 -1
- package/dist/core/amount.js +46 -9
- package/dist/core/amount.js.map +1 -1
- package/dist/core/balance.d.ts +4 -0
- package/dist/core/balance.d.ts.map +1 -1
- package/dist/core/balance.js +11 -5
- package/dist/core/balance.js.map +1 -1
- package/dist/core/bank-info.d.ts +21 -4
- package/dist/core/bank-info.d.ts.map +1 -1
- package/dist/core/bank-info.js +26 -5
- package/dist/core/bank-info.js.map +1 -1
- package/dist/core/client.d.ts +81 -19
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/config.d.ts +40 -20
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +85 -30
- package/dist/core/config.js.map +1 -1
- package/dist/core/errors.d.ts +100 -26
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +136 -53
- package/dist/core/errors.js.map +1 -1
- package/dist/core/feedback.d.ts +11 -1
- package/dist/core/feedback.d.ts.map +1 -1
- package/dist/core/feedback.js +14 -2
- package/dist/core/feedback.js.map +1 -1
- package/dist/core/transaction.d.ts +11 -1
- package/dist/core/transaction.d.ts.map +1 -1
- package/dist/core/transaction.js +21 -4
- package/dist/core/transaction.js.map +1 -1
- package/dist/core/transport.d.ts +48 -6
- package/dist/core/transport.d.ts.map +1 -1
- package/dist/core/transport.js +126 -29
- package/dist/core/transport.js.map +1 -1
- package/dist/formats/camt.d.ts +28 -5
- package/dist/formats/camt.d.ts.map +1 -1
- package/dist/formats/camt.js +132 -50
- package/dist/formats/camt.js.map +1 -1
- package/dist/formats/mt940.d.ts +29 -5
- package/dist/formats/mt940.d.ts.map +1 -1
- package/dist/formats/mt940.js +209 -113
- package/dist/formats/mt940.js.map +1 -1
- package/dist/formats/readers.d.ts +6 -2
- package/dist/formats/readers.d.ts.map +1 -1
- package/dist/formats/readers.js +28 -29
- package/dist/formats/readers.js.map +1 -1
- package/dist/formats/record-budget.d.ts +7 -0
- package/dist/formats/record-budget.d.ts.map +1 -0
- package/dist/formats/record-budget.js +17 -0
- package/dist/formats/record-budget.js.map +1 -0
- package/dist/formats/sepa-purpose.d.ts +9 -0
- package/dist/formats/sepa-purpose.d.ts.map +1 -1
- package/dist/formats/sepa-purpose.js +32 -2
- package/dist/formats/sepa-purpose.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/session/app-approval.d.ts +11 -5
- package/dist/session/app-approval.d.ts.map +1 -1
- package/dist/session/app-approval.js +23 -12
- package/dist/session/app-approval.js.map +1 -1
- package/dist/session/dialog-runner.d.ts +11 -11
- package/dist/session/dialog-runner.d.ts.map +1 -1
- package/dist/session/dialog-runner.js +34 -19
- package/dist/session/dialog-runner.js.map +1 -1
- package/dist/session/login-guard.d.ts +54 -49
- package/dist/session/login-guard.d.ts.map +1 -1
- package/dist/session/login-guard.js +170 -83
- package/dist/session/login-guard.js.map +1 -1
- package/dist/session/sepa-accounts.d.ts +20 -14
- package/dist/session/sepa-accounts.d.ts.map +1 -1
- package/dist/session/sepa-accounts.js +62 -46
- package/dist/session/sepa-accounts.js.map +1 -1
- package/dist/session/session-setup.d.ts +41 -24
- package/dist/session/session-setup.d.ts.map +1 -1
- package/dist/session/session-setup.js +106 -63
- package/dist/session/session-setup.js.map +1 -1
- package/dist/session/session-state.d.ts +10 -6
- package/dist/session/session-state.d.ts.map +1 -1
- package/dist/session/session-state.js +72 -18
- package/dist/session/session-state.js.map +1 -1
- package/dist/session/signed-dialog.d.ts +83 -0
- package/dist/session/signed-dialog.d.ts.map +1 -0
- package/dist/session/signed-dialog.js +185 -0
- package/dist/session/signed-dialog.js.map +1 -0
- package/dist/session/tan-selection.d.ts +26 -18
- package/dist/session/tan-selection.d.ts.map +1 -1
- package/dist/session/tan-selection.js +44 -33
- package/dist/session/tan-selection.js.map +1 -1
- package/dist/session/user-prompts.d.ts +14 -5
- package/dist/session/user-prompts.d.ts.map +1 -1
- package/dist/session/user-prompts.js +53 -18
- package/dist/session/user-prompts.js.map +1 -1
- package/dist/tan/challenge.d.ts +47 -24
- package/dist/tan/challenge.d.ts.map +1 -1
- package/dist/tan/challenge.js +56 -51
- package/dist/tan/challenge.js.map +1 -1
- package/dist/tan/flicker.d.ts +11 -1
- package/dist/tan/flicker.d.ts.map +1 -1
- package/dist/tan/flicker.js +70 -24
- package/dist/tan/flicker.js.map +1 -1
- package/dist/tan/tan-image.d.ts +6 -2
- package/dist/tan/tan-image.d.ts.map +1 -1
- package/dist/tan/tan-image.js +14 -6
- package/dist/tan/tan-image.js.map +1 -1
- package/dist/tan/tan-models.d.ts +14 -2
- package/dist/tan/tan-models.d.ts.map +1 -1
- package/dist/tan/tan-models.js +24 -7
- package/dist/tan/tan-models.js.map +1 -1
- package/dist/util/arguments.d.ts +13 -0
- package/dist/util/arguments.d.ts.map +1 -0
- package/dist/util/arguments.js +40 -0
- package/dist/util/arguments.js.map +1 -0
- package/dist/util/async.d.ts +4 -3
- package/dist/util/async.d.ts.map +1 -1
- package/dist/util/async.js +6 -7
- package/dist/util/async.js.map +1 -1
- package/dist/util/bytes.d.ts +3 -4
- package/dist/util/bytes.d.ts.map +1 -1
- package/dist/util/bytes.js +13 -21
- package/dist/util/bytes.js.map +1 -1
- package/dist/util/call-context.d.ts.map +1 -1
- package/dist/util/call-context.js +3 -2
- package/dist/util/call-context.js.map +1 -1
- package/dist/util/dates.d.ts +6 -20
- package/dist/util/dates.d.ts.map +1 -1
- package/dist/util/dates.js +58 -28
- package/dist/util/dates.js.map +1 -1
- package/dist/util/errors.d.ts +13 -3
- package/dist/util/errors.d.ts.map +1 -1
- package/dist/util/errors.js +19 -6
- package/dist/util/errors.js.map +1 -1
- package/dist/util/immutable-map.d.ts +5 -2
- package/dist/util/immutable-map.d.ts.map +1 -1
- package/dist/util/immutable-map.js +15 -12
- package/dist/util/immutable-map.js.map +1 -1
- package/dist/util/inspect.d.ts +11 -0
- package/dist/util/inspect.d.ts.map +1 -0
- package/dist/util/inspect.js +14 -0
- package/dist/util/inspect.js.map +1 -0
- package/dist/util/json.d.ts +4 -0
- package/dist/util/json.d.ts.map +1 -1
- package/dist/util/json.js +18 -0
- package/dist/util/json.js.map +1 -1
- package/dist/util/text.d.ts +38 -5
- package/dist/util/text.d.ts.map +1 -1
- package/dist/util/text.js +90 -16
- package/dist/util/text.js.map +1 -1
- package/dist/util/xml.d.ts +9 -6
- package/dist/util/xml.d.ts.map +1 -1
- package/dist/util/xml.js +129 -56
- package/dist/util/xml.js.map +1 -1
- package/dist/v3/account-extension.d.ts.map +1 -1
- package/dist/v3/account-extension.js +7 -10
- package/dist/v3/account-extension.js.map +1 -1
- package/dist/v3/bank-connection.d.ts +54 -0
- package/dist/v3/bank-connection.d.ts.map +1 -0
- package/dist/v3/bank-connection.js +118 -0
- package/dist/v3/bank-connection.js.map +1 -0
- package/dist/v3/dialog.d.ts +84 -46
- package/dist/v3/dialog.d.ts.map +1 -1
- package/dist/v3/dialog.js +174 -60
- package/dist/v3/dialog.js.map +1 -1
- package/dist/v3/message.d.ts +32 -11
- package/dist/v3/message.d.ts.map +1 -1
- package/dist/v3/message.js +144 -83
- package/dist/v3/message.js.map +1 -1
- package/dist/v3/parameter-store.d.ts +111 -0
- package/dist/v3/parameter-store.d.ts.map +1 -0
- package/dist/v3/parameter-store.js +197 -0
- package/dist/v3/parameter-store.js.map +1 -0
- package/dist/v3/parameters.d.ts +44 -17
- package/dist/v3/parameters.d.ts.map +1 -1
- package/dist/v3/parameters.js +136 -60
- package/dist/v3/parameters.js.map +1 -1
- package/dist/v3/protocol.d.ts +50 -50
- package/dist/v3/protocol.d.ts.map +1 -1
- package/dist/v3/protocol.js +91 -210
- package/dist/v3/protocol.js.map +1 -1
- package/dist/v3/queries.d.ts +51 -0
- package/dist/v3/queries.d.ts.map +1 -0
- package/dist/v3/queries.js +128 -0
- package/dist/v3/queries.js.map +1 -0
- package/dist/v3/segments.d.ts +43 -19
- package/dist/v3/segments.d.ts.map +1 -1
- package/dist/v3/segments.js +84 -33
- package/dist/v3/segments.js.map +1 -1
- package/dist/v3/syntax.d.ts +68 -13
- package/dist/v3/syntax.d.ts.map +1 -1
- package/dist/v3/syntax.js +183 -102
- package/dist/v3/syntax.js.map +1 -1
- package/package.json +10 -6
- package/src/client.ts +293 -0
- package/src/core/account.ts +153 -24
- package/src/core/amount.ts +45 -10
- package/src/core/balance.ts +12 -5
- package/src/core/bank-info.ts +29 -5
- package/src/core/client.ts +83 -19
- package/src/core/config.ts +95 -35
- package/src/core/errors.ts +148 -54
- package/src/core/feedback.ts +16 -2
- package/src/core/transaction.ts +22 -4
- package/src/core/transport.ts +153 -32
- package/src/formats/camt.ts +153 -45
- package/src/formats/mt940.ts +244 -122
- package/src/formats/readers.ts +28 -31
- package/src/formats/record-budget.ts +18 -0
- package/src/formats/sepa-purpose.ts +29 -2
- package/src/index.ts +4 -2
- package/src/session/app-approval.ts +51 -38
- package/src/session/dialog-runner.ts +61 -31
- package/src/session/login-guard.ts +189 -87
- package/src/session/sepa-accounts.ts +63 -45
- package/src/session/session-setup.ts +105 -68
- package/src/session/session-state.ts +74 -23
- package/src/session/signed-dialog.ts +222 -0
- package/src/session/tan-selection.ts +50 -39
- package/src/session/user-prompts.ts +62 -18
- package/src/tan/challenge.ts +87 -64
- package/src/tan/flicker.ts +72 -23
- package/src/tan/tan-image.ts +15 -6
- package/src/tan/tan-models.ts +26 -7
- package/src/util/arguments.ts +40 -0
- package/src/util/async.ts +10 -9
- package/src/util/bytes.ts +14 -20
- package/src/util/call-context.ts +3 -2
- package/src/util/dates.ts +64 -33
- package/src/util/errors.ts +22 -6
- package/src/util/immutable-map.ts +15 -12
- package/src/util/inspect.ts +14 -0
- package/src/util/json.ts +16 -0
- package/src/util/text.ts +87 -15
- package/src/util/xml.ts +130 -52
- package/src/v3/account-extension.ts +7 -12
- package/src/v3/bank-connection.ts +126 -0
- package/src/v3/dialog.ts +186 -92
- package/src/v3/message.ts +140 -79
- package/src/v3/parameter-store.ts +226 -0
- package/src/v3/parameters.ts +166 -66
- package/src/v3/protocol.ts +103 -253
- package/src/v3/queries.ts +198 -0
- package/src/v3/segments.ts +86 -31
- package/src/v3/syntax.ts +172 -93
- package/dist/session/bank-connection.d.ts +0 -28
- package/dist/session/bank-connection.d.ts.map +0 -1
- package/dist/session/bank-connection.js +0 -48
- package/dist/session/bank-connection.js.map +0 -1
- package/dist/session/parameter-data.d.ts +0 -83
- package/dist/session/parameter-data.d.ts.map +0 -1
- package/dist/session/parameter-data.js +0 -68
- package/dist/session/parameter-data.js.map +0 -1
- package/dist/session/pin-tan-client.d.ts +0 -54
- package/dist/session/pin-tan-client.d.ts.map +0 -1
- package/dist/session/pin-tan-client.js +0 -153
- package/dist/session/pin-tan-client.js.map +0 -1
- package/dist/session/pin-tan-dialog.d.ts +0 -71
- package/dist/session/pin-tan-dialog.d.ts.map +0 -1
- package/dist/session/pin-tan-dialog.js +0 -124
- package/dist/session/pin-tan-dialog.js.map +0 -1
- package/dist/session/protocol.d.ts +0 -209
- package/dist/session/protocol.d.ts.map +0 -1
- package/dist/session/protocol.js +0 -75
- package/dist/session/protocol.js.map +0 -1
- package/dist/session/response-handler.d.ts +0 -48
- package/dist/session/response-handler.d.ts.map +0 -1
- package/dist/session/response-handler.js +0 -121
- package/dist/session/response-handler.js.map +0 -1
- package/dist/v3/client.d.ts +0 -24
- package/dist/v3/client.d.ts.map +0 -1
- package/dist/v3/client.js +0 -16
- package/dist/v3/client.js.map +0 -1
- package/src/session/bank-connection.ts +0 -51
- package/src/session/parameter-data.ts +0 -137
- package/src/session/pin-tan-client.ts +0 -186
- package/src/session/pin-tan-dialog.ts +0 -160
- package/src/session/protocol.ts +0 -311
- package/src/session/response-handler.ts +0 -130
- package/src/v3/client.ts +0 -29
package/src/v3/parameters.ts
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
import { Account } from '../core/account.js';
|
|
4
4
|
import type { Balance } from '../core/balance.js';
|
|
5
5
|
import { BankInfo } from '../core/bank-info.js';
|
|
6
|
-
import {
|
|
6
|
+
import { FinTsProtocolError } from '../core/errors.js';
|
|
7
7
|
import { TanMethod, tanMediumRequirementOf } from '../tan/tan-models.js';
|
|
8
|
-
import { base64Decode, base64Encode,
|
|
9
|
-
import { tryParseInt } from '../util/text.js';
|
|
8
|
+
import { base64Decode, base64Encode, concatBytes } from '../util/bytes.js';
|
|
9
|
+
import { nonBlank, trimBlanks, tryParseInt } from '../util/text.js';
|
|
10
10
|
import { AccountExtension } from './account-extension.js';
|
|
11
11
|
import { parseSegments, type Segment } from './syntax.js';
|
|
12
12
|
|
|
@@ -33,6 +33,12 @@ const Hibpa = {
|
|
|
33
33
|
maxTimeout: 9,
|
|
34
34
|
} as const;
|
|
35
35
|
|
|
36
|
+
/** The size or time at `index` of `hibpa`, a field of at most four digits; `undefined` for a larger value, as for none. */
|
|
37
|
+
function hibpaLimit(hibpa: Segment, index: number): number | undefined {
|
|
38
|
+
const value = hibpa.integer(index);
|
|
39
|
+
return value !== undefined && value <= 9999 ? value : undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
36
42
|
/**
|
|
37
43
|
* The data element of every parameter segment (`HI…S`) that holds its specific parameters, after the maximum number of
|
|
38
44
|
* orders, the minimum number of signatures and the security class.
|
|
@@ -46,20 +52,35 @@ function defined<T>(values: readonly (T | undefined)[]): T[] {
|
|
|
46
52
|
return values.filter((v): v is T => v !== undefined);
|
|
47
53
|
}
|
|
48
54
|
|
|
55
|
+
/** `bpd`, or `FinTsProtocolError` if the bank has not sent its parameter data yet. */
|
|
56
|
+
export function requireBankParameters(bpd: Bpd3 | undefined): Bpd3 {
|
|
57
|
+
if (bpd === undefined) throw new FinTsProtocolError('No bank parameter data available');
|
|
58
|
+
return bpd;
|
|
59
|
+
}
|
|
60
|
+
|
|
49
61
|
/** FinTS 3.0 bank parameter data (BPD): HIBPA and the parameter segments (`HI…S`), parsed once. */
|
|
50
|
-
export class Bpd3
|
|
62
|
+
export class Bpd3 {
|
|
51
63
|
readonly bankInfo: BankInfo;
|
|
52
64
|
|
|
53
65
|
/** The two-step TAN methods of HITANS versions 6 and 7, by security function. */
|
|
54
66
|
private readonly offeredMethods: ReadonlyMap<string, OfferedMethod>;
|
|
55
67
|
|
|
68
|
+
/** Two-step TAN methods the bank offers and the client can run. */
|
|
56
69
|
readonly tanMethods: readonly TanMethod[];
|
|
70
|
+
|
|
71
|
+
/** Security functions of the two-step methods the bank offers in a form the client cannot run. */
|
|
57
72
|
readonly unsupportedTanMethods: readonly string[];
|
|
58
73
|
readonly pinTan: PinTanParameters;
|
|
59
74
|
|
|
60
|
-
/** camt formats the bank offers for HKCAZ. */
|
|
75
|
+
/** The camt.052 formats the bank offers for HKCAZ, the one camt message the client reads. */
|
|
61
76
|
readonly camtFormats: readonly string[];
|
|
62
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Whether an international account (KTI) may name the national account as well: HISPAS does not rule it out
|
|
80
|
+
* ("Nationale Kontoverbindung erlaubt").
|
|
81
|
+
*/
|
|
82
|
+
readonly nationalAccountAllowed: boolean;
|
|
83
|
+
|
|
63
84
|
/** Versions of each parameter segment, by segment type. */
|
|
64
85
|
private readonly versions: ReadonlyMap<string, ReadonlySet<number>>;
|
|
65
86
|
|
|
@@ -69,20 +90,19 @@ export class Bpd3 implements BankParameters {
|
|
|
69
90
|
*/
|
|
70
91
|
readonly dialogLanguage: number;
|
|
71
92
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
) {
|
|
93
|
+
/** The segments as the saved state keeps them. */
|
|
94
|
+
readonly encoded: string;
|
|
95
|
+
|
|
96
|
+
private constructor(segments: readonly Segment[], hibpa: Segment, fallbackBankCode: string) {
|
|
77
97
|
this.bankInfo = new BankInfo({
|
|
78
98
|
bankCode: hibpa.text(Hibpa.bankId, 1) ?? fallbackBankCode,
|
|
79
99
|
name: hibpa.text(Hibpa.bankName),
|
|
80
100
|
bpdVersion: hibpa.integer(Hibpa.version) ?? 0,
|
|
81
101
|
languages: defined(hibpa.texts(Hibpa.languages)),
|
|
82
102
|
supportedVersions: defined(hibpa.texts(Hibpa.fintsVersions)),
|
|
83
|
-
maxMessageSize: hibpa
|
|
84
|
-
minTimeout: hibpa
|
|
85
|
-
maxTimeout: hibpa
|
|
103
|
+
maxMessageSize: hibpaLimit(hibpa, Hibpa.maxMessageSize),
|
|
104
|
+
minTimeout: hibpaLimit(hibpa, Hibpa.minTimeout),
|
|
105
|
+
maxTimeout: hibpaLimit(hibpa, Hibpa.maxTimeout),
|
|
86
106
|
});
|
|
87
107
|
this.offeredMethods = parseTanMethods(segments);
|
|
88
108
|
const offered = [...this.offeredMethods.values()];
|
|
@@ -94,23 +114,18 @@ export class Bpd3 implements BankParameters {
|
|
|
94
114
|
this.camtFormats = segments
|
|
95
115
|
.filter((s) => s.type === 'HICAZS')
|
|
96
116
|
.flatMap((s) => defined(s.texts(parameterGroup)))
|
|
97
|
-
.filter((format) => format.includes('camt'));
|
|
117
|
+
.filter((format) => format.includes('camt.052'));
|
|
118
|
+
this.nationalAccountAllowed = highest(segments, 'HISPAS')?.text(parameterGroup, 1) !== 'N';
|
|
98
119
|
this.versions = versionsByType(segments);
|
|
99
120
|
this.dialogLanguage = dialogLanguageOf(segments, this.bankInfo);
|
|
121
|
+
this.encoded = encodeSegments(segments);
|
|
100
122
|
}
|
|
101
123
|
|
|
124
|
+
/** The version of the parameter data, which the client reports back so that the bank sends newer ones only. */
|
|
102
125
|
get version(): number {
|
|
103
126
|
return this.bankInfo.bpdVersion;
|
|
104
127
|
}
|
|
105
128
|
|
|
106
|
-
get pinLength(): { min: number | undefined; max: number | undefined } {
|
|
107
|
-
return this.pinTan.pinLength;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
allowsWithPinTan(transaction: string): boolean {
|
|
111
|
-
return this.pinTan.allowsWithPinTan(transaction);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
129
|
/** The BPD in `all`, or `undefined` if they contain no HIBPA segment. */
|
|
115
130
|
static fromSegments(all: readonly Segment[], fallbackBankCode: string): Bpd3 | undefined {
|
|
116
131
|
const segments = all.filter(isParameterSegment);
|
|
@@ -123,8 +138,13 @@ export class Bpd3 implements BankParameters {
|
|
|
123
138
|
return `HI${transaction.substring(2)}S`;
|
|
124
139
|
}
|
|
125
140
|
|
|
141
|
+
/** The highest version of `transaction` that the bank offers and the client `implemented`. */
|
|
126
142
|
bestVersion(transaction: string, implemented: ReadonlySet<number>): number | undefined {
|
|
127
|
-
|
|
143
|
+
let best: number | undefined;
|
|
144
|
+
for (const version of this.versions.get(Bpd3.parameterName(transaction)) ?? []) {
|
|
145
|
+
if (implemented.has(version) && (best === undefined || version > best)) best = version;
|
|
146
|
+
}
|
|
147
|
+
return best;
|
|
128
148
|
}
|
|
129
149
|
|
|
130
150
|
/** The version of the HKTAN segments that go with `method`; `undefined` for a method the bank does not offer. */
|
|
@@ -140,13 +160,23 @@ function dialogLanguageOf(segments: readonly Segment[], bankInfo: BankInfo): num
|
|
|
140
160
|
return bankInfo.languages.map((l) => tryParseInt(l)).find((l) => l !== undefined && languages.has(l)) ?? 0;
|
|
141
161
|
}
|
|
142
162
|
|
|
163
|
+
const generalParameterSegments = new Set(['HIBPA', 'HIKOM', 'HISHV', 'HIKPV']);
|
|
164
|
+
|
|
143
165
|
function isParameterSegment(s: Segment): boolean {
|
|
144
166
|
const type = s.type;
|
|
145
|
-
if (
|
|
146
|
-
|
|
167
|
+
if (generalParameterSegments.has(type)) return true;
|
|
168
|
+
// Return codes for single segments end with an S, as parameter segments do.
|
|
169
|
+
if (type === 'HIRMS') return false;
|
|
147
170
|
return type.length >= 5 && (type.startsWith('HI') || type.startsWith('DI')) && type.endsWith('S');
|
|
148
171
|
}
|
|
149
172
|
|
|
173
|
+
/** The segment of `type` with the highest version in `segments`. */
|
|
174
|
+
function highest(segments: readonly Segment[], type: string): Segment | undefined {
|
|
175
|
+
let found: Segment | undefined;
|
|
176
|
+
for (const s of segments) if (s.type === type && (found === undefined || s.version > found.version)) found = s;
|
|
177
|
+
return found;
|
|
178
|
+
}
|
|
179
|
+
|
|
150
180
|
function versionsByType(segments: readonly Segment[]): Map<string, Set<number>> {
|
|
151
181
|
const versions = new Map<string, Set<number>>();
|
|
152
182
|
for (const s of segments) {
|
|
@@ -256,17 +286,14 @@ function parseHitans(s: Segment): OfferedMethod[] {
|
|
|
256
286
|
export class PinTanParameters {
|
|
257
287
|
private constructor(
|
|
258
288
|
private readonly tanRequired: ReadonlyMap<string, boolean>,
|
|
259
|
-
/** The shortest and longest PIN the bank takes. */
|
|
260
|
-
readonly pinLength: { min: number | undefined; max: number | undefined },
|
|
289
|
+
/** The shortest and longest PIN the bank takes; `undefined` where the bank parameter data have no HIPINS. */
|
|
290
|
+
readonly pinLength: { min: number | undefined; max: number | undefined } | undefined,
|
|
261
291
|
) {}
|
|
262
292
|
|
|
263
293
|
/** Reads the HIPINS segment of the highest version. */
|
|
264
294
|
static parse(segments: readonly Segment[]): PinTanParameters {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if (hipins === undefined || s.version > hipins.version) hipins = s;
|
|
268
|
-
}
|
|
269
|
-
if (hipins === undefined) return new PinTanParameters(new Map(), { min: undefined, max: undefined });
|
|
295
|
+
const hipins = highest(segments, 'HIPINS');
|
|
296
|
+
if (hipins === undefined) return new PinTanParameters(new Map(), undefined);
|
|
270
297
|
const fields = hipins.texts(parameterGroup);
|
|
271
298
|
const tanRequired = new Map<string, boolean>();
|
|
272
299
|
// Five general fields, the PIN lengths first; then pairs of segment type and TAN flag.
|
|
@@ -293,7 +320,8 @@ interface UpdEntry {
|
|
|
293
320
|
readonly extension: AccountExtension | undefined;
|
|
294
321
|
}
|
|
295
322
|
|
|
296
|
-
/** The version and the usage ("UPD-Verwendung") of the user parameter data in HIUPA. */
|
|
323
|
+
/** The user ID (Benutzerkennung), the version and the usage ("UPD-Verwendung") of the user parameter data in HIUPA. */
|
|
324
|
+
const hiupaUserId = 1;
|
|
297
325
|
const hiupaVersion = 2;
|
|
298
326
|
const hiupaUsage = 3;
|
|
299
327
|
|
|
@@ -301,24 +329,41 @@ const hiupaUsage = 3;
|
|
|
301
329
|
const transactionCode = /^[A-Z]{4,6}$/;
|
|
302
330
|
|
|
303
331
|
/** FinTS 3.0 user parameter data (UPD): HIUPA and the accounts (HIUPD), parsed once. */
|
|
304
|
-
export class Upd3
|
|
332
|
+
export class Upd3 {
|
|
333
|
+
/** The user the bank sent them for; `undefined` where it names none. */
|
|
334
|
+
readonly userId: string | undefined;
|
|
335
|
+
|
|
336
|
+
/** The version of the parameter data, which the client reports back so that the bank sends newer ones only. */
|
|
305
337
|
readonly version: number;
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Whether the lists of permitted transactions are complete: the bank permits nothing they leave out ("UPD-Verwendung"
|
|
341
|
+
* 0).
|
|
342
|
+
*/
|
|
306
343
|
readonly listsAllTransactions: boolean;
|
|
344
|
+
|
|
345
|
+
/** The transactions any entry lists as permitted, with or without an account. */
|
|
307
346
|
readonly listedTransactions: ReadonlySet<string>;
|
|
308
347
|
|
|
309
348
|
/** The accounts with the extension each carries, where it does. */
|
|
310
349
|
private readonly entries: readonly UpdEntry[];
|
|
350
|
+
|
|
351
|
+
/** The accounts the user may access. */
|
|
311
352
|
readonly accounts: readonly Account[];
|
|
312
353
|
|
|
354
|
+
/** The segments as the saved state keeps them. */
|
|
355
|
+
readonly encoded: string;
|
|
356
|
+
|
|
313
357
|
/** `listsAll` tells whether HIUPA declares the lists of permitted transactions complete ("UPD-Verwendung" 0). */
|
|
314
358
|
private constructor(
|
|
315
|
-
|
|
359
|
+
segments: readonly Segment[],
|
|
316
360
|
hiupa: Segment,
|
|
317
361
|
listsAll: boolean,
|
|
318
362
|
defaultBankCode: string,
|
|
319
363
|
defaultCountry: string,
|
|
320
364
|
) {
|
|
321
365
|
const hiupds = segments.filter((s) => s.type === 'HIUPD');
|
|
366
|
+
this.userId = nonBlank(hiupa.text(hiupaUserId));
|
|
322
367
|
this.version = hiupa.integer(hiupaVersion) ?? 0;
|
|
323
368
|
this.listsAllTransactions = listsAll;
|
|
324
369
|
this.listedTransactions = new Set(hiupds.flatMap(transactionsOf));
|
|
@@ -326,13 +371,13 @@ export class Upd3 implements UserParameters {
|
|
|
326
371
|
hiupds.map((s) => entryOf(s, defaultBankCode, defaultCountry, listsAll)).filter((e) => e !== undefined),
|
|
327
372
|
);
|
|
328
373
|
this.accounts = Object.freeze(this.entries.map((e) => e.account));
|
|
374
|
+
this.encoded = encodeSegments(segments);
|
|
329
375
|
}
|
|
330
376
|
|
|
331
|
-
/**
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
377
|
+
/**
|
|
378
|
+
* The balance of `account` the user parameter data carry, where it holds all a balance query would deliver, so that
|
|
379
|
+
* the client asks for none (Formals E.3.1, F.3.2); `undefined` otherwise.
|
|
380
|
+
*/
|
|
336
381
|
completeBalance(account: Account): Balance | undefined {
|
|
337
382
|
return this.entries.find((e) => e.account.matches(account))?.extension?.completeBalance(account);
|
|
338
383
|
}
|
|
@@ -372,11 +417,13 @@ function transactionsOf(s: Segment): string[] {
|
|
|
372
417
|
function extensionOf(s: Segment): AccountExtension | undefined {
|
|
373
418
|
if (s.version < 6) return undefined;
|
|
374
419
|
for (let i = allowedFrom(s.version); i < s.length; i++) {
|
|
375
|
-
const text =
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
420
|
+
const text = trimBlanks(
|
|
421
|
+
s
|
|
422
|
+
.group(i)
|
|
423
|
+
.map((e) => e.text)
|
|
424
|
+
.join(':'),
|
|
425
|
+
);
|
|
426
|
+
if (text.startsWith('{')) return AccountExtension.tryParse(text);
|
|
380
427
|
}
|
|
381
428
|
return undefined;
|
|
382
429
|
}
|
|
@@ -391,6 +438,22 @@ function entryOf(s: Segment, defaultBankCode: string, defaultCountry: string, li
|
|
|
391
438
|
};
|
|
392
439
|
}
|
|
393
440
|
|
|
441
|
+
/** Whether `code` is a country code, three digits, such as `280`; a German bank code has eight. */
|
|
442
|
+
function isCountryCode(code: string | undefined): boolean {
|
|
443
|
+
return code !== undefined && /^[0-9]{3}$/.test(code);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* The national account of the HIUPD `s`: number, sub-account, country and bank code (KTV2), by position in every
|
|
448
|
+
* version, so that a sub-account or bank code the bank leaves out is empty. Three elements whose second is a country
|
|
449
|
+
* code and whose third is not are the older layout without sub-account: number, country and bank code.
|
|
450
|
+
*/
|
|
451
|
+
function nationalAccountOf(s: Segment): (string | undefined)[] {
|
|
452
|
+
const ktv = s.texts(1);
|
|
453
|
+
const [number, second, third] = ktv;
|
|
454
|
+
return ktv.length === 3 && isCountryCode(second) && !isCountryCode(third) ? [number, undefined, second, third] : ktv;
|
|
455
|
+
}
|
|
456
|
+
|
|
394
457
|
function accountOf(
|
|
395
458
|
s: Segment,
|
|
396
459
|
defaultBankCode: string,
|
|
@@ -398,19 +461,7 @@ function accountOf(
|
|
|
398
461
|
listsAll: boolean,
|
|
399
462
|
): Account | undefined {
|
|
400
463
|
const version = s.version;
|
|
401
|
-
const
|
|
402
|
-
let number: string | undefined;
|
|
403
|
-
let subAccount: string | undefined;
|
|
404
|
-
let country: string | undefined;
|
|
405
|
-
let bank: string | undefined;
|
|
406
|
-
if (nationalAccount.length >= 4) {
|
|
407
|
-
[number, subAccount, country, bank] = nationalAccount;
|
|
408
|
-
} else if (nationalAccount.length === 3) {
|
|
409
|
-
// Older banks leave out the sub-account.
|
|
410
|
-
[number, country, bank] = nationalAccount;
|
|
411
|
-
} else if (nationalAccount.length > 0) {
|
|
412
|
-
number = nationalAccount[0];
|
|
413
|
-
}
|
|
464
|
+
const [number, subAccount, country, bank] = nationalAccountOf(s);
|
|
414
465
|
const iban = version >= 6 ? s.text(2) : undefined;
|
|
415
466
|
if (number === undefined && iban === undefined) return undefined;
|
|
416
467
|
// Field positions by HIUPD version: version 6 inserts the IBAN, version 5 the account type.
|
|
@@ -420,9 +471,6 @@ function accountOf(
|
|
|
420
471
|
: version === 5
|
|
421
472
|
? { customer: 2, type: 3, currency: 4, name1: 5, name2: 6, product: 7 }
|
|
422
473
|
: { customer: 2, type: undefined, currency: 3, name1: 4, name2: 5, product: 6 };
|
|
423
|
-
const owner = defined([s.text(fields.name1), s.text(fields.name2)])
|
|
424
|
-
.join(' ')
|
|
425
|
-
.trim();
|
|
426
474
|
return new Account({
|
|
427
475
|
accountNumber: number,
|
|
428
476
|
subAccount,
|
|
@@ -432,18 +480,70 @@ function accountOf(
|
|
|
432
480
|
customerId: s.text(fields.customer),
|
|
433
481
|
typeCode: fields.type === undefined ? undefined : s.integer(fields.type),
|
|
434
482
|
currency: s.text(fields.currency),
|
|
435
|
-
ownerName:
|
|
483
|
+
ownerName: nonBlank(defined([s.text(fields.name1), s.text(fields.name2)]).join(' ')),
|
|
436
484
|
productName: s.text(fields.product),
|
|
437
485
|
allowedTransactions: transactionsOf(s),
|
|
438
486
|
allowedTransactionsComplete: listsAll,
|
|
439
487
|
});
|
|
440
488
|
}
|
|
441
489
|
|
|
490
|
+
/**
|
|
491
|
+
* The version of `transaction` to send, for `account` where it concerns one: the highest the bank offers and the client
|
|
492
|
+
* `implemented`. `undefined` where there is none, or the parameter data do not permit the transaction
|
|
493
|
+
* (`permitsTransaction`).
|
|
494
|
+
*/
|
|
495
|
+
export function offeredVersion(
|
|
496
|
+
bpd: Bpd3 | undefined,
|
|
497
|
+
upd: Upd3 | undefined,
|
|
498
|
+
transaction: string,
|
|
499
|
+
implemented: ReadonlySet<number>,
|
|
500
|
+
options: { account?: Account; requiresEntry?: boolean },
|
|
501
|
+
): number | undefined {
|
|
502
|
+
return permitsTransaction(bpd, upd, transaction, options) ? bpd?.bestVersion(transaction, implemented) : undefined;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Whether the parameter data let the user send `transaction`, for `account` where it concerns one: the bank allows it
|
|
507
|
+
* with PIN/TAN, and the user parameter data permit it (Formals E.1, F.3.4; PIN/TAN B.1, B.8.1).
|
|
508
|
+
*
|
|
509
|
+
* - `account`, which the session has taken as the bank knows it (`SepaAccounts.complete`), permits what it lists, and
|
|
510
|
+
* anything else only where its list is incomplete ("UPD-Verwendung" 1). One only the SEPA account list names lists
|
|
511
|
+
* nothing, and so permits anything; where the lists are complete, the session refuses one the bank names nowhere.
|
|
512
|
+
* - A transaction without account, such as the TAN media list, is permitted where some entry lists it or the lists are
|
|
513
|
+
* incomplete.
|
|
514
|
+
* - One that `requiresEntry`, the SEPA account list, must be listed even where the lists are incomplete (Formals F.3.4),
|
|
515
|
+
* unless they list nothing at all, which tells nothing.
|
|
516
|
+
*/
|
|
517
|
+
function permitsTransaction(
|
|
518
|
+
bpd: Bpd3 | undefined,
|
|
519
|
+
upd: Upd3 | undefined,
|
|
520
|
+
transaction: string,
|
|
521
|
+
options: { account?: Account; requiresEntry?: boolean },
|
|
522
|
+
): boolean {
|
|
523
|
+
if (bpd !== undefined && !bpd.pinTan.allowsWithPinTan(transaction)) return false;
|
|
524
|
+
if (upd === undefined) return true;
|
|
525
|
+
if (options.account !== undefined) return allows(options.account, transaction);
|
|
526
|
+
const listed = upd.listedTransactions;
|
|
527
|
+
if (listed.has(transaction)) return true;
|
|
528
|
+
return !upd.listsAllTransactions && (!(options.requiresEntry ?? false) || listed.size === 0);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/** Whether the bank lists `transaction`, such as `HKCAZ`, as permitted for `account`. */
|
|
532
|
+
export function lists(account: Account, transaction: string): boolean {
|
|
533
|
+
return account.allowedTransactions.includes(transaction);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Whether the bank may permit `transaction` on `account`: it lists it, or its list is not complete
|
|
538
|
+
* (`Account.allowedTransactionsComplete`), so that only the bank can tell.
|
|
539
|
+
*/
|
|
540
|
+
function allows(account: Account, transaction: string): boolean {
|
|
541
|
+
return lists(account, transaction) || !account.allowedTransactionsComplete;
|
|
542
|
+
}
|
|
543
|
+
|
|
442
544
|
/** Serializes parameter segments for the saved state. */
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
for (const s of segments) out.add(s.encode());
|
|
446
|
-
return base64Encode(out.takeBytes());
|
|
545
|
+
function encodeSegments(segments: readonly Segment[]): string {
|
|
546
|
+
return base64Encode(concatBytes(segments.map((s) => s.encode())));
|
|
447
547
|
}
|
|
448
548
|
|
|
449
549
|
export function decodeSegments(data: string): Segment[] {
|