lib-fints 1.4.0 → 1.4.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/dist/client.js CHANGED
@@ -62,8 +62,7 @@ export class FinTSClient {
62
62
  * @returns the synchronization response
63
63
  */
64
64
  async synchronizeWithTan(tanReference, tan) {
65
- const responses = await this.continueCustomerInteractionWithTan(tanReference, tan);
66
- return responses.get(HKIDN.Id);
65
+ return await this.continueCustomerInteractionWithTan([HKIDN.Id], tanReference, tan);
67
66
  }
68
67
  /**
69
68
  * Checks if the bank supports fetching an account balance in general or for the given account number when provided
@@ -81,8 +80,8 @@ export class FinTSClient {
81
80
  * @returns the account balance response
82
81
  */
83
82
  async getAccountBalance(accountNumber) {
84
- const responses = await this.startCustomerOrderInteraction(new BalanceInteraction(accountNumber));
85
- return responses.get(HKSAL.Id);
83
+ const response = await this.startCustomerOrderInteraction(new BalanceInteraction(accountNumber));
84
+ return response;
86
85
  }
87
86
  /**
88
87
  * Continues the account balance fetching when a TAN is required
@@ -91,8 +90,7 @@ export class FinTSClient {
91
90
  * @returns the account balance response
92
91
  */
93
92
  async getAccountBalanceWithTan(tanReference, tan) {
94
- const responses = await this.continueCustomerInteractionWithTan(tanReference, tan);
95
- return responses.get(HKSAL.Id);
93
+ return (await this.continueCustomerInteractionWithTan([HKSAL.Id], tanReference, tan));
96
94
  }
97
95
  /**
98
96
  * Checks if the bank supports fetching account statements in general or for the given account number when provided
@@ -128,12 +126,10 @@ export class FinTSClient {
128
126
  // Choose format based on support and preference
129
127
  const useCAMT = (preferCamt && camtSupported) || (!mt940Supported && camtSupported);
130
128
  if (useCAMT) {
131
- const responses = await this.startCustomerOrderInteraction(new StatementInteractionCAMT(accountNumber, from, to));
132
- return responses.get(HKCAZ.Id);
129
+ return (await this.startCustomerOrderInteraction(new StatementInteractionCAMT(accountNumber, from, to)));
133
130
  }
134
131
  else {
135
- const responses = await this.startCustomerOrderInteraction(new StatementInteractionMT940(accountNumber, from, to));
136
- return responses.get(HKKAZ.Id);
132
+ return (await this.startCustomerOrderInteraction(new StatementInteractionMT940(accountNumber, from, to)));
137
133
  }
138
134
  }
139
135
  /**
@@ -143,8 +139,7 @@ export class FinTSClient {
143
139
  * @returns an account statements response containing an array of statements
144
140
  */
145
141
  async getAccountStatementsWithTan(tanReference, tan) {
146
- const responses = await this.continueCustomerInteractionWithTan(tanReference, tan);
147
- return responses.get(HKKAZ.Id);
142
+ return (await this.continueCustomerInteractionWithTan([HKCAZ.Id, HKKAZ.Id], tanReference, tan));
148
143
  }
149
144
  /**
150
145
  * Checks if the bank supports fetching portfolio information in general or for the given account number when provided
@@ -165,19 +160,17 @@ export class FinTSClient {
165
160
  * @returns a portfolio response containing holdings and total value
166
161
  */
167
162
  async getPortfolio(accountNumber, currency, priceQuality, maxEntries) {
168
- const responses = await this.startCustomerOrderInteraction(new PortfolioInteraction(accountNumber, currency, priceQuality, maxEntries));
169
- return responses.get(HKWPD.Id);
163
+ return (await this.startCustomerOrderInteraction(new PortfolioInteraction(accountNumber, currency, priceQuality, maxEntries)));
170
164
  }
171
165
  /**
172
- * Continues the portfolio fetching when a TAN is required
173
- * @param tanReference The TAN reference provided in the first call's response
174
-
175
- * @param tan The TAN entered by the user, can be omitted if a decoupled TAN method is used
176
- * @returns a portfolio response containing holdings and total value
177
- */
166
+ * Continues the portfolio fetching when a TAN is required
167
+ * @param tanReference The TAN reference provided in the first call's response
168
+
169
+ * @param tan The TAN entered by the user, can be omitted if a decoupled TAN method is used
170
+ * @returns a portfolio response containing holdings and total value
171
+ */
178
172
  async getPortfolioWithTan(tanReference, tan) {
179
- const responses = await this.continueCustomerInteractionWithTan(tanReference, tan);
180
- return responses.get(HKWPD.Id);
173
+ return (await this.continueCustomerInteractionWithTan([HKWPD.Id], tanReference, tan));
181
174
  }
182
175
  /**
183
176
  * Checks if the bank supports fetching credit card statements in general or for the given account number
@@ -198,8 +191,7 @@ export class FinTSClient {
198
191
  * @returns an account statements response containing an array of statements
199
192
  */
200
193
  async getCreditCardStatements(accountNumber, from) {
201
- const responses = await this.startCustomerOrderInteraction(new CreditCardStatementInteraction(accountNumber, from));
202
- return responses.get(DKKKU.Id);
194
+ return (await this.startCustomerOrderInteraction(new CreditCardStatementInteraction(accountNumber, from)));
203
195
  }
204
196
  /**
205
197
  * Continues the credit card statements fetching when a TAN is required
@@ -208,18 +200,31 @@ export class FinTSClient {
208
200
  * @returns a credit card statements response containing an array of statements
209
201
  */
210
202
  async getCreditCardStatementsWithTan(tanReference, tan) {
211
- const responses = await this.continueCustomerInteractionWithTan(tanReference, tan);
212
- return responses.get(DKKKU.Id);
203
+ return (await this.continueCustomerInteractionWithTan([DKKKU.Id], tanReference, tan));
213
204
  }
214
205
  async startCustomerOrderInteraction(interaction) {
215
206
  this.currentDialog = new Dialog(this.config, false);
216
207
  this.currentDialog.addCustomerInteraction(interaction);
217
- return await this.currentDialog.start();
208
+ const responses = await this.currentDialog.start();
209
+ const response = responses.get(interaction.segId);
210
+ if (response) {
211
+ return response;
212
+ }
213
+ else {
214
+ return [...responses.values()].at(-1);
215
+ }
218
216
  }
219
- async continueCustomerInteractionWithTan(tanReference, tan) {
217
+ async continueCustomerInteractionWithTan(segIds, tanReference, tan) {
220
218
  if (!this.currentDialog) {
221
219
  throw new Error('no customer dialog was started which can continue');
222
220
  }
223
- return await this.currentDialog.continue(tanReference, tan);
221
+ const responses = await this.currentDialog.continue(tanReference, tan);
222
+ for (const segId of segIds) {
223
+ const response = responses.get(segId);
224
+ if (response) {
225
+ return response;
226
+ }
227
+ }
228
+ return [...responses.values()].at(-1);
224
229
  }
225
230
  }
@@ -2,7 +2,100 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
2
  import { FinTSClient } from '../client.js';
3
3
  import { Dialog } from '../dialog.js';
4
4
  import { FinTSConfig } from '../config.js';
5
+ import { Language } from '../codes.js';
6
+ import { AccountType } from '../bankAccount.js';
5
7
  describe('FinTSClient', () => {
8
+ const client = new FinTSClient(FinTSConfig.fromBankingInformation('product', '1.0', {
9
+ systemId: 'SYSTEM01',
10
+ bpd: {
11
+ version: 1,
12
+ url: 'https://bank.example.com/fints',
13
+ countryCode: 280,
14
+ bankId: '10020030',
15
+ bankName: 'Example Bank',
16
+ allowedTransactions: [
17
+ { transId: 'HKSAL', versions: [6, 7], tanRequired: false },
18
+ { transId: 'HKCAZ', versions: [1], tanRequired: false },
19
+ { transId: 'HKKAZ', versions: [6], tanRequired: true },
20
+ { transId: 'HKWPD', versions: [7], tanRequired: true },
21
+ { transId: 'DKKKU', versions: [2], tanRequired: false },
22
+ ],
23
+ maxTransactionsPerMessage: 1,
24
+ supportedLanguages: [Language.German],
25
+ supportedHbciVersions: [300],
26
+ supportedTanMethods: [
27
+ {
28
+ id: 1,
29
+ name: 'ChipTAN',
30
+ version: 1,
31
+ isDecoupled: false,
32
+ activeTanMediaCount: 1,
33
+ activeTanMedia: ['TAN-Generator 123'],
34
+ tanMediaRequirement: 0,
35
+ },
36
+ {
37
+ id: 2,
38
+ name: 'pushTAN',
39
+ version: 1,
40
+ isDecoupled: true,
41
+ activeTanMediaCount: 1,
42
+ activeTanMedia: ['Mobile App'],
43
+ tanMediaRequirement: 1,
44
+ },
45
+ ],
46
+ availableTanMethodIds: [1, 2],
47
+ },
48
+ upd: {
49
+ version: 1,
50
+ usage: 0,
51
+ bankAccounts: [
52
+ {
53
+ accountNumber: '1234567890',
54
+ bank: {
55
+ bankId: '10020030',
56
+ country: 280,
57
+ },
58
+ iban: 'DE89370400440532013000',
59
+ customerId: 'customer1',
60
+ accountType: AccountType.CheckingAccount,
61
+ currency: 'EUR',
62
+ holder1: 'Test User',
63
+ allowedTransactions: [
64
+ { transId: 'HKSAL', numSignatures: 1 },
65
+ { transId: 'HKCAZ', numSignatures: 1 },
66
+ { transId: 'HKKAZ', numSignatures: 1 },
67
+ ],
68
+ },
69
+ {
70
+ accountNumber: '9876543210',
71
+ bank: {
72
+ bankId: '10020030',
73
+ country: 280,
74
+ },
75
+ iban: 'DE89370400440532013001',
76
+ customerId: 'customer1',
77
+ accountType: AccountType.SecuritiesAccount,
78
+ currency: 'EUR',
79
+ holder1: 'Test User',
80
+ allowedTransactions: [{ transId: 'HKWPD', numSignatures: 1 }],
81
+ },
82
+ {
83
+ accountNumber: '1111222233',
84
+ bank: {
85
+ bankId: '10020030',
86
+ country: 280,
87
+ },
88
+ iban: 'DE89370400440532013002',
89
+ customerId: 'customer1',
90
+ accountType: AccountType.CreditCardAccount,
91
+ currency: 'EUR',
92
+ holder1: 'Test User',
93
+ allowedTransactions: [{ transId: 'DKKKU', numSignatures: 1 }],
94
+ },
95
+ ],
96
+ },
97
+ bankMessages: [],
98
+ }));
6
99
  let dialogStartMock;
7
100
  let dialogContinueMock;
8
101
  beforeEach(() => {
@@ -13,26 +106,547 @@ describe('FinTSClient', () => {
13
106
  dialogStartMock.mockRestore();
14
107
  dialogContinueMock.mockRestore();
15
108
  });
16
- it('sends a message', async () => {
17
- const client = new FinTSClient(FinTSConfig.forFirstTimeUse('product', '1.0', 'http://localhost', '12030000', 'user'));
18
- dialogStartMock.mockResolvedValueOnce(new Map([
19
- [
20
- 'HKIDN',
21
- {
22
- dialogId: 'DIALOG1',
23
- success: true,
24
- requiresTan: false,
25
- bankingInformationUpdated: true,
26
- bankingInformation: { systemId: 'SYSTEM01', bankMessages: [] },
27
- bankAnswers: [{ code: 20, text: 'Auftrag ausgeführt' }],
28
- },
29
- ],
30
- ]));
31
- const response = await client.synchronize();
32
- expect(response.success).toBe(true);
33
- expect(response.requiresTan).toBe(false);
34
- expect(response.bankingInformation).toBeDefined();
35
- expect(dialogStartMock).toHaveBeenCalledOnce();
36
- expect(dialogContinueMock).toHaveBeenCalledTimes(0);
109
+ describe('getAccountBalance', () => {
110
+ it('returns balance response when no TAN is required', async () => {
111
+ dialogStartMock.mockResolvedValueOnce(new Map([
112
+ [
113
+ 'HKIDN',
114
+ {
115
+ dialogId: 'DIALOG1',
116
+ success: true,
117
+ requiresTan: false,
118
+ bankingInformationUpdated: false,
119
+ bankAnswers: [{ code: 20, text: 'Auftrag ausgeführt' }],
120
+ },
121
+ ],
122
+ [
123
+ 'HKSAL',
124
+ {
125
+ dialogId: 'DIALOG1',
126
+ success: true,
127
+ requiresTan: false,
128
+ bankingInformationUpdated: false,
129
+ bankAnswers: [{ code: 20, text: 'Auftrag ausgeführt' }],
130
+ balance: { date: new Date('2025-12-23'), currency: 'EUR', balance: 1000.0 },
131
+ },
132
+ ],
133
+ [
134
+ 'HKEND',
135
+ {
136
+ dialogId: 'DIALOG1',
137
+ success: true,
138
+ requiresTan: false,
139
+ bankingInformationUpdated: false,
140
+ bankAnswers: [{ code: 100, text: 'Dialog beendet' }],
141
+ },
142
+ ],
143
+ ]));
144
+ const response = (await client.getAccountBalance('1234567890'));
145
+ expect(response.success).toBe(true);
146
+ expect(response.requiresTan).toBe(false);
147
+ expect(response.balance).toBeDefined();
148
+ expect(dialogStartMock).toHaveBeenCalledOnce();
149
+ expect(dialogContinueMock).toHaveBeenCalledTimes(0);
150
+ });
151
+ it('returns init dialog response when TAN is required', async () => {
152
+ dialogStartMock.mockResolvedValueOnce(new Map([
153
+ [
154
+ 'HKIDN',
155
+ {
156
+ dialogId: 'DIALOG1',
157
+ success: true,
158
+ requiresTan: true,
159
+ tanReference: 'TANREF123',
160
+ tanChallenge: 'Bitte geben Sie Ihre TAN ein.',
161
+ bankingInformationUpdated: false,
162
+ bankAnswers: [{ code: 20, text: 'Auftrag ausgeführt' }],
163
+ },
164
+ ],
165
+ ]));
166
+ const response = (await client.getAccountBalance('1234567890'));
167
+ expect(response.success).toBe(true);
168
+ expect(response.requiresTan).toBe(true);
169
+ expect(response.tanReference).toBe('TANREF123');
170
+ expect(dialogStartMock).toHaveBeenCalledOnce();
171
+ expect(dialogContinueMock).toHaveBeenCalledTimes(0);
172
+ });
173
+ });
174
+ describe('getAccountBalanceWithTan', () => {
175
+ it('returns balance response when TAN is provided', async () => {
176
+ dialogContinueMock.mockResolvedValueOnce(new Map([
177
+ [
178
+ 'HKSAL',
179
+ {
180
+ dialogId: 'DIALOG1',
181
+ success: true,
182
+ requiresTan: false,
183
+ bankingInformationUpdated: false,
184
+ bankAnswers: [{ code: 20, text: 'Auftrag ausgeführt' }],
185
+ balance: { date: new Date('2025-12-23'), currency: 'EUR', balance: 2500.0 },
186
+ },
187
+ ],
188
+ [
189
+ 'HKEND',
190
+ {
191
+ dialogId: 'DIALOG1',
192
+ success: true,
193
+ requiresTan: false,
194
+ bankingInformationUpdated: false,
195
+ bankAnswers: [{ code: 100, text: 'Dialog beendet' }],
196
+ },
197
+ ],
198
+ ]));
199
+ const response = (await client.getAccountBalanceWithTan('TANREF123', '123456'));
200
+ expect(response.success).toBe(true);
201
+ expect(response.requiresTan).toBe(false);
202
+ expect(response.balance).toBeDefined();
203
+ expect(response.balance.balance).toBe(2500.0);
204
+ expect(dialogContinueMock).toHaveBeenCalledWith('TANREF123', '123456');
205
+ });
206
+ });
207
+ describe('synchronize', () => {
208
+ it('returns synchronization response when no TAN is required', async () => {
209
+ dialogStartMock.mockResolvedValueOnce(new Map([
210
+ [
211
+ 'HKIDN',
212
+ {
213
+ dialogId: 'DIALOG1',
214
+ success: true,
215
+ requiresTan: false,
216
+ bankingInformationUpdated: true,
217
+ bankAnswers: [{ code: 20, text: 'Synchronisierung erfolgreich' }],
218
+ },
219
+ ],
220
+ [
221
+ 'HKEND',
222
+ {
223
+ dialogId: 'DIALOG1',
224
+ success: true,
225
+ requiresTan: false,
226
+ bankingInformationUpdated: false,
227
+ bankAnswers: [{ code: 100, text: 'Dialog beendet' }],
228
+ },
229
+ ],
230
+ ]));
231
+ const response = await client.synchronize();
232
+ expect(response.success).toBe(true);
233
+ expect(response.requiresTan).toBe(false);
234
+ expect(response.bankingInformationUpdated).toBe(true);
235
+ expect(dialogStartMock).toHaveBeenCalledOnce();
236
+ });
237
+ it('returns sync response requiring TAN', async () => {
238
+ dialogStartMock.mockResolvedValueOnce(new Map([
239
+ [
240
+ 'HKIDN',
241
+ {
242
+ dialogId: 'DIALOG1',
243
+ success: true,
244
+ requiresTan: true,
245
+ tanReference: 'SYNCTAN123',
246
+ tanChallenge: 'TAN für Synchronisation eingeben.',
247
+ bankingInformationUpdated: false,
248
+ bankAnswers: [{ code: 3955, text: 'TAN erforderlich' }],
249
+ },
250
+ ],
251
+ ]));
252
+ const response = await client.synchronize();
253
+ expect(response.success).toBe(true);
254
+ expect(response.requiresTan).toBe(true);
255
+ expect(response.tanReference).toBe('SYNCTAN123');
256
+ expect(response.tanChallenge).toBe('TAN für Synchronisation eingeben.');
257
+ });
258
+ });
259
+ describe('synchronizeWithTan', () => {
260
+ it('completes synchronization with TAN', async () => {
261
+ dialogContinueMock.mockResolvedValueOnce(new Map([
262
+ [
263
+ 'HKIDN',
264
+ {
265
+ dialogId: 'DIALOG1',
266
+ success: true,
267
+ requiresTan: false,
268
+ bankingInformationUpdated: true,
269
+ bankAnswers: [{ code: 20, text: 'Synchronisierung erfolgreich' }],
270
+ },
271
+ ],
272
+ [
273
+ 'HKEND',
274
+ {
275
+ dialogId: 'DIALOG1',
276
+ success: true,
277
+ requiresTan: false,
278
+ bankingInformationUpdated: false,
279
+ bankAnswers: [{ code: 100, text: 'Dialog beendet' }],
280
+ },
281
+ ],
282
+ ]));
283
+ const response = await client.synchronizeWithTan('SYNCTAN123', '789012');
284
+ expect(response.success).toBe(true);
285
+ expect(response.requiresTan).toBe(false);
286
+ expect(response.bankingInformationUpdated).toBe(true);
287
+ expect(dialogContinueMock).toHaveBeenCalledWith('SYNCTAN123', '789012');
288
+ });
289
+ });
290
+ describe('getAccountStatements', () => {
291
+ it('fetches statements using CAMT format when available and preferred', async () => {
292
+ dialogStartMock.mockResolvedValueOnce(new Map([
293
+ [
294
+ 'HKIDN',
295
+ {
296
+ dialogId: 'DIALOG1',
297
+ success: true,
298
+ requiresTan: false,
299
+ bankingInformationUpdated: false,
300
+ bankAnswers: [{ code: 20, text: 'Auftrag ausgeführt' }],
301
+ },
302
+ ],
303
+ [
304
+ 'HKCAZ',
305
+ {
306
+ dialogId: 'DIALOG1',
307
+ success: true,
308
+ requiresTan: false,
309
+ bankingInformationUpdated: false,
310
+ bankAnswers: [{ code: 20, text: 'Auftrag ausgeführt' }],
311
+ statements: [
312
+ {
313
+ account: '1234567890',
314
+ transactions: [],
315
+ openingBalance: { date: new Date('2025-12-01'), currency: 'EUR', value: 500.0 },
316
+ closingBalance: { date: new Date('2025-12-23'), currency: 'EUR', value: 1500.0 },
317
+ },
318
+ ],
319
+ },
320
+ ],
321
+ [
322
+ 'HKEND',
323
+ {
324
+ dialogId: 'DIALOG1',
325
+ success: true,
326
+ requiresTan: false,
327
+ bankingInformationUpdated: false,
328
+ bankAnswers: [{ code: 100, text: 'Dialog beendet' }],
329
+ },
330
+ ],
331
+ ]));
332
+ const response = (await client.getAccountStatements('1234567890'));
333
+ expect(response.success).toBe(true);
334
+ expect(response.statements).toBeDefined();
335
+ expect(response.statements.length).toBe(1);
336
+ expect(response.statements[0].account).toBe('1234567890');
337
+ });
338
+ it('throws error when account does not support statements', async () => {
339
+ await expect(client.getAccountStatements('9999999999')).rejects.toThrow('Account 9999999999 not found in UPD');
340
+ });
341
+ it('uses MT940 when preferCamt is false', async () => {
342
+ dialogStartMock.mockResolvedValueOnce(new Map([
343
+ [
344
+ 'HKIDN',
345
+ {
346
+ dialogId: 'DIALOG1',
347
+ success: true,
348
+ requiresTan: false,
349
+ bankingInformationUpdated: false,
350
+ bankAnswers: [{ code: 20, text: 'Auftrag ausgeführt' }],
351
+ },
352
+ ],
353
+ [
354
+ 'HKKAZ',
355
+ {
356
+ dialogId: 'DIALOG1',
357
+ success: true,
358
+ requiresTan: false,
359
+ bankingInformationUpdated: false,
360
+ bankAnswers: [{ code: 20, text: 'Auftrag ausgeführt' }],
361
+ statements: [
362
+ {
363
+ account: '1234567890',
364
+ transactions: [],
365
+ openingBalance: { date: new Date('2025-12-01'), currency: 'EUR', value: 500.0 },
366
+ closingBalance: { date: new Date('2025-12-23'), currency: 'EUR', value: 1500.0 },
367
+ },
368
+ ],
369
+ },
370
+ ],
371
+ [
372
+ 'HKEND',
373
+ {
374
+ dialogId: 'DIALOG1',
375
+ success: true,
376
+ requiresTan: false,
377
+ bankingInformationUpdated: false,
378
+ bankAnswers: [{ code: 100, text: 'Dialog beendet' }],
379
+ },
380
+ ],
381
+ ]));
382
+ const response = (await client.getAccountStatements('1234567890', undefined, undefined, false));
383
+ expect(response.success).toBe(true);
384
+ expect(response.statements).toBeDefined();
385
+ expect(response.statements.length).toBe(1);
386
+ expect(response.statements[0].account).toBe('1234567890');
387
+ });
388
+ });
389
+ describe('getAccountStatementsWithTan', () => {
390
+ it('continues statement fetching with TAN', async () => {
391
+ dialogContinueMock.mockResolvedValueOnce(new Map([
392
+ [
393
+ 'HKCAZ',
394
+ {
395
+ dialogId: 'DIALOG1',
396
+ success: true,
397
+ requiresTan: false,
398
+ bankingInformationUpdated: false,
399
+ bankAnswers: [{ code: 20, text: 'Auftrag ausgeführt' }],
400
+ statements: [
401
+ {
402
+ account: '1234567890',
403
+ transactions: [],
404
+ openingBalance: { date: new Date('2025-12-01'), currency: 'EUR', value: 500.0 },
405
+ closingBalance: { date: new Date('2025-12-23'), currency: 'EUR', value: 1500.0 },
406
+ },
407
+ ],
408
+ },
409
+ ],
410
+ ]));
411
+ const response = (await client.getAccountStatementsWithTan('STATEMENTTAN123', '456789'));
412
+ expect(response.success).toBe(true);
413
+ expect(response.statements).toBeDefined();
414
+ expect(dialogContinueMock).toHaveBeenCalledWith('STATEMENTTAN123', '456789');
415
+ });
416
+ });
417
+ describe('getPortfolio', () => {
418
+ it('fetches portfolio information', async () => {
419
+ dialogStartMock.mockResolvedValueOnce(new Map([
420
+ [
421
+ 'HKIDN',
422
+ {
423
+ dialogId: 'DIALOG1',
424
+ success: true,
425
+ requiresTan: false,
426
+ bankingInformationUpdated: false,
427
+ bankAnswers: [{ code: 20, text: 'Auftrag ausgeführt' }],
428
+ },
429
+ ],
430
+ [
431
+ 'HKWPD',
432
+ {
433
+ dialogId: 'DIALOG1',
434
+ success: true,
435
+ requiresTan: false,
436
+ bankingInformationUpdated: false,
437
+ bankAnswers: [{ code: 20, text: 'Auftrag ausgeführt' }],
438
+ portfolioStatement: {
439
+ currency: 'EUR',
440
+ holdings: [
441
+ {
442
+ isin: 'DE0001234567',
443
+ price: 25.5,
444
+ currency: 'EUR',
445
+ },
446
+ ],
447
+ totalValue: 2550.0,
448
+ },
449
+ },
450
+ ],
451
+ ]));
452
+ const response = (await client.getPortfolio('9876543210'));
453
+ expect(response.success).toBe(true);
454
+ expect(response.portfolioStatement?.holdings).toBeDefined();
455
+ expect(response.portfolioStatement.holdings.length).toBe(1);
456
+ expect(response.portfolioStatement.totalValue).toBe(2550.0);
457
+ });
458
+ it('fetches portfolio with optional parameters', async () => {
459
+ dialogStartMock.mockResolvedValueOnce(new Map([
460
+ [
461
+ 'HKIDN',
462
+ {
463
+ dialogId: 'DIALOG1',
464
+ success: true,
465
+ requiresTan: false,
466
+ bankingInformationUpdated: false,
467
+ bankAnswers: [{ code: 20, text: 'Auftrag ausgeführt' }],
468
+ },
469
+ ],
470
+ [
471
+ 'HKWPD',
472
+ {
473
+ dialogId: 'DIALOG1',
474
+ success: true,
475
+ requiresTan: false,
476
+ bankingInformationUpdated: false,
477
+ bankAnswers: [{ code: 20, text: 'Auftrag ausgeführt' }],
478
+ portfolioStatement: {
479
+ currency: 'EUR',
480
+ holdings: [
481
+ {
482
+ isin: 'DE0001234567',
483
+ price: 25.5,
484
+ },
485
+ ],
486
+ totalValue: 1000.0,
487
+ },
488
+ },
489
+ ],
490
+ ]));
491
+ const response = (await client.getPortfolio('9876543210', 'EUR', '1', 10));
492
+ expect(response.success).toBe(true);
493
+ expect(response.portfolioStatement.holdings).toBeDefined();
494
+ expect(response.portfolioStatement.holdings.length).toBe(1);
495
+ expect(response.portfolioStatement.holdings[0].isin).toBe('DE0001234567');
496
+ });
497
+ });
498
+ describe('getPortfolioWithTan', () => {
499
+ it('continues portfolio fetching with TAN', async () => {
500
+ dialogContinueMock.mockResolvedValueOnce(new Map([
501
+ [
502
+ 'HKWPD',
503
+ {
504
+ dialogId: 'DIALOG1',
505
+ success: true,
506
+ requiresTan: false,
507
+ bankingInformationUpdated: false,
508
+ bankAnswers: [{ code: 20, text: 'Auftrag ausgeführt' }],
509
+ portfolioStatement: {
510
+ currency: 'EUR',
511
+ holdings: [],
512
+ },
513
+ },
514
+ ],
515
+ ]));
516
+ const response = (await client.getPortfolioWithTan('PORTFOLIOTAN123', '987654'));
517
+ expect(response.success).toBe(true);
518
+ expect(response.portfolioStatement).toBeDefined();
519
+ expect(dialogContinueMock).toHaveBeenCalledWith('PORTFOLIOTAN123', '987654');
520
+ });
521
+ });
522
+ describe('getCreditCardStatements', () => {
523
+ it('fetches credit card statements', async () => {
524
+ dialogStartMock.mockResolvedValueOnce(new Map([
525
+ [
526
+ 'HKIDN',
527
+ {
528
+ dialogId: 'DIALOG1',
529
+ success: true,
530
+ requiresTan: false,
531
+ bankingInformationUpdated: false,
532
+ bankAnswers: [{ code: 20, text: 'Auftrag ausgeführt' }],
533
+ },
534
+ ],
535
+ [
536
+ 'DKKKU',
537
+ {
538
+ dialogId: 'DIALOG1',
539
+ success: true,
540
+ requiresTan: false,
541
+ bankingInformationUpdated: false,
542
+ bankAnswers: [{ code: 20, text: 'Auftrag ausgeführt' }],
543
+ statements: [
544
+ {
545
+ openingBalance: { date: new Date('2025-12-01'), currency: 'EUR', value: 0.0 },
546
+ closingBalance: { date: new Date('2025-12-23'), currency: 'EUR', value: -150.0 },
547
+ transactions: [],
548
+ },
549
+ ],
550
+ },
551
+ ],
552
+ ]));
553
+ const response = (await client.getCreditCardStatements('1111222233'));
554
+ expect(response.success).toBe(true);
555
+ expect(response.statements).toBeDefined();
556
+ expect(response.statements.length).toBe(1);
557
+ expect(response.statements[0].closingBalance?.value).toBe(-150.0);
558
+ });
559
+ });
560
+ describe('getCreditCardStatementsWithTan', () => {
561
+ it('continues credit card statement fetching with TAN', async () => {
562
+ dialogContinueMock.mockResolvedValueOnce(new Map([
563
+ [
564
+ 'DKKKU',
565
+ {
566
+ dialogId: 'DIALOG1',
567
+ success: true,
568
+ requiresTan: false,
569
+ bankingInformationUpdated: false,
570
+ bankAnswers: [{ code: 20, text: 'Auftrag ausgeführt' }],
571
+ statements: [],
572
+ },
573
+ ],
574
+ ]));
575
+ const response = (await client.getCreditCardStatementsWithTan('CCTAN123', '111222'));
576
+ expect(response.success).toBe(true);
577
+ expect(response.statements).toBeDefined();
578
+ expect(dialogContinueMock).toHaveBeenCalledWith('CCTAN123', '111222');
579
+ });
580
+ });
581
+ describe('capability checks', () => {
582
+ describe('canGetAccountBalance', () => {
583
+ it('returns true for general capability when bank supports it', () => {
584
+ expect(client.canGetAccountBalance()).toBe(true);
585
+ });
586
+ it('returns true for specific account that supports balance checking', () => {
587
+ expect(client.canGetAccountBalance('1234567890')).toBe(true);
588
+ });
589
+ it('returns false for account that does not support balance checking', () => {
590
+ expect(client.canGetAccountBalance('9876543210')).toBe(false);
591
+ });
592
+ });
593
+ describe('canGetAccountStatements', () => {
594
+ it('returns true for general capability when bank supports it', () => {
595
+ expect(client.canGetAccountStatements()).toBe(true);
596
+ });
597
+ it('returns true for account that supports statements', () => {
598
+ expect(client.canGetAccountStatements('1234567890')).toBe(true);
599
+ });
600
+ it('returns false for account that does not support statements', () => {
601
+ expect(client.canGetAccountStatements('1111222233')).toBe(false);
602
+ });
603
+ });
604
+ describe('canGetPortfolio', () => {
605
+ it('returns true for general capability when bank supports it', () => {
606
+ expect(client.canGetPortfolio()).toBe(true);
607
+ });
608
+ it('returns true for depot account', () => {
609
+ expect(client.canGetPortfolio('9876543210')).toBe(true);
610
+ });
611
+ it('returns false for non-depot account', () => {
612
+ expect(client.canGetPortfolio('1234567890')).toBe(false);
613
+ });
614
+ });
615
+ describe('canGetCreditCardStatements', () => {
616
+ it('returns true for general capability when bank supports it', () => {
617
+ expect(client.canGetCreditCardStatements()).toBe(true);
618
+ });
619
+ it('returns true for credit card account', () => {
620
+ expect(client.canGetCreditCardStatements('1111222233')).toBe(true);
621
+ });
622
+ it('returns false for non-credit card account', () => {
623
+ expect(client.canGetCreditCardStatements('1234567890')).toBe(false);
624
+ });
625
+ });
626
+ });
627
+ describe('TAN method and media selection', () => {
628
+ describe('selectTanMethod', () => {
629
+ it('selects TAN method by ID and returns the method', () => {
630
+ const tanMethod = client.selectTanMethod(2);
631
+ expect(tanMethod).toBeDefined();
632
+ expect(tanMethod.id).toBe(2);
633
+ expect(tanMethod.name).toBe('pushTAN');
634
+ expect(client.config.selectedTanMethod).toBe(tanMethod);
635
+ });
636
+ it('throws error for invalid TAN method ID', () => {
637
+ expect(() => client.selectTanMethod(999)).toThrow();
638
+ });
639
+ });
640
+ describe('selectTanMedia', () => {
641
+ it('selects TAN media by name', () => {
642
+ // First select a TAN method that has TAN media
643
+ client.selectTanMethod(1);
644
+ expect(() => client.selectTanMedia('TAN-Generator 123')).not.toThrow();
645
+ });
646
+ it('throws error for invalid TAN media name', () => {
647
+ client.selectTanMethod(1);
648
+ expect(() => client.selectTanMedia('Invalid Media')).toThrow();
649
+ });
650
+ });
37
651
  });
38
652
  });
@@ -97,12 +97,12 @@ export declare class FinTSClient {
97
97
  */
98
98
  getPortfolio(accountNumber: string, currency?: string, priceQuality?: '1' | '2', maxEntries?: number): Promise<PortfolioResponse>;
99
99
  /**
100
- * Continues the portfolio fetching when a TAN is required
101
- * @param tanReference The TAN reference provided in the first call's response
102
-
103
- * @param tan The TAN entered by the user, can be omitted if a decoupled TAN method is used
104
- * @returns a portfolio response containing holdings and total value
105
- */
100
+ * Continues the portfolio fetching when a TAN is required
101
+ * @param tanReference The TAN reference provided in the first call's response
102
+
103
+ * @param tan The TAN entered by the user, can be omitted if a decoupled TAN method is used
104
+ * @returns a portfolio response containing holdings and total value
105
+ */
106
106
  getPortfolioWithTan(tanReference: string, tan?: string): Promise<PortfolioResponse>;
107
107
  /**
108
108
  * Checks if the bank supports fetching credit card statements in general or for the given account number
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAC1E,OAAO,EAAE,sBAAsB,EAAsB,MAAM,sCAAsC,CAAC;AAClG,OAAO,EAAE,iBAAiB,EAAwB,MAAM,wCAAwC,CAAC;AACjG,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAM3C,OAAO,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAC;AAIvE,MAAM,WAAW,mBAAoB,SAAQ,YAAY;CAAG;AAE5D;;GAEG;AACH,qBAAa,WAAW;IAOJ,MAAM,EAAE,WAAW;IANtC,OAAO,CAAC,aAAa,CAAqB;IAE1C;;;OAGG;gBACgB,MAAM,EAAE,WAAW;IAUtC;;;;OAIG;IACH,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS;IAK/C;;;OAGG;IACH,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAI1C;;;OAGG;IACG,WAAW,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAMjD;;;;;OAKG;IACG,kBAAkB,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAK1F;;;;OAIG;IACH,oBAAoB,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO;IAMrD;;;;OAIG;IACG,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAK/E;;;;;OAKG;IACG,wBAAwB,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAKnG;;;;OAIG;IACH,uBAAuB,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO;IAaxD;;;;;;;OAOG;IACG,oBAAoB,CACzB,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE,IAAI,EACX,EAAE,CAAC,EAAE,IAAI,EACT,UAAU,GAAE,OAAc,GACxB,OAAO,CAAC,iBAAiB,CAAC;IAuB7B;;;;;OAKG;IACG,2BAA2B,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAKjG;;;;OAIG;IACH,eAAe,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO;IAMhD;;;;;;;OAOG;IACG,YAAY,CACjB,aAAa,EAAE,MAAM,EACrB,QAAQ,CAAC,EAAE,MAAM,EACjB,YAAY,CAAC,EAAE,GAAG,GAAG,GAAG,EACxB,UAAU,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,iBAAiB,CAAC;IAO7B;;;;;;OAMG;IACG,mBAAmB,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAKzF;;;;OAIG;IACH,0BAA0B,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO;IAM3D;;;;;;;OAOG;IACG,uBAAuB,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAK7F;;;;;OAKG;IACG,8BAA8B,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;YAKtF,6BAA6B;YAQ7B,kCAAkC;CAUhD"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAC1E,OAAO,EAAE,sBAAsB,EAAsB,MAAM,sCAAsC,CAAC;AAClG,OAAO,EAAE,iBAAiB,EAAwB,MAAM,wCAAwC,CAAC;AACjG,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAM3C,OAAO,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAC;AAIvE,MAAM,WAAW,mBAAoB,SAAQ,YAAY;CAAG;AAE5D;;GAEG;AACH,qBAAa,WAAW;IAOH,MAAM,EAAE,WAAW;IANtC,OAAO,CAAC,aAAa,CAAqB;IAE1C;;;OAGG;gBACgB,MAAM,EAAE,WAAW;IAUtC;;;;OAIG;IACH,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS;IAK/C;;;OAGG;IACH,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAI1C;;;OAGG;IACG,WAAW,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAMjD;;;;;OAKG;IACG,kBAAkB,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAI1F;;;;OAIG;IACH,oBAAoB,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO;IAMrD;;;;OAIG;IACG,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAK/E;;;;;OAKG;IACG,wBAAwB,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAInG;;;;OAIG;IACH,uBAAuB,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO;IAaxD;;;;;;;OAOG;IACG,oBAAoB,CACxB,aAAa,EAAE,MAAM,EACrB,IAAI,CAAC,EAAE,IAAI,EACX,EAAE,CAAC,EAAE,IAAI,EACT,UAAU,GAAE,OAAc,GACzB,OAAO,CAAC,iBAAiB,CAAC;IAuB7B;;;;;OAKG;IACG,2BAA2B,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAQjG;;;;OAIG;IACH,eAAe,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO;IAMhD;;;;;;;OAOG;IACG,YAAY,CAChB,aAAa,EAAE,MAAM,EACrB,QAAQ,CAAC,EAAE,MAAM,EACjB,YAAY,CAAC,EAAE,GAAG,GAAG,GAAG,EACxB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,iBAAiB,CAAC;IAM7B;;;;;;SAME;IACI,mBAAmB,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAIzF;;;;OAIG;IACH,0BAA0B,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO;IAM3D;;;;;;;OAOG;IACG,uBAAuB,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAM7F;;;;;OAKG;IACG,8BAA8B,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;YAItF,6BAA6B;YAa7B,kCAAkC;CAoBjD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lib-fints",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "Typescript/Javascript client library for Online-Banking via the FinTS 3.0 protocol with PIN/TAN",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"