@coderoftime/fints 0.1.0 → 0.2.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 +142 -1
- package/README.md +127 -37
- package/dist/client.d.ts +48 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +199 -0
- package/dist/client.js.map +1 -0
- package/dist/core/account.d.ts +35 -7
- package/dist/core/account.d.ts.map +1 -1
- package/dist/core/account.js +84 -13
- package/dist/core/account.js.map +1 -1
- package/dist/core/amount.d.ts +2 -1
- package/dist/core/amount.d.ts.map +1 -1
- package/dist/core/amount.js +4 -1
- package/dist/core/amount.js.map +1 -1
- package/dist/core/balance.d.ts +1 -0
- package/dist/core/balance.d.ts.map +1 -1
- package/dist/core/balance.js +6 -5
- package/dist/core/balance.js.map +1 -1
- package/dist/core/bank-info.d.ts +1 -1
- package/dist/core/bank-info.js +1 -1
- package/dist/core/client.d.ts +50 -15
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/config.d.ts +28 -16
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +66 -26
- package/dist/core/config.js.map +1 -1
- package/dist/core/errors.d.ts +85 -23
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +112 -47
- package/dist/core/errors.js.map +1 -1
- package/dist/core/feedback.d.ts +4 -1
- package/dist/core/feedback.d.ts.map +1 -1
- package/dist/core/feedback.js +4 -1
- package/dist/core/feedback.js.map +1 -1
- package/dist/core/transaction.d.ts +6 -1
- package/dist/core/transaction.d.ts.map +1 -1
- package/dist/core/transaction.js +9 -4
- package/dist/core/transaction.js.map +1 -1
- package/dist/core/transport.d.ts +33 -6
- package/dist/core/transport.d.ts.map +1 -1
- package/dist/core/transport.js +77 -20
- package/dist/core/transport.js.map +1 -1
- package/dist/formats/camt.d.ts +24 -4
- package/dist/formats/camt.d.ts.map +1 -1
- package/dist/formats/camt.js +124 -41
- package/dist/formats/camt.js.map +1 -1
- package/dist/formats/mt940.d.ts +15 -3
- package/dist/formats/mt940.d.ts.map +1 -1
- package/dist/formats/mt940.js +161 -82
- 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 +24 -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 +6 -0
- package/dist/formats/sepa-purpose.d.ts.map +1 -1
- package/dist/formats/sepa-purpose.js +23 -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 +7 -10
- package/dist/session/dialog-runner.d.ts.map +1 -1
- package/dist/session/dialog-runner.js +15 -16
- 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 +156 -84
- 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 +33 -25
- package/dist/session/session-setup.d.ts.map +1 -1
- package/dist/session/session-setup.js +87 -65
- package/dist/session/session-setup.js.map +1 -1
- package/dist/session/session-state.d.ts +9 -6
- package/dist/session/session-state.d.ts.map +1 -1
- package/dist/session/session-state.js +52 -17
- package/dist/session/session-state.js.map +1 -1
- package/dist/session/signed-dialog.d.ts +77 -0
- package/dist/session/signed-dialog.d.ts.map +1 -0
- package/dist/session/signed-dialog.js +170 -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 +8 -4
- package/dist/session/user-prompts.d.ts.map +1 -1
- package/dist/session/user-prompts.js +34 -14
- package/dist/session/user-prompts.js.map +1 -1
- package/dist/tan/challenge.d.ts +34 -13
- package/dist/tan/challenge.d.ts.map +1 -1
- package/dist/tan/challenge.js +25 -26
- package/dist/tan/challenge.js.map +1 -1
- package/dist/tan/flicker.d.ts +7 -1
- package/dist/tan/flicker.d.ts.map +1 -1
- package/dist/tan/flicker.js +40 -6
- package/dist/tan/flicker.js.map +1 -1
- package/dist/tan/tan-image.d.ts +3 -2
- package/dist/tan/tan-image.d.ts.map +1 -1
- package/dist/tan/tan-image.js +7 -5
- package/dist/tan/tan-image.js.map +1 -1
- package/dist/tan/tan-models.d.ts +5 -1
- package/dist/tan/tan-models.d.ts.map +1 -1
- package/dist/tan/tan-models.js +10 -5
- package/dist/tan/tan-models.js.map +1 -1
- package/dist/util/async.d.ts +0 -2
- package/dist/util/async.d.ts.map +1 -1
- package/dist/util/async.js +0 -4
- package/dist/util/async.js.map +1 -1
- package/dist/util/bytes.d.ts +2 -3
- package/dist/util/bytes.d.ts.map +1 -1
- package/dist/util/bytes.js +12 -20
- 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 +9 -16
- package/dist/util/dates.d.ts.map +1 -1
- package/dist/util/dates.js +39 -19
- package/dist/util/dates.js.map +1 -1
- package/dist/util/errors.d.ts +9 -1
- package/dist/util/errors.d.ts.map +1 -1
- package/dist/util/errors.js +12 -3
- package/dist/util/errors.js.map +1 -1
- 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 +21 -4
- package/dist/util/text.d.ts.map +1 -1
- package/dist/util/text.js +42 -13
- package/dist/util/text.js.map +1 -1
- package/dist/util/xml.d.ts +4 -3
- package/dist/util/xml.d.ts.map +1 -1
- package/dist/util/xml.js +37 -26
- 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 +34 -0
- package/dist/v3/bank-connection.d.ts.map +1 -0
- package/dist/v3/bank-connection.js +58 -0
- package/dist/v3/bank-connection.js.map +1 -0
- package/dist/v3/dialog.d.ts +80 -46
- package/dist/v3/dialog.d.ts.map +1 -1
- package/dist/v3/dialog.js +162 -59
- package/dist/v3/dialog.js.map +1 -1
- package/dist/v3/message.d.ts +17 -4
- package/dist/v3/message.d.ts.map +1 -1
- package/dist/v3/message.js +62 -18
- package/dist/v3/message.js.map +1 -1
- package/dist/v3/parameter-store.d.ts +75 -0
- package/dist/v3/parameter-store.d.ts.map +1 -0
- package/dist/v3/parameter-store.js +143 -0
- package/dist/v3/parameter-store.js.map +1 -0
- package/dist/v3/parameters.d.ts +38 -12
- package/dist/v3/parameters.d.ts.map +1 -1
- package/dist/v3/parameters.js +91 -37
- package/dist/v3/parameters.js.map +1 -1
- package/dist/v3/protocol.d.ts +43 -50
- package/dist/v3/protocol.d.ts.map +1 -1
- package/dist/v3/protocol.js +58 -208
- 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 +120 -0
- package/dist/v3/queries.js.map +1 -0
- package/dist/v3/segments.d.ts +29 -12
- package/dist/v3/segments.d.ts.map +1 -1
- package/dist/v3/segments.js +65 -23
- package/dist/v3/segments.js.map +1 -1
- package/dist/v3/syntax.d.ts +65 -14
- package/dist/v3/syntax.d.ts.map +1 -1
- package/dist/v3/syntax.js +167 -101
- package/dist/v3/syntax.js.map +1 -1
- package/package.json +10 -6
- package/src/client.ts +252 -0
- package/src/core/account.ts +86 -13
- package/src/core/amount.ts +3 -1
- package/src/core/balance.ts +6 -5
- package/src/core/bank-info.ts +1 -1
- package/src/core/client.ts +52 -15
- package/src/core/config.ts +69 -28
- package/src/core/errors.ts +122 -48
- package/src/core/feedback.ts +4 -1
- package/src/core/transaction.ts +9 -4
- package/src/core/transport.ts +98 -23
- package/src/formats/camt.ts +136 -39
- package/src/formats/mt940.ts +167 -81
- package/src/formats/readers.ts +24 -31
- package/src/formats/record-budget.ts +18 -0
- package/src/formats/sepa-purpose.ts +21 -2
- package/src/index.ts +4 -2
- package/src/session/app-approval.ts +51 -38
- package/src/session/dialog-runner.ts +31 -28
- package/src/session/login-guard.ts +174 -88
- package/src/session/sepa-accounts.ts +63 -45
- package/src/session/session-setup.ts +90 -71
- package/src/session/session-state.ts +56 -22
- package/src/session/signed-dialog.ts +192 -0
- package/src/session/tan-selection.ts +50 -39
- package/src/session/user-prompts.ts +35 -16
- package/src/tan/challenge.ts +40 -31
- package/src/tan/flicker.ts +44 -6
- package/src/tan/tan-image.ts +7 -5
- package/src/tan/tan-models.ts +10 -5
- package/src/util/async.ts +0 -5
- package/src/util/bytes.ts +13 -19
- package/src/util/call-context.ts +3 -2
- package/src/util/dates.ts +41 -24
- package/src/util/errors.ts +14 -3
- package/src/util/json.ts +16 -0
- package/src/util/text.ts +42 -12
- package/src/util/xml.ts +37 -26
- package/src/v3/account-extension.ts +7 -12
- package/src/v3/bank-connection.ts +67 -0
- package/src/v3/dialog.ts +175 -91
- package/src/v3/message.ts +69 -17
- package/src/v3/parameter-store.ts +160 -0
- package/src/v3/parameters.ts +110 -37
- package/src/v3/protocol.ts +70 -253
- package/src/v3/queries.ts +190 -0
- package/src/v3/segments.ts +65 -22
- package/src/v3/syntax.ts +153 -91
- 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/formats/mt940.ts
CHANGED
|
@@ -2,7 +2,8 @@ import { Amount } from '../core/amount.js';
|
|
|
2
2
|
import { Transaction, type TransactionStatus } from '../core/transaction.js';
|
|
3
3
|
import { clampedDate, parseSwiftDate } from '../util/dates.js';
|
|
4
4
|
import { FormatError } from '../util/errors.js';
|
|
5
|
-
import {
|
|
5
|
+
import { nonBlank } from '../util/text.js';
|
|
6
|
+
import { RecordBudget } from './record-budget.js';
|
|
6
7
|
import { providedReference, SepaPurpose } from './sepa-purpose.js';
|
|
7
8
|
|
|
8
9
|
/** One statement from an MT940 or MT942 file, with its balances and transactions. */
|
|
@@ -23,7 +24,10 @@ export interface Mt940Statement {
|
|
|
23
24
|
readonly closingBalance: Amount | undefined;
|
|
24
25
|
/** The date of `closingBalance`. */
|
|
25
26
|
readonly closingDate: Date | undefined;
|
|
26
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* The transactions of the statement, in the order the bank lists them; they lead from `openingBalance` to
|
|
29
|
+
* `closingBalance` where the statement states these.
|
|
30
|
+
*/
|
|
27
31
|
readonly transactions: readonly Transaction[];
|
|
28
32
|
}
|
|
29
33
|
|
|
@@ -36,8 +40,6 @@ export interface Mt940Options {
|
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
const tagPattern = /^:(\d{2}[A-Z]?):/gm;
|
|
39
|
-
const statementEnd = /\n-\s*$/;
|
|
40
|
-
const trailingNewlines = /\n+$/;
|
|
41
43
|
|
|
42
44
|
/** Parser for SWIFT MT940/MT942 statements, including German structured `:86:` fields. */
|
|
43
45
|
export const Mt940 = Object.freeze({
|
|
@@ -47,35 +49,79 @@ export const Mt940 = Object.freeze({
|
|
|
47
49
|
},
|
|
48
50
|
|
|
49
51
|
/**
|
|
50
|
-
* Parses all statements.
|
|
52
|
+
* Parses all statements. Their lines are separated by line breaks, and by `@@` as well where the first line holds it,
|
|
53
|
+
* as some banks separate them.
|
|
51
54
|
*
|
|
52
|
-
* Throws `FormatError` if a transaction line (`:61:`) cannot be read or
|
|
55
|
+
* Throws `FormatError` for text without any field, and if a transaction line (`:61:`) or a balance cannot be read or
|
|
56
|
+
* the currency of a transaction is unknown. It does as well for a statement that states only one of its opening and
|
|
57
|
+
* closing balances, one of them, its account or its number twice, or transactions that do not lead from the one
|
|
58
|
+
* balance to the other: a line of a purpose that starts like a field, such as `:61:` or `:25:`, would pass for a
|
|
59
|
+
* transaction, the account or a statement of its own otherwise. Text of more than 200,000 statements and
|
|
60
|
+
* transactions is a `FormatError` as well.
|
|
53
61
|
*/
|
|
54
62
|
parse(text: string, options: Mt940Options = {}): Mt940Statement[] {
|
|
55
|
-
|
|
56
|
-
const lines = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
57
|
-
// Some banks separate lines with `@@` instead of line breaks; where there are line breaks, `@@` is text.
|
|
58
|
-
const normalized = lines.includes('\n') ? lines : lines.replace(/@@/g, '\n');
|
|
59
|
-
const tags = [...normalized.matchAll(tagPattern)];
|
|
60
|
-
const statements: Mt940Statement[] = [];
|
|
61
|
-
let current: StatementBuilder | undefined;
|
|
62
|
-
tags.forEach((tag, i) => {
|
|
63
|
-
const end = i + 1 < tags.length ? tags[i + 1]!.index : normalized.length;
|
|
64
|
-
const value = normalized
|
|
65
|
-
.substring(tag.index + tag[0].length, end)
|
|
66
|
-
.replace(statementEnd, '')
|
|
67
|
-
.replace(trailingNewlines, '');
|
|
68
|
-
if (tag[1] === '20' || current === undefined) {
|
|
69
|
-
if (current !== undefined) statements.push(current.build());
|
|
70
|
-
current = new StatementBuilder(status, options.currency);
|
|
71
|
-
}
|
|
72
|
-
current.add(tag[1]!, value);
|
|
73
|
-
});
|
|
74
|
-
if (current !== undefined) statements.push(current.build());
|
|
75
|
-
return statements;
|
|
63
|
+
return parseMt940(text, options, new RecordBudget());
|
|
76
64
|
},
|
|
77
65
|
});
|
|
78
66
|
|
|
67
|
+
/** `Mt940.parse`, which takes each statement and transaction from `budget`, before it builds it. */
|
|
68
|
+
export function parseMt940(text: string, options: Mt940Options, budget: RecordBudget): Mt940Statement[] {
|
|
69
|
+
const status = options.status ?? 'booked';
|
|
70
|
+
const normalized = withLineFeeds(text);
|
|
71
|
+
const statements: Mt940Statement[] = [];
|
|
72
|
+
let current: StatementBuilder | undefined;
|
|
73
|
+
for (const [tag, value] of taggedParts(normalized, tagPattern)) {
|
|
74
|
+
if (tag === '20' || current === undefined) {
|
|
75
|
+
if (current !== undefined) statements.push(current.build());
|
|
76
|
+
budget.take();
|
|
77
|
+
current = new StatementBuilder(status, options.currency);
|
|
78
|
+
}
|
|
79
|
+
if (tag === '61') budget.take();
|
|
80
|
+
current.add(tag, fieldValue(value));
|
|
81
|
+
}
|
|
82
|
+
if (current !== undefined) statements.push(current.build());
|
|
83
|
+
else if (normalized.trim() !== '') throw new FormatError('MT940 data without fields');
|
|
84
|
+
return statements;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The parts of `text` that the matches of `tag` start, each the first group of its match and the text up to the next
|
|
89
|
+
* match: the fields of a statement, or the subfields of a `:86:`. One by one: text of a few megabytes holds millions of
|
|
90
|
+
* them. A pattern that matched the text up to the next tag as well would run out of backtracking stack on a part of a
|
|
91
|
+
* few million characters.
|
|
92
|
+
*/
|
|
93
|
+
function* taggedParts(text: string, tag: RegExp): Generator<[tag: string, text: string]> {
|
|
94
|
+
let current: string | undefined;
|
|
95
|
+
let start = 0;
|
|
96
|
+
for (const match of text.matchAll(tag)) {
|
|
97
|
+
if (current !== undefined) yield [current, text.substring(start, match.index)];
|
|
98
|
+
current = match[1]!;
|
|
99
|
+
start = match.index + match[0].length;
|
|
100
|
+
}
|
|
101
|
+
if (current !== undefined) yield [current, text.substring(start)];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* `text` with line feeds between its lines. Some banks separate them with `@@`, and may still break the line between
|
|
106
|
+
* statements or at the ends. So `@@` separates lines where the first line holds it, which the bank writes; in other
|
|
107
|
+
* text, `@@` may be part of a purpose, which the payer writes.
|
|
108
|
+
*/
|
|
109
|
+
function withLineFeeds(text: string): string {
|
|
110
|
+
const lines = text.replace(/\r\n?/g, '\n');
|
|
111
|
+
return lines.trimStart().split('\n', 1)[0]!.includes('@@') ? lines.replace(/@@/g, '\n') : lines;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The value of a field in `text`, which runs from its tag to the next one: without the blanks and the statement end
|
|
116
|
+
* (`-`) at its end.
|
|
117
|
+
*
|
|
118
|
+
* Trimmed without a pattern such as `/\n+$/`, which takes quadratic time on a long run of line breaks.
|
|
119
|
+
*/
|
|
120
|
+
function fieldValue(text: string): string {
|
|
121
|
+
const value = text.trimEnd();
|
|
122
|
+
return value.endsWith('\n-') ? value.substring(0, value.length - 2).trimEnd() : value;
|
|
123
|
+
}
|
|
124
|
+
|
|
79
125
|
const balancePattern = /^([CD])(\d{6})([A-Z]{3})(\d+,\d*)/;
|
|
80
126
|
const currencyPrefix = /^([A-Z]{3})/;
|
|
81
127
|
|
|
@@ -108,28 +154,36 @@ class StatementBuilder {
|
|
|
108
154
|
this.reference = value.trim();
|
|
109
155
|
break;
|
|
110
156
|
case '25':
|
|
157
|
+
if (this.accountId !== undefined) throw new FormatError('MT940 statement with two accounts', this.reference);
|
|
111
158
|
this.accountId = value.trim();
|
|
112
159
|
break;
|
|
113
160
|
case '28C':
|
|
114
161
|
case '28':
|
|
162
|
+
if (this.statementNumber !== undefined) {
|
|
163
|
+
throw new FormatError('MT940 statement with two statement numbers', this.reference);
|
|
164
|
+
}
|
|
115
165
|
this.statementNumber = value.trim();
|
|
116
166
|
break;
|
|
117
167
|
case '60F':
|
|
118
168
|
case '60M': {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
[this.opening, this.openingDate] = balance;
|
|
122
|
-
this.currency ??= balance[0].currency;
|
|
169
|
+
if (this.opening !== undefined) {
|
|
170
|
+
throw new FormatError('MT940 statement with two opening balances', this.reference);
|
|
123
171
|
}
|
|
172
|
+
const [balance, date] = parseBalance(value);
|
|
173
|
+
this.opening = balance;
|
|
174
|
+
this.openingDate = date;
|
|
175
|
+
this.currency ??= balance.currency;
|
|
124
176
|
break;
|
|
125
177
|
}
|
|
126
178
|
case '62F':
|
|
127
179
|
case '62M': {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
[this.closing, this.closingDate] = balance;
|
|
131
|
-
this.currency ??= balance[0].currency;
|
|
180
|
+
if (this.closing !== undefined) {
|
|
181
|
+
throw new FormatError('MT940 statement with two closing balances', this.reference);
|
|
132
182
|
}
|
|
183
|
+
const [balance, date] = parseBalance(value);
|
|
184
|
+
this.closing = balance;
|
|
185
|
+
this.closingDate = date;
|
|
186
|
+
this.currency ??= balance.currency;
|
|
133
187
|
break;
|
|
134
188
|
}
|
|
135
189
|
case '34F':
|
|
@@ -150,6 +204,10 @@ class StatementBuilder {
|
|
|
150
204
|
if (statementCurrency === undefined && this.transactions.length > 0) {
|
|
151
205
|
throw new FormatError('MT940 statement without currency', this.reference);
|
|
152
206
|
}
|
|
207
|
+
const { openingDate, closingDate } = this;
|
|
208
|
+
const period = openingDate && closingDate ? ([openingDate, closingDate] as const) : undefined;
|
|
209
|
+
const transactions = this.transactions.map((t) => t.toTransaction(statementCurrency!, this.status, period));
|
|
210
|
+
this.checkBalances(transactions);
|
|
153
211
|
return Object.freeze({
|
|
154
212
|
reference: this.reference,
|
|
155
213
|
accountId: this.accountId,
|
|
@@ -159,14 +217,39 @@ class StatementBuilder {
|
|
|
159
217
|
openingDate: this.openingDate,
|
|
160
218
|
closingBalance: this.closing,
|
|
161
219
|
closingDate: this.closingDate,
|
|
162
|
-
transactions: Object.freeze(
|
|
220
|
+
transactions: Object.freeze(transactions),
|
|
163
221
|
});
|
|
164
222
|
}
|
|
223
|
+
|
|
224
|
+
/** Checks that the statement states both balances or neither, and that its `transactions` lead from one to the other. */
|
|
225
|
+
private checkBalances(transactions: readonly Transaction[]): void {
|
|
226
|
+
const { opening, closing } = this;
|
|
227
|
+
if (opening === undefined && closing === undefined) return;
|
|
228
|
+
if (opening === undefined || closing === undefined) {
|
|
229
|
+
throw new FormatError('MT940 statement with only one of its balances', this.reference);
|
|
230
|
+
}
|
|
231
|
+
if (opening.currency !== this.currency || closing.currency !== this.currency) {
|
|
232
|
+
throw new FormatError('MT940 balances in another currency than the statement', this.reference);
|
|
233
|
+
}
|
|
234
|
+
if (!balanceAfter(opening, transactions).equals(closing)) {
|
|
235
|
+
throw new FormatError('MT940 transactions do not add up to the closing balance', this.reference);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** `opening` plus the amounts of `transactions`; throws `FormatError` for a sum beyond what an `Amount` holds. */
|
|
241
|
+
function balanceAfter(opening: Amount, transactions: readonly Transaction[]): Amount {
|
|
242
|
+
try {
|
|
243
|
+
return transactions.reduce((balance, t) => balance.add(t.amount), opening);
|
|
244
|
+
} catch (error) {
|
|
245
|
+
if (error instanceof RangeError) throw new FormatError('MT940 amounts too large to add up');
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
165
248
|
}
|
|
166
249
|
|
|
167
|
-
function parseBalance(value: string): [Amount, Date]
|
|
250
|
+
function parseBalance(value: string): [Amount, Date] {
|
|
168
251
|
const m = balancePattern.exec(value.trim());
|
|
169
|
-
if (!m)
|
|
252
|
+
if (!m) throw new FormatError('Unreadable MT940 balance', value);
|
|
170
253
|
const amount = Amount.parseDecimal(m[4]!, m[3]!);
|
|
171
254
|
const date = parseSwiftDate(m[2]!);
|
|
172
255
|
if (date === undefined) throw new FormatError('Invalid balance date', value);
|
|
@@ -180,34 +263,33 @@ function parseBalance(value: string): [Amount, Date] | undefined {
|
|
|
180
263
|
const line61 =
|
|
181
264
|
/^(\d{6})(\d{4})?(RC|RD|C|D)([A-Z])?(\d+,\d*)([NFS][A-Z0-9]{3})([^\n]*?)(?:\/\/([^\n]*))?(?:\n([\s\S]*))?$/;
|
|
182
265
|
|
|
266
|
+
/** The dates of the opening and the closing balance of a statement, between which its transactions are booked. */
|
|
267
|
+
type Period = readonly [first: Date, last: Date];
|
|
268
|
+
|
|
183
269
|
/** The `:61:` line of a transaction and its `:86:` details. */
|
|
184
270
|
class TransactionLines {
|
|
185
271
|
details: string | undefined;
|
|
186
272
|
|
|
187
273
|
constructor(readonly line: string) {}
|
|
188
274
|
|
|
189
|
-
toTransaction(currency: string, status: TransactionStatus): Transaction {
|
|
275
|
+
toTransaction(currency: string, status: TransactionStatus, period: Period | undefined): Transaction {
|
|
190
276
|
const m = line61.exec(this.line.trim());
|
|
191
277
|
const valueDate = m ? parseSwiftDate(m[1]!) : undefined;
|
|
192
278
|
if (!m || valueDate === undefined) throw new FormatError('Unreadable MT940 transaction line (:61:)', this.line);
|
|
193
279
|
const mark = m[3]!;
|
|
194
280
|
const amount = Amount.parseDecimal(m[5]!, currency);
|
|
195
281
|
const debit = mark === 'D' || mark === 'RC';
|
|
196
|
-
const customerReference = m[7]
|
|
197
|
-
const bankReference = m[8]?.trim();
|
|
282
|
+
const customerReference = nonBlank(m[7]);
|
|
198
283
|
const d = Details.parse(this.details);
|
|
199
284
|
return new Transaction({
|
|
200
285
|
status,
|
|
201
|
-
bookingDate: this.bookingDate(m[2], valueDate),
|
|
286
|
+
bookingDate: this.bookingDate(m[2], valueDate, period),
|
|
202
287
|
valueDate,
|
|
203
288
|
amount: debit ? amount.negate() : amount,
|
|
204
289
|
isReversal: mark.startsWith('R'),
|
|
205
290
|
transactionTypeId: m[6],
|
|
206
|
-
customerReference:
|
|
207
|
-
|
|
208
|
-
? undefined
|
|
209
|
-
: customerReference,
|
|
210
|
-
bankReference: bankReference === undefined || bankReference === '' ? undefined : bankReference,
|
|
291
|
+
customerReference: customerReference === 'NONREF' ? undefined : customerReference,
|
|
292
|
+
bankReference: nonBlank(m[8]),
|
|
211
293
|
transactionCode: d.transactionCode,
|
|
212
294
|
bookingText: d.bookingText,
|
|
213
295
|
primaNota: d.primaNota,
|
|
@@ -226,34 +308,44 @@ class TransactionLines {
|
|
|
226
308
|
});
|
|
227
309
|
}
|
|
228
310
|
|
|
229
|
-
/**
|
|
230
|
-
|
|
311
|
+
/**
|
|
312
|
+
* The booking date `MMDD`, in the year that places it within `period`, or else closest to it; of several such years,
|
|
313
|
+
* the one closest to `valueDate`; without a period, the year closest to `valueDate`. The value date alone misleads:
|
|
314
|
+
* banks book refunds and corrections with the value date of the original booking, up to 13 months before.
|
|
315
|
+
*/
|
|
316
|
+
private bookingDate(mmdd: string | undefined, valueDate: Date, period: Period | undefined): Date | undefined {
|
|
231
317
|
if (mmdd === undefined) return undefined;
|
|
232
318
|
const month = Number(mmdd.substring(0, 2));
|
|
233
319
|
const day = Number(mmdd.substring(2, 4));
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
320
|
+
const [first, last] = period ?? [valueDate, valueDate];
|
|
321
|
+
const outside = (date: Date): number =>
|
|
322
|
+
Math.max(first.getTime() - date.getTime(), date.getTime() - last.getTime(), 0);
|
|
323
|
+
const fromValueDate = (date: Date): number => Math.abs(date.getTime() - valueDate.getTime());
|
|
324
|
+
// The best year lies next to one of these, however long the period.
|
|
325
|
+
const years = new Set(
|
|
326
|
+
[first, valueDate, last].flatMap((d) => [d.getFullYear() - 1, d.getFullYear(), d.getFullYear() + 1]),
|
|
327
|
+
);
|
|
328
|
+
let best: Date | undefined;
|
|
329
|
+
for (const year of years) {
|
|
330
|
+
const date = clampedDate(year, month, day);
|
|
331
|
+
if (date === undefined) throw new FormatError('Invalid booking date', this.line);
|
|
332
|
+
if (best === undefined || (outside(date) - outside(best) || fromValueDate(date) - fromValueDate(best)) < 0) {
|
|
333
|
+
best = date;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return best;
|
|
240
337
|
}
|
|
241
338
|
}
|
|
242
339
|
|
|
243
340
|
/**
|
|
244
|
-
* Three-digit business transaction code (GVC), then the first subfield:
|
|
245
|
-
*
|
|
341
|
+
* Three-digit business transaction code (GVC), then the first subfield: `?` and its two-digit number. Text that merely
|
|
342
|
+
* starts with three digits, such as `100 Euro` or `100,00 EUR`, is free text.
|
|
246
343
|
*/
|
|
247
|
-
const structuredPattern =
|
|
344
|
+
const structuredPattern = /^\d{3}\?\d{2}/;
|
|
345
|
+
const subfieldTag = /\?(\d{2})/g;
|
|
248
346
|
const ibanPattern = /^[A-Z]{2}\d{2}[A-Z0-9]+$/;
|
|
249
347
|
const bicPattern = /^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$/;
|
|
250
348
|
|
|
251
|
-
/** Subfield pattern for a separator: the separator, a two-digit key, then everything up to the next subfield. */
|
|
252
|
-
function subfieldPattern(separator: string): RegExp {
|
|
253
|
-
const s = escapeRegExp(separator);
|
|
254
|
-
return new RegExp(`${s}(\\d{2})((?:(?!${s}\\d{2})[\\s\\S])*)`, 'g');
|
|
255
|
-
}
|
|
256
|
-
|
|
257
349
|
/** The German structured `:86:` field: a business transaction code and `?nn` subfields. */
|
|
258
350
|
class Details {
|
|
259
351
|
transactionCode: string | undefined;
|
|
@@ -271,20 +363,16 @@ class Details {
|
|
|
271
363
|
const d = new Details();
|
|
272
364
|
if (raw === undefined) return d;
|
|
273
365
|
const text = raw.replace(/\n/g, '');
|
|
274
|
-
|
|
275
|
-
if (!structured) {
|
|
366
|
+
if (!structuredPattern.test(text)) {
|
|
276
367
|
// Free text, whose lines are separate phrases; the purpose and its keywords are read from the same text.
|
|
277
368
|
const free = raw.replace(/\n/g, ' ').trim();
|
|
278
369
|
d.sepa = SepaPurpose.parse(free);
|
|
279
|
-
d.purpose = d.sepa.get('SVWZ') ?? (free
|
|
370
|
+
d.purpose = d.sepa.get('SVWZ') ?? nonBlank(free);
|
|
280
371
|
return d;
|
|
281
372
|
}
|
|
282
|
-
d.transactionCode =
|
|
283
|
-
const subfields = Details.subfields(text.substring(3)
|
|
284
|
-
const subfield = (key: number): string | undefined =>
|
|
285
|
-
const value = subfields.get(key)?.trim();
|
|
286
|
-
return value === undefined || value === '' ? undefined : value;
|
|
287
|
-
};
|
|
373
|
+
d.transactionCode = text.substring(0, 3);
|
|
374
|
+
const subfields = Details.subfields(text.substring(3));
|
|
375
|
+
const subfield = (key: number): string | undefined => nonBlank(subfields.get(key));
|
|
288
376
|
// Subfields continue each other: they are joined keeping the spaces at their boundaries.
|
|
289
377
|
const parts = (keys: number[]): string[] =>
|
|
290
378
|
keys.map((k) => subfields.get(k)).filter((v): v is string => v !== undefined);
|
|
@@ -292,11 +380,9 @@ class Details {
|
|
|
292
380
|
d.bookingText = subfield(0);
|
|
293
381
|
d.primaNota = subfield(10);
|
|
294
382
|
const purposeParts = parts([20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 60, 61, 62, 63]);
|
|
295
|
-
const purpose = purposeParts.join('').trim();
|
|
296
383
|
d.sepa = SepaPurpose.parseParts(purposeParts);
|
|
297
|
-
d.purpose = d.sepa.get('SVWZ') ?? (
|
|
298
|
-
|
|
299
|
-
d.name = name === '' ? undefined : name;
|
|
384
|
+
d.purpose = d.sepa.get('SVWZ') ?? nonBlank(purposeParts.join(''));
|
|
385
|
+
d.name = nonBlank(parts([32, 33]).join(''));
|
|
300
386
|
d.readCounterpartyAccount(subfield(30), subfield(31));
|
|
301
387
|
d.iban ??= d.sepa.get('IBAN');
|
|
302
388
|
d.bic ??= d.sepa.get('BIC');
|
|
@@ -304,11 +390,11 @@ class Details {
|
|
|
304
390
|
}
|
|
305
391
|
|
|
306
392
|
/** The text of each subfield (`?nn` and what follows), by its number; a subfield that appears twice is joined. */
|
|
307
|
-
private static subfields(text: string
|
|
393
|
+
private static subfields(text: string): Map<number, string> {
|
|
308
394
|
const subfields = new Map<number, string>();
|
|
309
|
-
for (const
|
|
310
|
-
const key = Number(
|
|
311
|
-
subfields.set(key, (subfields.get(key) ?? '') +
|
|
395
|
+
for (const [tag, value] of taggedParts(text, subfieldTag)) {
|
|
396
|
+
const key = Number(tag);
|
|
397
|
+
subfields.set(key, (subfields.get(key) ?? '') + value);
|
|
312
398
|
}
|
|
313
399
|
return subfields;
|
|
314
400
|
}
|
package/src/formats/readers.ts
CHANGED
|
@@ -1,56 +1,49 @@
|
|
|
1
1
|
import type { Transaction } from '../core/transaction.js';
|
|
2
|
-
import { BytesBuilder } from '../util/bytes.js';
|
|
3
2
|
import { decodeBankText, decodeXmlText } from '../util/text.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { parseCamt } from './camt.js';
|
|
4
|
+
import { parseMt940 } from './mt940.js';
|
|
5
|
+
import { RecordBudget } from './record-budget.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* The transactions in MT940 data: the `booked` parts joined, since a statement may continue on the next page, then the
|
|
9
9
|
* `pending` ones (MT942).
|
|
10
10
|
*
|
|
11
|
-
* `currency` applies to statements that do not state one; throws `FormatError` if both are missing
|
|
11
|
+
* `currency` applies to statements that do not state one; throws `FormatError` if both are missing, and for more than
|
|
12
|
+
* 200,000 statements and transactions in all.
|
|
12
13
|
*/
|
|
13
14
|
export function mt940Transactions(
|
|
14
15
|
booked: readonly Uint8Array[],
|
|
15
16
|
pending: readonly Uint8Array[],
|
|
16
17
|
currency: string | undefined,
|
|
17
18
|
): Transaction[] {
|
|
19
|
+
const budget = new RecordBudget();
|
|
18
20
|
return [
|
|
19
|
-
...
|
|
20
|
-
...
|
|
21
|
-
];
|
|
21
|
+
...parseMt940(joinedMt940(booked), { currency }, budget),
|
|
22
|
+
...parseMt940(joinedMt940(pending), { currency, status: 'pending' }, budget),
|
|
23
|
+
].flatMap((statement) => statement.transactions);
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
const lineFeed = 0x0a;
|
|
25
|
-
const carriageReturn = 0x0d;
|
|
26
|
-
const atSign = 0x40;
|
|
27
|
-
|
|
28
26
|
/**
|
|
29
|
-
* The MT940 `parts` as one text
|
|
27
|
+
* The MT940 `parts` as one text, whose line separator the parser takes from its first line: a later page may start
|
|
28
|
+
* with a purpose, which the payer writes.
|
|
30
29
|
*
|
|
31
|
-
* Each part
|
|
32
|
-
*
|
|
30
|
+
* Each part is decoded on its own, since a bank may start each page with a byte order mark. Each part ends a line
|
|
31
|
+
* before the next one follows, since the parser finds fields at the start of a line and a bank need not end its pages
|
|
32
|
+
* with a line break.
|
|
33
33
|
*/
|
|
34
34
|
function joinedMt940(parts: readonly Uint8Array[]): string {
|
|
35
|
-
const pages = parts.filter((part) => part.length > 0);
|
|
36
|
-
|
|
37
|
-
const endsLine = (page: Uint8Array): boolean =>
|
|
38
|
-
atSigns
|
|
39
|
-
? page.length >= 2 && page[page.length - 1] === atSign && page[page.length - 2] === atSign
|
|
40
|
-
: page.at(-1) === lineFeed || page.at(-1) === carriageReturn;
|
|
41
|
-
|
|
42
|
-
const bytes = new BytesBuilder();
|
|
43
|
-
pages.forEach((page, i) => {
|
|
44
|
-
if (i > 0 && !endsLine(pages[i - 1]!)) bytes.add(atSigns ? [atSign, atSign] : [lineFeed]);
|
|
45
|
-
bytes.add(page);
|
|
46
|
-
});
|
|
47
|
-
return decodeBankText(bytes.takeBytes());
|
|
35
|
+
const pages = parts.filter((part) => part.length > 0).map((part) => decodeBankText(part));
|
|
36
|
+
return pages.map((page, i) => (i + 1 < pages.length && !page.endsWith('\n') ? `${page}\n` : page)).join('');
|
|
48
37
|
}
|
|
49
38
|
|
|
50
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* The transactions in camt documents; each document stands on its own. Throws `FormatError` for more than 200,000
|
|
41
|
+
* statements and transactions in all.
|
|
42
|
+
*/
|
|
51
43
|
export function camtTransactions(booked: readonly Uint8Array[], pending: readonly Uint8Array[]): Transaction[] {
|
|
44
|
+
const budget = new RecordBudget();
|
|
52
45
|
return [
|
|
53
|
-
...booked.flatMap((document) =>
|
|
54
|
-
...pending.flatMap((document) =>
|
|
55
|
-
];
|
|
46
|
+
...booked.flatMap((document) => parseCamt(decodeXmlText(document), undefined, budget)),
|
|
47
|
+
...pending.flatMap((document) => parseCamt(decodeXmlText(document), 'pending', budget)),
|
|
48
|
+
].flatMap((statement) => statement.transactions);
|
|
56
49
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { FormatError } from '../util/errors.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Most statements and transactions the parsers build for one query: several times what a year of a busy business
|
|
5
|
+
* account holds, and few enough that a hostile answer cannot exhaust the memory, at about 600 bytes each. The size of
|
|
6
|
+
* the answer does not bound them: a transaction takes up to 25 times the bytes of its MT940 line.
|
|
7
|
+
*/
|
|
8
|
+
const maxRecords = 200_000;
|
|
9
|
+
|
|
10
|
+
/** The statements and transactions the parsers may still build; the parses of one query share one budget. */
|
|
11
|
+
export class RecordBudget {
|
|
12
|
+
private left = maxRecords;
|
|
13
|
+
|
|
14
|
+
/** Counts a statement or transaction about to be built; throws `FormatError` for one beyond the budget. */
|
|
15
|
+
take(): void {
|
|
16
|
+
if (--this.left < 0) throw new FormatError(`More than ${maxRecords} statements and transactions`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -26,6 +26,9 @@ export function providedReference(reference: string | undefined): string | undef
|
|
|
26
26
|
|
|
27
27
|
const keywordPattern = new RegExp(`(${keywords.join('|')})\\+`, 'g');
|
|
28
28
|
|
|
29
|
+
/** The keywords German banks write after the remittance information (`SVWZ`). */
|
|
30
|
+
const afterRemittance = new Set(['ABWA', 'ABWE']);
|
|
31
|
+
|
|
29
32
|
/** Parser for the SEPA keywords in a purpose text. */
|
|
30
33
|
export const SepaPurpose = Object.freeze({
|
|
31
34
|
/**
|
|
@@ -34,6 +37,12 @@ export const SepaPurpose = Object.freeze({
|
|
|
34
37
|
* A keyword counts unless a capital letter comes right before it, so `XEREF+` does not open an EREF value. After a
|
|
35
38
|
* digit or a small letter it does: a purpose line cut at 35 characters is joined to the next one without a space, so
|
|
36
39
|
* `…4711EREF+` may be where one line ends and the next begins.
|
|
40
|
+
*
|
|
41
|
+
* The remittance information (`SVWZ+`) and the ultimate debtor and creditor (`ABWA+`, `ABWE+`) are text the payer or
|
|
42
|
+
* the payee writes, which banks put after every other keyword. So from the first of them on only `ABWA+` and `ABWE+`
|
|
43
|
+
* start a value: a keyword in that text, such as `IBAN+`, is part of it. A keyword that occurs twice has no value:
|
|
44
|
+
* which of the two the bank wrote cannot be told, since a reference the payee chose, such as `EREF+`, comes before
|
|
45
|
+
* the bank's `CRED+` and may hold `CRED+…` and `SVWZ+`, which turns the bank's value into remittance information.
|
|
37
46
|
*/
|
|
38
47
|
parse(purpose: string): Map<string, string> {
|
|
39
48
|
return split(purpose, new Set());
|
|
@@ -60,11 +69,21 @@ export const SepaPurpose = Object.freeze({
|
|
|
60
69
|
function split(purpose: string, partStarts: ReadonlySet<number>): Map<string, string> {
|
|
61
70
|
const atWordStart = (index: number): boolean =>
|
|
62
71
|
index === 0 || partStarts.has(index) || !isCapitalLetter(purpose, index - 1);
|
|
63
|
-
const
|
|
72
|
+
const occurrences = new Map<string, number>();
|
|
73
|
+
const matches: RegExpExecArray[] = [];
|
|
74
|
+
let inPayerText = false;
|
|
75
|
+
for (const m of purpose.matchAll(keywordPattern)) {
|
|
76
|
+
if (!atWordStart(m.index)) continue;
|
|
77
|
+
const keyword = m[1]!;
|
|
78
|
+
occurrences.set(keyword, (occurrences.get(keyword) ?? 0) + 1);
|
|
79
|
+
if (inPayerText && !afterRemittance.has(keyword)) continue;
|
|
80
|
+
inPayerText ||= keyword === 'SVWZ' || afterRemittance.has(keyword);
|
|
81
|
+
matches.push(m);
|
|
82
|
+
}
|
|
64
83
|
const result = new Map<string, string>();
|
|
65
84
|
matches.forEach((m, i) => {
|
|
66
85
|
const end = i + 1 < matches.length ? matches[i + 1]!.index : purpose.length;
|
|
67
|
-
result.set(m[1]!, purpose.substring(m.index + m[0].length, end).trim());
|
|
86
|
+
if (occurrences.get(m[1]!) === 1) result.set(m[1]!, purpose.substring(m.index + m[0].length, end).trim());
|
|
68
87
|
});
|
|
69
88
|
return result;
|
|
70
89
|
}
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* @packageDocumentation
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
export { FinTs3Client, type FinTs3ClientInit } from './client.js';
|
|
10
11
|
export { Account, AccountDataStatus, type AccountCategory, type AccountInit } from './core/account.js';
|
|
11
12
|
export { Amount } from './core/amount.js';
|
|
12
13
|
export { Balance, type BalanceInit } from './core/balance.js';
|
|
@@ -27,11 +28,13 @@ export {
|
|
|
27
28
|
FinTsDialogAbortedError,
|
|
28
29
|
FinTsError,
|
|
29
30
|
FinTsLoginDataChangedError,
|
|
31
|
+
FinTsLoginRefusedError,
|
|
30
32
|
FinTsPinError,
|
|
31
33
|
FinTsProtocolError,
|
|
32
34
|
FinTsStateError,
|
|
33
35
|
FinTsTanCancelledError,
|
|
34
36
|
FinTsTanError,
|
|
37
|
+
FinTsTanSetupError,
|
|
35
38
|
FinTsTransportError,
|
|
36
39
|
FinTsUnsupportedError,
|
|
37
40
|
} from './core/errors.js';
|
|
@@ -43,7 +46,7 @@ export {
|
|
|
43
46
|
type TransactionStatus,
|
|
44
47
|
} from './core/transaction.js';
|
|
45
48
|
export { HttpTransport, type FinTsTransport, type HttpTransportOptions } from './core/transport.js';
|
|
46
|
-
export { Camt } from './formats/camt.js';
|
|
49
|
+
export { Camt, type CamtStatement } from './formats/camt.js';
|
|
47
50
|
export { Mt940, type Mt940Options, type Mt940Statement } from './formats/mt940.js';
|
|
48
51
|
export { SepaPurpose } from './formats/sepa-purpose.js';
|
|
49
52
|
export {
|
|
@@ -65,4 +68,3 @@ export {
|
|
|
65
68
|
type TanMethodInit,
|
|
66
69
|
} from './tan/tan-models.js';
|
|
67
70
|
export { FormatError } from './util/errors.js';
|
|
68
|
-
export { FinTs3Client, type FinTs3ClientInit } from './v3/client.js';
|