lib-fints 1.5.0 → 1.5.1
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/README.md +21 -17
- package/dist/accountDescriptor.js +65 -0
- package/dist/bankAccount.js +8 -0
- package/dist/client.js +41 -40
- package/dist/config.js +57 -10
- package/dist/interactions/balanceInteraction.js +10 -8
- package/dist/interactions/creditcardStatementInteraction.js +8 -7
- package/dist/interactions/electronicStatementInteraction.js +6 -5
- package/dist/interactions/portfolioInteraction.js +9 -7
- package/dist/interactions/sepaAccountInteraction.js +6 -5
- package/dist/interactions/statementInteractionCAMT.js +6 -5
- package/dist/interactions/statementInteractionMT940.js +7 -6
- package/dist/message.js +5 -1
- package/dist/segments/HKKAZ.js +1 -1
- package/dist/tests/accountDescriptor.test.js +99 -0
- package/dist/tests/accountReference.test.js +126 -0
- package/dist/tests/interactionAccountDescriptor.test.js +153 -0
- package/dist/tests/message.test.js +18 -0
- package/dist/types/accountDescriptor.d.ts +49 -0
- package/dist/types/accountDescriptor.d.ts.map +1 -0
- package/dist/types/bankAccount.d.ts +13 -0
- package/dist/types/bankAccount.d.ts.map +1 -1
- package/dist/types/client.d.ts +21 -20
- package/dist/types/client.d.ts.map +1 -1
- package/dist/types/config.d.ts +27 -6
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/interactions/balanceInteraction.d.ts +3 -2
- package/dist/types/interactions/balanceInteraction.d.ts.map +1 -1
- package/dist/types/interactions/creditcardStatementInteraction.d.ts +3 -2
- package/dist/types/interactions/creditcardStatementInteraction.d.ts.map +1 -1
- package/dist/types/interactions/electronicStatementInteraction.d.ts +3 -2
- package/dist/types/interactions/electronicStatementInteraction.d.ts.map +1 -1
- package/dist/types/interactions/portfolioInteraction.d.ts +3 -2
- package/dist/types/interactions/portfolioInteraction.d.ts.map +1 -1
- package/dist/types/interactions/sepaAccountInteraction.d.ts +3 -2
- package/dist/types/interactions/sepaAccountInteraction.d.ts.map +1 -1
- package/dist/types/interactions/statementInteractionCAMT.d.ts +3 -2
- package/dist/types/interactions/statementInteractionCAMT.d.ts.map +1 -1
- package/dist/types/interactions/statementInteractionMT940.d.ts +3 -2
- package/dist/types/interactions/statementInteractionMT940.d.ts.map +1 -1
- package/dist/types/message.d.ts.map +1 -1
- package/dist/types/segments/HKKAZ.d.ts +2 -2
- package/dist/types/segments/HKKAZ.d.ts.map +1 -1
- package/dist/types/tests/accountDescriptor.test.d.ts +2 -0
- package/dist/types/tests/accountDescriptor.test.d.ts.map +1 -0
- package/dist/types/tests/accountReference.test.d.ts +2 -0
- package/dist/types/tests/accountReference.test.d.ts.map +1 -0
- package/dist/types/tests/interactionAccountDescriptor.test.d.ts +2 -0
- package/dist/types/tests/interactionAccountDescriptor.test.d.ts.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,14 +85,17 @@ Finally you can start fetching balances or statements:
|
|
|
85
85
|
// for simplicity, use the first account
|
|
86
86
|
const account = syncResponse.bankingInformation.upd.bankAccounts[0];
|
|
87
87
|
|
|
88
|
+
// Account-specific methods accept either an account number or a BankAccount.
|
|
89
|
+
// Pass the account object when multiple accounts share the same number.
|
|
90
|
+
|
|
88
91
|
// fetch the current balance
|
|
89
|
-
const balanceResponse = await client.getAccountBalance(account
|
|
92
|
+
const balanceResponse = await client.getAccountBalance(account);
|
|
90
93
|
|
|
91
94
|
// fetch all available statements
|
|
92
|
-
const statementResponse = await client.getAccountStatements(account
|
|
95
|
+
const statementResponse = await client.getAccountStatements(account);
|
|
93
96
|
|
|
94
97
|
// or fetch portfolio from a securities account
|
|
95
|
-
client.getPortfolio(account
|
|
98
|
+
client.getPortfolio(account);
|
|
96
99
|
```
|
|
97
100
|
|
|
98
101
|
These are only the most basic steps needed to retrieve information from the bank. There are still some unanswered questions like "how to handle TANs" or "how to avoid synchronizations every time you start a new session". These are explained in the corresponding sections below.
|
|
@@ -110,7 +113,7 @@ const rl = readline.createInterface({
|
|
|
110
113
|
output: process.stdout,
|
|
111
114
|
});
|
|
112
115
|
|
|
113
|
-
let response = await client.getAccountStatements(account
|
|
116
|
+
let response = await client.getAccountStatements(account);
|
|
114
117
|
|
|
115
118
|
if (!response.success) {
|
|
116
119
|
return;
|
|
@@ -197,11 +200,11 @@ The following table shows all transactions supported by the FinTSClient interfac
|
|
|
197
200
|
| Transaction | Method | Description | FinTS Segment(s) | TAN Support | Account-Specific |
|
|
198
201
|
| -------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------- | -------------------------- | ----------- | ---------------- |
|
|
199
202
|
| **Synchronization** | `synchronize()` | Synchronizes bank and account information, updating config.bankingInformation | HKIDN, HKVVB, HKSYN, HKTAB | ✓ | ❌ |
|
|
200
|
-
| **Account Balance** | `getAccountBalance(
|
|
201
|
-
| **Account Statements** | `getAccountStatements(
|
|
202
|
-
| **Portfolio** | `getPortfolio(
|
|
203
|
-
| **Credit Card Statements** | `getCreditCardStatements(
|
|
204
|
-
| **Electronic Statements** | `getElectronicStatements(
|
|
203
|
+
| **Account Balance** | `getAccountBalance(account: AccountRef)` | Fetches the current balance for a specific account | HKSAL | ✓ | ✓ |
|
|
204
|
+
| **Account Statements** | `getAccountStatements(account: AccountRef, from?, to?)` | Fetches account transactions/statements for a date range (MT940 or CAMT format) | HKKAZ, HKCAZ | ✓ | ✓ |
|
|
205
|
+
| **Portfolio** | `getPortfolio(account: AccountRef, currency?, priceQuality?, maxEntries?)` | Fetches securities portfolio information for depot accounts | HKWPD | ✓ | ✓ |
|
|
206
|
+
| **Credit Card Statements** | `getCreditCardStatements(account: AccountRef, from?)` | Fetches credit card statements for credit card accounts | DKKKU | ✓ | ✓ |
|
|
207
|
+
| **Electronic Statements** | `getElectronicStatements(account: AccountRef, options?)` | Fetches the statement document from the electronic mailbox, usually a PDF | HKEKA | ✓ | ✓ |
|
|
205
208
|
| **TAN Method Selection** | `selectTanMethod(tanMethodId)` | Selects a TAN method by ID from available methods | - | ❌ | ❌ |
|
|
206
209
|
| **TAN Media Selection** | `selectTanMedia(tanMediaName)` | Selects a specific TAN media device by name | - | ❌ | ❌ |
|
|
207
210
|
|
|
@@ -211,11 +214,11 @@ For each account-specific transaction, the client provides corresponding `can*`
|
|
|
211
214
|
|
|
212
215
|
| Support Check Method | Purpose |
|
|
213
216
|
| -------------------------------------------- | --------------------------------------------------------------- |
|
|
214
|
-
| `canGetAccountBalance(
|
|
215
|
-
| `canGetAccountStatements(
|
|
216
|
-
| `canGetPortfolio(
|
|
217
|
-
| `canGetCreditCardStatements(
|
|
218
|
-
| `canGetElectronicStatements(
|
|
217
|
+
| `canGetAccountBalance(account?: AccountRef)` | Checks if account balance fetching is supported |
|
|
218
|
+
| `canGetAccountStatements(account?: AccountRef)` | Checks if account statements fetching is supported (MT940/CAMT) |
|
|
219
|
+
| `canGetPortfolio(account?: AccountRef)` | Checks if portfolio information fetching is supported |
|
|
220
|
+
| `canGetCreditCardStatements(account?: AccountRef)` | Checks if credit card statements fetching is supported |
|
|
221
|
+
| `canGetElectronicStatements(account?: AccountRef)` | Checks if electronic account statements fetching is supported |
|
|
219
222
|
|
|
220
223
|
### Transaction Parameters
|
|
221
224
|
|
|
@@ -246,12 +249,12 @@ if (config.isTransactionSupported('HKWPD')) {
|
|
|
246
249
|
}
|
|
247
250
|
```
|
|
248
251
|
|
|
249
|
-
#### `config.isAccountTransactionSupported(
|
|
252
|
+
#### `config.isAccountTransactionSupported(account: AccountRef, transId: string): boolean`
|
|
250
253
|
|
|
251
254
|
Checks whether a specific transaction type is supported for a particular account.
|
|
252
255
|
|
|
253
256
|
```typescript
|
|
254
|
-
if (config.isAccountTransactionSupported(
|
|
257
|
+
if (config.isAccountTransactionSupported(account, 'HKWPD')) {
|
|
255
258
|
console.log('Account supports portfolio requests');
|
|
256
259
|
}
|
|
257
260
|
```
|
|
@@ -280,6 +283,7 @@ Implementing further transactions should be straight forward and contributions a
|
|
|
280
283
|
|
|
281
284
|
### Successfully tested with the following banks
|
|
282
285
|
|
|
286
|
+
- comdirect
|
|
283
287
|
- DKB
|
|
284
288
|
- ING-DiBa
|
|
285
289
|
- Renault Bank Direkt
|
|
@@ -301,7 +305,7 @@ As this is a free-time project, a lot of things are still remaining which could
|
|
|
301
305
|
|
|
302
306
|
## License
|
|
303
307
|
|
|
304
|
-
This project is licensed under the LGPL
|
|
308
|
+
This project is licensed under the LGPL-2.1-or-later License - see the [LICENSE](LICENSE) file for details
|
|
305
309
|
|
|
306
310
|
## References
|
|
307
311
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { HKSPA } from './segments/HKSPA.js';
|
|
2
|
+
/**
|
|
3
|
+
* Builds the account connection ("Kontoverbindung") a segment carries.
|
|
4
|
+
*
|
|
5
|
+
* FinTS has two forms, and segments pick one by version: the national form (KTV,
|
|
6
|
+
* account number + sub-account + bank) and the international one (KTI, which adds
|
|
7
|
+
* IBAN and BIC and makes every field optional). The international form allows the
|
|
8
|
+
* national fields to be present as well, but only where the bank permits it — and
|
|
9
|
+
* the bank says so in the HISPAS parameters, in `nationalAccountAllowed`.
|
|
10
|
+
*
|
|
11
|
+
* Filling both halves regardless is rejected by banks that set the flag to false.
|
|
12
|
+
* Measured at comdirect (BLZ 2004xxxx), same account, same range, same session:
|
|
13
|
+
*
|
|
14
|
+
* IBAN + BIC + number + sub-account + bank → 3010 "Kontonummer ist ungültig", 0 statements
|
|
15
|
+
* IBAN + BIC → 0020 "Auftrag ausgeführt", 19 statements
|
|
16
|
+
* IBAN → 0020 "Auftrag ausgeführt", 19 statements
|
|
17
|
+
* number + sub-account + bank → 3010 "Kontonummer ist ungültig", 0 statements
|
|
18
|
+
*
|
|
19
|
+
* The last line is why this is a rule about the national *fields* rather than about
|
|
20
|
+
* the combination: the bank rejects them in a KTI even when no IBAN accompanies
|
|
21
|
+
* them.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* The national form. Built field by field rather than by spreading the account and
|
|
25
|
+
* blanking what does not belong: the data group has exactly these three fields, and
|
|
26
|
+
* saying so is clearer than relying on the encoder to ignore the rest.
|
|
27
|
+
*/
|
|
28
|
+
export function nationalAccount(account) {
|
|
29
|
+
return {
|
|
30
|
+
accountNumber: account.accountNumber,
|
|
31
|
+
subAccountId: account.subAccountId,
|
|
32
|
+
bank: account.bank,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The international form. IBAN and BIC always; the national fields only where the
|
|
37
|
+
* bank's HISPAS parameters allow them.
|
|
38
|
+
*
|
|
39
|
+
* An account without an IBAN — a securities account, typically — has nothing else
|
|
40
|
+
* to identify it with, so it keeps the national fields whatever the flag says. A
|
|
41
|
+
* request the bank refuses is more useful than one it cannot resolve at all.
|
|
42
|
+
*
|
|
43
|
+
* A bank that announces no HISPAS at all keeps both halves, exactly as before. The
|
|
44
|
+
* specification would read absent permission as no permission, but this library has
|
|
45
|
+
* already been round that loop: #20 reduced the CAMT descriptor to IBAN and BIC for
|
|
46
|
+
* comdirect, #25 reported Postbank answering "Angaben zur nationalen Kontoverbindung
|
|
47
|
+
* für Identifikation erforderlich", and the reduction was reverted. Only a bank that
|
|
48
|
+
* says `false` gets the shorter form, so no bank that works today can regress on a
|
|
49
|
+
* rule it never stated.
|
|
50
|
+
*/
|
|
51
|
+
export function internationalAccount(config, account) {
|
|
52
|
+
if (!account.iban) {
|
|
53
|
+
return nationalAccount(account);
|
|
54
|
+
}
|
|
55
|
+
const hispas = config.getTransactionParameters(HKSPA.Id);
|
|
56
|
+
return hispas?.nationalAccountAllowed === false
|
|
57
|
+
? { iban: account.iban, bic: account.bic }
|
|
58
|
+
: {
|
|
59
|
+
iban: account.iban,
|
|
60
|
+
bic: account.bic,
|
|
61
|
+
accountNumber: account.accountNumber,
|
|
62
|
+
subAccountId: account.subAccountId,
|
|
63
|
+
bank: account.bank,
|
|
64
|
+
};
|
|
65
|
+
}
|
package/dist/bankAccount.js
CHANGED
|
@@ -11,6 +11,14 @@ export var AccountType;
|
|
|
11
11
|
AccountType["InsurancePolicy"] = "InsurancePolicy";
|
|
12
12
|
AccountType["Miscellaneous"] = "Miscellaneous";
|
|
13
13
|
})(AccountType || (AccountType = {}));
|
|
14
|
+
/** How an account reference reads in an error message. */
|
|
15
|
+
export function describeAccount(account) {
|
|
16
|
+
if (typeof account === 'string')
|
|
17
|
+
return account;
|
|
18
|
+
return account.subAccountId
|
|
19
|
+
? `${account.accountNumber} (${account.subAccountId})`
|
|
20
|
+
: account.accountNumber;
|
|
21
|
+
}
|
|
14
22
|
export function finTsAccountTypeToEnum(accountType) {
|
|
15
23
|
if (accountType >= 1 && accountType <= 9)
|
|
16
24
|
return AccountType.CheckingAccount;
|
package/dist/client.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { describeAccount } from './bankAccount.js';
|
|
1
2
|
import { FinTSConfig } from './config.js';
|
|
2
3
|
import { Dialog } from './dialog.js';
|
|
3
4
|
import { BalanceInteraction, } from './interactions/balanceInteraction.js';
|
|
@@ -67,21 +68,21 @@ export class FinTSClient {
|
|
|
67
68
|
}
|
|
68
69
|
/**
|
|
69
70
|
* Checks if the bank supports fetching an account balance in general or for the given account number when provided
|
|
70
|
-
* @param
|
|
71
|
+
* @param account when the account number is provided, checks if the account supports fetching the balance
|
|
71
72
|
* @returns true if the bank (and account) supports fetching the account balance
|
|
72
73
|
*/
|
|
73
|
-
canGetAccountBalance(
|
|
74
|
-
return
|
|
75
|
-
? this.config.isAccountTransactionSupported(
|
|
74
|
+
canGetAccountBalance(account) {
|
|
75
|
+
return account
|
|
76
|
+
? this.config.isAccountTransactionSupported(account, HKSAL.Id)
|
|
76
77
|
: this.config.isTransactionSupported(HKSAL.Id);
|
|
77
78
|
}
|
|
78
79
|
/**
|
|
79
80
|
* Fetches the account balance for the given account number
|
|
80
|
-
* @param
|
|
81
|
+
* @param account - the account number to fetch the balance for, must be an account available in the config.baningInformation.UPD.accounts
|
|
81
82
|
* @returns the account balance response
|
|
82
83
|
*/
|
|
83
|
-
async getAccountBalance(
|
|
84
|
-
const response = await this.startCustomerOrderInteraction(new BalanceInteraction(
|
|
84
|
+
async getAccountBalance(account) {
|
|
85
|
+
const response = await this.startCustomerOrderInteraction(new BalanceInteraction(account));
|
|
85
86
|
return response;
|
|
86
87
|
}
|
|
87
88
|
/**
|
|
@@ -95,14 +96,14 @@ export class FinTSClient {
|
|
|
95
96
|
}
|
|
96
97
|
/**
|
|
97
98
|
* Checks if the bank supports fetching account statements in general or for the given account number when provided
|
|
98
|
-
* @param
|
|
99
|
+
* @param account when the account number is provided, checks if the account supports fetching of statements
|
|
99
100
|
* @returns true if the bank (and account) supports fetching account statements
|
|
100
101
|
*/
|
|
101
|
-
canGetAccountStatements(
|
|
102
|
-
if (
|
|
102
|
+
canGetAccountStatements(account) {
|
|
103
|
+
if (account) {
|
|
103
104
|
// Check if either CAMT or MT940 is supported for this account
|
|
104
|
-
return (this.config.isAccountTransactionSupported(
|
|
105
|
-
this.config.isAccountTransactionSupported(
|
|
105
|
+
return (this.config.isAccountTransactionSupported(account, HKCAZ.Id) ||
|
|
106
|
+
this.config.isAccountTransactionSupported(account, HKKAZ.Id));
|
|
106
107
|
}
|
|
107
108
|
else {
|
|
108
109
|
// Check if either CAMT or MT940 is supported by the bank
|
|
@@ -111,26 +112,26 @@ export class FinTSClient {
|
|
|
111
112
|
}
|
|
112
113
|
/**
|
|
113
114
|
* Fetches the account statements for the given account number
|
|
114
|
-
* @param
|
|
115
|
+
* @param account - the account number to fetch the statements for, must be an account available in the config.baningInformation.UPD.accounts
|
|
115
116
|
* @param from - an optional start date of the period to fetch the statements for
|
|
116
117
|
* @param to - an optional end date of the period to fetch the statements for
|
|
117
118
|
* @param preferCamt - whether to prefer CAMT format over MT940 when both are supported (default: true)
|
|
118
119
|
* @returns an account statements response containing an array of statements
|
|
119
120
|
*/
|
|
120
|
-
async getAccountStatements(
|
|
121
|
+
async getAccountStatements(account, from, to, preferCamt = true) {
|
|
121
122
|
// Check what formats the bank supports
|
|
122
|
-
const camtSupported = this.config.isAccountTransactionSupported(
|
|
123
|
-
const mt940Supported = this.config.isAccountTransactionSupported(
|
|
123
|
+
const camtSupported = this.config.isAccountTransactionSupported(account, 'HKCAZ');
|
|
124
|
+
const mt940Supported = this.config.isAccountTransactionSupported(account, 'HKKAZ');
|
|
124
125
|
if (!camtSupported && !mt940Supported) {
|
|
125
|
-
throw Error(`Account ${
|
|
126
|
+
throw Error(`Account ${describeAccount(account)} does not support account statements`);
|
|
126
127
|
}
|
|
127
128
|
// Choose format based on support and preference
|
|
128
129
|
const useCAMT = (preferCamt && camtSupported) || (!mt940Supported && camtSupported);
|
|
129
130
|
if (useCAMT) {
|
|
130
|
-
return (await this.startCustomerOrderInteraction(new StatementInteractionCAMT(
|
|
131
|
+
return (await this.startCustomerOrderInteraction(new StatementInteractionCAMT(account, from, to)));
|
|
131
132
|
}
|
|
132
133
|
else {
|
|
133
|
-
return (await this.startCustomerOrderInteraction(new StatementInteractionMT940(
|
|
134
|
+
return (await this.startCustomerOrderInteraction(new StatementInteractionMT940(account, from, to)));
|
|
134
135
|
}
|
|
135
136
|
}
|
|
136
137
|
/**
|
|
@@ -144,24 +145,24 @@ export class FinTSClient {
|
|
|
144
145
|
}
|
|
145
146
|
/**
|
|
146
147
|
* Checks if the bank supports fetching portfolio information in general or for the given account number when provided
|
|
147
|
-
* @param
|
|
148
|
+
* @param account when the account number is provided, checks if the account supports fetching of portfolio information
|
|
148
149
|
* @returns true if the bank (and account) supports fetching portfolio information
|
|
149
150
|
*/
|
|
150
|
-
canGetPortfolio(
|
|
151
|
-
return
|
|
152
|
-
? this.config.isAccountTransactionSupported(
|
|
151
|
+
canGetPortfolio(account) {
|
|
152
|
+
return account
|
|
153
|
+
? this.config.isAccountTransactionSupported(account, HKWPD.Id)
|
|
153
154
|
: this.config.isTransactionSupported(HKWPD.Id);
|
|
154
155
|
}
|
|
155
156
|
/**
|
|
156
157
|
* Fetches the portfolio information for the given depot account number
|
|
157
|
-
* @param
|
|
158
|
+
* @param account - the depot account number to fetch the portfolio for, must be an account available in the config.bankingInformation.UPD.accounts
|
|
158
159
|
* @param currency - optional currency filter for the portfolio statement
|
|
159
160
|
* @param priceQuality - optional price quality filter ('1' for real-time, '2' for delayed)
|
|
160
161
|
* @param maxEntries - optional maximum number of entries to retrieve
|
|
161
162
|
* @returns a portfolio response containing holdings and total value
|
|
162
163
|
*/
|
|
163
|
-
async getPortfolio(
|
|
164
|
-
return (await this.startCustomerOrderInteraction(new PortfolioInteraction(
|
|
164
|
+
async getPortfolio(account, currency, priceQuality, maxEntries) {
|
|
165
|
+
return (await this.startCustomerOrderInteraction(new PortfolioInteraction(account, currency, priceQuality, maxEntries)));
|
|
165
166
|
}
|
|
166
167
|
/**
|
|
167
168
|
* Continues the portfolio fetching when a TAN is required
|
|
@@ -175,24 +176,24 @@ export class FinTSClient {
|
|
|
175
176
|
}
|
|
176
177
|
/**
|
|
177
178
|
* Checks if the bank supports fetching credit card statements in general or for the given account number
|
|
178
|
-
* @param
|
|
179
|
+
* @param account when the account number is provided, checks if the account supports fetching of statements
|
|
179
180
|
* @returns true if the bank (and account) supports fetching credit card statements
|
|
180
181
|
*/
|
|
181
|
-
canGetCreditCardStatements(
|
|
182
|
-
return
|
|
183
|
-
? this.config.isAccountTransactionSupported(
|
|
182
|
+
canGetCreditCardStatements(account) {
|
|
183
|
+
return account
|
|
184
|
+
? this.config.isAccountTransactionSupported(account, DKKKU.Id)
|
|
184
185
|
: this.config.isTransactionSupported(DKKKU.Id);
|
|
185
186
|
}
|
|
186
187
|
/**
|
|
187
188
|
* Fetches the credit card statements for the given account number
|
|
188
|
-
* @param
|
|
189
|
+
* @param account - the account number to fetch the statements for, must be a credit card account available
|
|
189
190
|
* in the config.baningInformation.UPD.accounts
|
|
190
191
|
* @param from - an optional start date of the period to fetch the statements for
|
|
191
192
|
* @param to - an optional end date of the period to fetch the statements for
|
|
192
193
|
* @returns an account statements response containing an array of statements
|
|
193
194
|
*/
|
|
194
|
-
async getCreditCardStatements(
|
|
195
|
-
return (await this.startCustomerOrderInteraction(new CreditCardStatementInteraction(
|
|
195
|
+
async getCreditCardStatements(account, from) {
|
|
196
|
+
return (await this.startCustomerOrderInteraction(new CreditCardStatementInteraction(account, from)));
|
|
196
197
|
}
|
|
197
198
|
/**
|
|
198
199
|
* Continues the credit card statements fetching when a TAN is required
|
|
@@ -205,12 +206,12 @@ export class FinTSClient {
|
|
|
205
206
|
}
|
|
206
207
|
/**
|
|
207
208
|
* Checks if the bank supports fetching electronic account statements in general or for the given account number
|
|
208
|
-
* @param
|
|
209
|
+
* @param account when the account number is provided, checks if the account supports fetching of electronic statements
|
|
209
210
|
* @returns true if the bank (and account) supports fetching electronic account statements
|
|
210
211
|
*/
|
|
211
|
-
canGetElectronicStatements(
|
|
212
|
-
return
|
|
213
|
-
? this.config.isAccountTransactionSupported(
|
|
212
|
+
canGetElectronicStatements(account) {
|
|
213
|
+
return account
|
|
214
|
+
? this.config.isAccountTransactionSupported(account, HKEKA.Id)
|
|
214
215
|
: this.config.isTransactionSupported(HKEKA.Id);
|
|
215
216
|
}
|
|
216
217
|
/**
|
|
@@ -222,12 +223,12 @@ export class FinTSClient {
|
|
|
222
223
|
* fetch the next one. Banks that set `receiptRequired` in their HIEKAS parameters keep
|
|
223
224
|
* offering a statement until it has been acknowledged with its receipt.
|
|
224
225
|
*
|
|
225
|
-
* @param
|
|
226
|
+
* @param account - the account number to fetch the statement for, must be an account available in the config.bankingInformation.upd.accounts
|
|
226
227
|
* @param options - optional format, statement number and year, entry limit and offset
|
|
227
228
|
* @returns a response containing the statement documents and the offset of a waiting successor
|
|
228
229
|
*/
|
|
229
|
-
async getElectronicStatements(
|
|
230
|
-
return (await this.startCustomerOrderInteraction(new ElectronicStatementInteraction(
|
|
230
|
+
async getElectronicStatements(account, options) {
|
|
231
|
+
return (await this.startCustomerOrderInteraction(new ElectronicStatementInteraction(account, options)));
|
|
231
232
|
}
|
|
232
233
|
/**
|
|
233
234
|
* Continues the electronic account statement fetching when a TAN is required
|
package/dist/config.js
CHANGED
|
@@ -157,13 +157,36 @@ export class FinTSConfig {
|
|
|
157
157
|
return (this.bankingInformation.bpd?.allowedTransactions.find((t) => t.transId === transId) !==
|
|
158
158
|
undefined);
|
|
159
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* The account the bank meant, without demanding that it be unambiguous.
|
|
162
|
+
*
|
|
163
|
+
* For entries the *bank* supplied — a SEPA account from HISPA, say — rather than
|
|
164
|
+
* ones a caller asked for. A caller who names an ambiguous account has made a
|
|
165
|
+
* mistake worth an exception; a bank listing its own accounts has not, and
|
|
166
|
+
* throwing there would break every dialog at an institution that shares numbers.
|
|
167
|
+
*
|
|
168
|
+
* @param account An account number with, where the bank gave one, its sub-account id
|
|
169
|
+
*/
|
|
170
|
+
matchBankAccount(account) {
|
|
171
|
+
const accounts = this.bankingInformation.upd?.bankAccounts ?? [];
|
|
172
|
+
const exactMatch = accounts.find((a) => a.accountNumber === account.accountNumber && a.subAccountId === account.subAccountId);
|
|
173
|
+
if (exactMatch)
|
|
174
|
+
return exactMatch;
|
|
175
|
+
// A tolerance, not a rule: B.3.1 requires the sub-account id to appear the same
|
|
176
|
+
// way in the UPD and in HKSPA/HISPA, and a bank that omits it here has not kept
|
|
177
|
+
// to that. Refusing would cost the IBAN for an account that is otherwise
|
|
178
|
+
// perfectly identified, so a number only one account has still identifies it.
|
|
179
|
+
// One that several share does not, and guessing is what this change exists to stop.
|
|
180
|
+
const matches = accounts.filter((a) => a.accountNumber === account.accountNumber);
|
|
181
|
+
return matches.length === 1 ? matches[0] : undefined;
|
|
182
|
+
}
|
|
160
183
|
/**
|
|
161
184
|
* Checks if a transaction is supported for a specific account
|
|
162
|
-
* @param
|
|
185
|
+
* @param account An account number, or an account from `bankingInformation.upd.bankAccounts`
|
|
163
186
|
* @param transId The transaction ID
|
|
164
187
|
*/
|
|
165
|
-
isAccountTransactionSupported(
|
|
166
|
-
const bankAccount = this.getBankAccount(
|
|
188
|
+
isAccountTransactionSupported(account, transId) {
|
|
189
|
+
const bankAccount = this.getBankAccount(account);
|
|
167
190
|
return !!bankAccount.allowedTransactions?.find((t) => t.transId === transId);
|
|
168
191
|
}
|
|
169
192
|
/**
|
|
@@ -181,14 +204,38 @@ export class FinTSConfig {
|
|
|
181
204
|
return maxSupportedversion;
|
|
182
205
|
}
|
|
183
206
|
/**
|
|
184
|
-
*
|
|
185
|
-
*
|
|
207
|
+
* Resolves an account reference against the accounts the bank reported.
|
|
208
|
+
*
|
|
209
|
+
* A number alone is enough wherever it is unique, which is the usual case. Where
|
|
210
|
+
* it is not, this throws instead of picking one: FinTS identifies an account by
|
|
211
|
+
* number *and* sub-account id, so a number that matches two accounts does not say
|
|
212
|
+
* which one is meant, and answering for the wrong one produces a balance or a list
|
|
213
|
+
* of transactions that belongs to a different account with nothing to indicate it.
|
|
214
|
+
*
|
|
215
|
+
* @param account An account number, or an account from `bankingInformation.upd.bankAccounts`
|
|
186
216
|
*/
|
|
187
|
-
getBankAccount(
|
|
188
|
-
const
|
|
189
|
-
if (
|
|
190
|
-
|
|
217
|
+
getBankAccount(account) {
|
|
218
|
+
const accounts = this.bankingInformation.upd?.bankAccounts ?? [];
|
|
219
|
+
if (typeof account !== 'string') {
|
|
220
|
+
// Resolved against the UPD rather than trusted as given: the caller may hold
|
|
221
|
+
// an account from an earlier session, and the entry the bank sent this time
|
|
222
|
+
// is the one carrying the current allowed transactions.
|
|
223
|
+
const matchedAccount = accounts.find((a) => a.accountNumber === account.accountNumber && a.subAccountId === account.subAccountId);
|
|
224
|
+
if (!matchedAccount) {
|
|
225
|
+
throw Error(`Account ${account.accountNumber}${account.subAccountId ? ` (${account.subAccountId})` : ''} not found in UPD`);
|
|
226
|
+
}
|
|
227
|
+
return matchedAccount;
|
|
228
|
+
}
|
|
229
|
+
const matches = accounts.filter((a) => a.accountNumber === account);
|
|
230
|
+
if (matches.length === 0) {
|
|
231
|
+
throw Error(`Account ${account} not found in UPD`);
|
|
232
|
+
}
|
|
233
|
+
if (matches.length > 1) {
|
|
234
|
+
const subAccountIds = matches.map((a) => a.subAccountId ?? '(none)').join(', ');
|
|
235
|
+
throw Error(`Account number ${account} is not unique in UPD: ${matches.length} accounts share it, ` +
|
|
236
|
+
`with sub-account ids ${subAccountIds}. Pass the account itself instead of its number, ` +
|
|
237
|
+
`from bankingInformation.upd.bankAccounts.`);
|
|
191
238
|
}
|
|
192
|
-
return
|
|
239
|
+
return matches[0];
|
|
193
240
|
}
|
|
194
241
|
}
|
|
@@ -1,26 +1,28 @@
|
|
|
1
|
+
import { internationalAccount, nationalAccount } from '../accountDescriptor.js';
|
|
2
|
+
import { describeAccount } from '../bankAccount.js';
|
|
1
3
|
import { CreditDebit } from '../codes.js';
|
|
2
4
|
import { HISAL } from '../segments/HISAL.js';
|
|
3
5
|
import { HKSAL } from '../segments/HKSAL.js';
|
|
4
6
|
import { CustomerOrderInteraction } from './customerInteraction.js';
|
|
5
7
|
export class BalanceInteraction extends CustomerOrderInteraction {
|
|
6
|
-
|
|
7
|
-
constructor(
|
|
8
|
+
account;
|
|
9
|
+
constructor(account) {
|
|
8
10
|
super(HKSAL.Id, HISAL.Id);
|
|
9
|
-
this.
|
|
11
|
+
this.account = account;
|
|
10
12
|
}
|
|
11
13
|
createSegments(init) {
|
|
12
|
-
const bankAccount = init.getBankAccount(this.
|
|
13
|
-
if (!init.isAccountTransactionSupported(this.
|
|
14
|
-
throw Error(`Account ${this.
|
|
14
|
+
const bankAccount = init.getBankAccount(this.account);
|
|
15
|
+
if (!init.isAccountTransactionSupported(this.account, this.segId)) {
|
|
16
|
+
throw Error(`Account ${describeAccount(this.account)} does not support business transaction '${this.segId}'`);
|
|
15
17
|
}
|
|
16
18
|
const version = init.getMaxSupportedTransactionVersion(HKSAL.Id);
|
|
17
19
|
if (!version) {
|
|
18
20
|
throw Error(`There is no supported version for business transaction '${HKSAL.Id}`);
|
|
19
21
|
}
|
|
20
|
-
const account = version <= 6 ?
|
|
22
|
+
const account = version <= 6 ? nationalAccount(bankAccount) : internationalAccount(init, bankAccount);
|
|
21
23
|
const hksal = {
|
|
22
24
|
header: { segId: HKSAL.Id, segNr: 0, version: version },
|
|
23
|
-
account,
|
|
25
|
+
account: account,
|
|
24
26
|
allAccounts: false,
|
|
25
27
|
};
|
|
26
28
|
return [hksal];
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
+
import { describeAccount } from '../bankAccount.js';
|
|
1
2
|
import { DIKKU } from '../segments/DIKKU.js';
|
|
2
3
|
import { DKKKU } from '../segments/DKKKU.js';
|
|
3
4
|
import { CustomerOrderInteraction } from './customerInteraction.js';
|
|
4
5
|
export class CreditCardStatementInteraction extends CustomerOrderInteraction {
|
|
5
|
-
|
|
6
|
+
account;
|
|
6
7
|
from;
|
|
7
|
-
constructor(
|
|
8
|
+
constructor(account, from) {
|
|
8
9
|
super(DKKKU.Id, DIKKU.Id);
|
|
9
|
-
this.
|
|
10
|
+
this.account = account;
|
|
10
11
|
this.from = from;
|
|
11
12
|
}
|
|
12
13
|
createSegments(init) {
|
|
13
|
-
const bankAccount = init.getBankAccount(this.
|
|
14
|
-
if (!init.isAccountTransactionSupported(this.
|
|
15
|
-
throw Error(`Account ${this.
|
|
14
|
+
const bankAccount = init.getBankAccount(this.account);
|
|
15
|
+
if (!init.isAccountTransactionSupported(this.account, this.segId)) {
|
|
16
|
+
throw Error(`Account ${describeAccount(this.account)} does not support business transaction '${this.segId}'`);
|
|
16
17
|
}
|
|
17
18
|
const account = { ...bankAccount, iban: undefined };
|
|
18
19
|
const version = 2;
|
|
@@ -58,7 +59,7 @@ export class CreditCardStatementInteraction extends CustomerOrderInteraction {
|
|
|
58
59
|
if (dikku.transactions) {
|
|
59
60
|
for (let i = 0; i < dikku.transactions.length; i++) {
|
|
60
61
|
const parts = dikku.transactions[i].split(':');
|
|
61
|
-
// const
|
|
62
|
+
// const account = parts[0];
|
|
62
63
|
const transactionDateStr = parts[1];
|
|
63
64
|
const valueDateStr = parts[2];
|
|
64
65
|
const currencyOrig = parts[5];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { internationalAccount, nationalAccount } from '../accountDescriptor.js';
|
|
1
2
|
import { HIEKA } from '../segments/HIEKA.js';
|
|
2
3
|
import { HKEKA } from '../segments/HKEKA.js';
|
|
3
4
|
import { CustomerOrderInteraction } from './customerInteraction.js';
|
|
@@ -36,15 +37,15 @@ function unwrapBase64(bytes) {
|
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
export class ElectronicStatementInteraction extends CustomerOrderInteraction {
|
|
39
|
-
|
|
40
|
+
account;
|
|
40
41
|
options;
|
|
41
|
-
constructor(
|
|
42
|
+
constructor(account, options = {}) {
|
|
42
43
|
super(HKEKA.Id, HIEKA.Id);
|
|
43
|
-
this.
|
|
44
|
+
this.account = account;
|
|
44
45
|
this.options = options;
|
|
45
46
|
}
|
|
46
47
|
createSegments(init) {
|
|
47
|
-
const bankAccount = init.getBankAccount(this.
|
|
48
|
+
const bankAccount = init.getBankAccount(this.account);
|
|
48
49
|
const version = init.getMaxSupportedTransactionVersion(HKEKA.Id);
|
|
49
50
|
if (!version) {
|
|
50
51
|
throw Error(`There is no supported version for business transaction '${HKEKA.Id}'`);
|
|
@@ -53,7 +54,7 @@ export class ElectronicStatementInteraction extends CustomerOrderInteraction {
|
|
|
53
54
|
const format = this.options.format ?? params?.supportedFormats?.[0];
|
|
54
55
|
const hkeka = {
|
|
55
56
|
header: { segId: HKEKA.Id, segNr: 0, version: version },
|
|
56
|
-
account: bankAccount,
|
|
57
|
+
account: version <= 3 ? nationalAccount(bankAccount) : internationalAccount(init, bankAccount),
|
|
57
58
|
statementFormat: format,
|
|
58
59
|
statementNumber: this.options.number,
|
|
59
60
|
statementYear: this.options.year,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { nationalAccount } from '../accountDescriptor.js';
|
|
2
|
+
import { describeAccount } from '../bankAccount.js';
|
|
1
3
|
import { Mt535Parser } from '../mt535parser.js';
|
|
2
4
|
import { HIWPD } from '../segments/HIWPD.js';
|
|
3
5
|
import { HKWPD } from '../segments/HKWPD.js';
|
|
@@ -6,25 +8,25 @@ import { CustomerOrderInteraction } from './customerInteraction.js';
|
|
|
6
8
|
* Interaction for requesting and parsing stock portfolio information (HKWPD/HIWPD)
|
|
7
9
|
*/
|
|
8
10
|
export class PortfolioInteraction extends CustomerOrderInteraction {
|
|
9
|
-
|
|
11
|
+
account;
|
|
10
12
|
currency;
|
|
11
13
|
priceQuality;
|
|
12
14
|
maxEntries;
|
|
13
15
|
paginationMarker;
|
|
14
|
-
constructor(
|
|
16
|
+
constructor(account, currency, priceQuality, maxEntries, paginationMarker) {
|
|
15
17
|
super(HKWPD.Id, HIWPD.Id);
|
|
16
|
-
this.
|
|
18
|
+
this.account = account;
|
|
17
19
|
this.currency = currency;
|
|
18
20
|
this.priceQuality = priceQuality;
|
|
19
21
|
this.maxEntries = maxEntries;
|
|
20
22
|
this.paginationMarker = paginationMarker;
|
|
21
23
|
}
|
|
22
24
|
createSegments(config) {
|
|
23
|
-
const bankAccount = config.getBankAccount(this.
|
|
24
|
-
if (!config.isAccountTransactionSupported(this.
|
|
25
|
-
throw Error(`Account ${this.
|
|
25
|
+
const bankAccount = config.getBankAccount(this.account);
|
|
26
|
+
if (!config.isAccountTransactionSupported(this.account, this.segId)) {
|
|
27
|
+
throw Error(`Account ${describeAccount(this.account)} does not support business transaction '${this.segId}'`);
|
|
26
28
|
}
|
|
27
|
-
const depotAccount =
|
|
29
|
+
const depotAccount = nationalAccount(bankAccount); // HKWPD uses KTV, which has no IBAN
|
|
28
30
|
const version = config.getMaxSupportedTransactionVersion(HKWPD.Id);
|
|
29
31
|
if (!version) {
|
|
30
32
|
throw Error(`There is no supported version for business transaction '${HKWPD.Id}'`);
|
|
@@ -4,7 +4,7 @@ import { CustomerOrderInteraction } from './customerInteraction.js';
|
|
|
4
4
|
export class SepaAccountInteraction extends CustomerOrderInteraction {
|
|
5
5
|
accounts;
|
|
6
6
|
maxEntries;
|
|
7
|
-
constructor(accounts, // optional
|
|
7
|
+
constructor(accounts, // optional: only these accounts
|
|
8
8
|
maxEntries) {
|
|
9
9
|
super(HKSPA.Id, HISPA.Id);
|
|
10
10
|
this.accounts = accounts;
|
|
@@ -18,9 +18,7 @@ export class SepaAccountInteraction extends CustomerOrderInteraction {
|
|
|
18
18
|
if (!version) {
|
|
19
19
|
throw Error(`There is no supported version for business transaction '${HKSPA.Id}'`);
|
|
20
20
|
}
|
|
21
|
-
const accounts = this.accounts?.map((
|
|
22
|
-
return init.getBankAccount(accountNumber);
|
|
23
|
-
});
|
|
21
|
+
const accounts = this.accounts?.map((account) => init.getBankAccount(account));
|
|
24
22
|
const hkspa = {
|
|
25
23
|
header: { segId: HKSPA.Id, segNr: 0, version: version },
|
|
26
24
|
accounts: accounts,
|
|
@@ -38,7 +36,10 @@ export class SepaAccountInteraction extends CustomerOrderInteraction {
|
|
|
38
36
|
bankAccount.isSepaAccount = false;
|
|
39
37
|
});
|
|
40
38
|
clientResponse.sepaAccounts.forEach((sepaAccount) => {
|
|
41
|
-
|
|
39
|
+
// Matched, not resolved: this is the bank listing its own accounts, and at an
|
|
40
|
+
// institution where two of them share a number, demanding an unambiguous
|
|
41
|
+
// answer here would fail every dialog before it reached its order.
|
|
42
|
+
const bankAccount = this.dialog?.config.matchBankAccount(sepaAccount);
|
|
42
43
|
if (bankAccount && !bankAccount.isSepaAccount) {
|
|
43
44
|
bankAccount.isSepaAccount = sepaAccount.isSepaAccount;
|
|
44
45
|
bankAccount.iban = sepaAccount.iban;
|