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 +34 -29
- package/dist/tests/client.test.js +635 -21
- package/dist/types/client.d.ts +6 -6
- package/dist/types/client.d.ts.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
|
85
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
132
|
-
return responses.get(HKCAZ.Id);
|
|
129
|
+
return (await this.startCustomerOrderInteraction(new StatementInteractionCAMT(accountNumber, from, to)));
|
|
133
130
|
}
|
|
134
131
|
else {
|
|
135
|
-
|
|
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
|
-
|
|
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
|
-
|
|
169
|
-
return responses.get(HKWPD.Id);
|
|
163
|
+
return (await this.startCustomerOrderInteraction(new PortfolioInteraction(accountNumber, currency, priceQuality, maxEntries)));
|
|
170
164
|
}
|
|
171
165
|
/**
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
});
|
package/dist/types/client.d.ts
CHANGED
|
@@ -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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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;
|
|
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"}
|