lib-fints 1.3.1 → 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/README.md +43 -40
- package/dist/camtParser.js +412 -0
- package/dist/client.js +65 -82
- package/dist/dataGroups/SepaAccount.js +17 -0
- package/dist/dataGroups/SepaAccountParameters.js +16 -0
- package/dist/dialog.js +126 -108
- package/dist/httpClient.js +17 -3
- package/dist/index.js +1 -0
- package/dist/interactions/balanceInteraction.js +1 -1
- package/dist/interactions/customerInteraction.js +6 -3
- package/dist/interactions/endDialogInteraction.js +17 -0
- package/dist/interactions/initDialogInteraction.js +18 -8
- package/dist/interactions/sepaAccountInteraction.js +48 -0
- package/dist/interactions/statementInteractionCAMT.js +54 -0
- package/dist/interactions/{statementInteraction.js → statementInteractionMT940.js} +11 -8
- package/dist/interactions/tanMediaInteraction.js +4 -0
- package/dist/segments/HICAZ.js +22 -0
- package/dist/segments/HICAZS.js +19 -0
- package/dist/segments/HISPA.js +14 -0
- package/dist/segments/HISPAS.js +14 -0
- package/dist/segments/HKCAZ.js +28 -0
- package/dist/segments/HKSPA.js +21 -0
- package/dist/segments/registry.js +16 -2
- package/dist/statement.js +1 -0
- package/dist/tests/HICAZS.test.js +18 -0
- package/dist/tests/HKCAZ.test.js +47 -0
- package/dist/tests/camtParser.test.js +673 -0
- package/dist/tests/client.test.js +642 -23
- package/dist/tests/dialog.test.js +234 -0
- package/dist/types/bankAccount.d.ts +2 -3
- package/dist/types/bankAccount.d.ts.map +1 -1
- package/dist/types/camtParser.d.ts +31 -0
- package/dist/types/camtParser.d.ts.map +1 -0
- package/dist/types/client.d.ts +10 -10
- package/dist/types/client.d.ts.map +1 -1
- package/dist/types/config.d.ts +2 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/dataGroups/SepaAccount.d.ts +11 -0
- package/dist/types/dataGroups/SepaAccount.d.ts.map +1 -0
- package/dist/types/dataGroups/SepaAccountParameters.d.ts +13 -0
- package/dist/types/dataGroups/SepaAccountParameters.d.ts.map +1 -0
- package/dist/types/dialog.d.ts +12 -8
- package/dist/types/dialog.d.ts.map +1 -1
- package/dist/types/httpClient.d.ts +2 -1
- package/dist/types/httpClient.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/interactions/customerInteraction.d.ts +7 -3
- package/dist/types/interactions/customerInteraction.d.ts.map +1 -1
- package/dist/types/interactions/endDialogInteraction.d.ts +13 -0
- package/dist/types/interactions/endDialogInteraction.d.ts.map +1 -0
- package/dist/types/interactions/initDialogInteraction.d.ts +2 -3
- package/dist/types/interactions/initDialogInteraction.d.ts.map +1 -1
- package/dist/types/interactions/sepaAccountInteraction.d.ts +17 -0
- package/dist/types/interactions/sepaAccountInteraction.d.ts.map +1 -0
- package/dist/types/interactions/{statementInteraction.d.ts → statementInteractionCAMT.d.ts} +4 -7
- package/dist/types/interactions/statementInteractionCAMT.d.ts.map +1 -0
- package/dist/types/interactions/statementInteractionMT940.d.ts +13 -0
- package/dist/types/interactions/statementInteractionMT940.d.ts.map +1 -0
- package/dist/types/interactions/tanMediaInteraction.d.ts.map +1 -1
- package/dist/types/mt940parser.d.ts +2 -38
- package/dist/types/mt940parser.d.ts.map +1 -1
- package/dist/types/segments/HICAZ.d.ts +22 -0
- package/dist/types/segments/HICAZ.d.ts.map +1 -0
- package/dist/types/segments/HICAZS.d.ts +17 -0
- package/dist/types/segments/HICAZS.d.ts.map +1 -0
- package/dist/types/segments/HISPA.d.ts +17 -0
- package/dist/types/segments/HISPA.d.ts.map +1 -0
- package/dist/types/segments/HISPAS.d.ts +14 -0
- package/dist/types/segments/HISPAS.d.ts.map +1 -0
- package/dist/types/segments/HKCAZ.d.ts +27 -0
- package/dist/types/segments/HKCAZ.d.ts.map +1 -0
- package/dist/types/segments/HKKAZ.d.ts +1 -1
- package/dist/types/segments/HKSAL.d.ts +1 -1
- package/dist/types/segments/HKSPA.d.ts +21 -0
- package/dist/types/segments/HKSPA.d.ts.map +1 -0
- package/dist/types/segments/HKTAN.d.ts +1 -1
- package/dist/types/segments/registry.d.ts.map +1 -1
- package/dist/types/statement.d.ts +39 -0
- package/dist/types/statement.d.ts.map +1 -0
- package/dist/types/tests/HICAZS.test.d.ts +2 -0
- package/dist/types/tests/HICAZS.test.d.ts.map +1 -0
- package/dist/types/tests/HKCAZ.test.d.ts +2 -0
- package/dist/types/tests/HKCAZ.test.d.ts.map +1 -0
- package/dist/types/tests/camtParser.test.d.ts +2 -0
- package/dist/types/tests/camtParser.test.d.ts.map +1 -0
- package/dist/types/tests/dialog.test.d.ts +2 -0
- package/dist/types/tests/dialog.test.d.ts.map +1 -0
- package/package.json +4 -1
- package/dist/types/interactions/statementInteraction.d.ts.map +0 -1
package/dist/client.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import { Dialog } from './dialog.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { StatementInteractionMT940 } from './interactions/statementInteractionMT940.js';
|
|
3
|
+
import { StatementInteractionCAMT } from './interactions/statementInteractionCAMT.js';
|
|
4
4
|
import { BalanceInteraction } from './interactions/balanceInteraction.js';
|
|
5
5
|
import { PortfolioInteraction } from './interactions/portfolioInteraction.js';
|
|
6
6
|
import { FinTSConfig } from './config.js';
|
|
7
|
-
import { TanMediaInteraction } from './interactions/tanMediaInteraction.js';
|
|
8
7
|
import { HKSAL } from './segments/HKSAL.js';
|
|
9
8
|
import { HKKAZ } from './segments/HKKAZ.js';
|
|
9
|
+
import { HKCAZ } from './segments/HKCAZ.js';
|
|
10
10
|
import { HKWPD } from './segments/HKWPD.js';
|
|
11
11
|
import { DKKKU } from './segments/DKKKU.js';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
12
|
+
import { CreditCardStatementInteraction } from './interactions/creditcardStatementInteraction.js';
|
|
13
|
+
import { HKIDN } from './segments/HKIDN.js';
|
|
14
14
|
/**
|
|
15
15
|
* A client to communicate with a bank over the FinTS protocol
|
|
16
16
|
*/
|
|
17
17
|
export class FinTSClient {
|
|
18
18
|
config;
|
|
19
|
-
|
|
19
|
+
currentDialog;
|
|
20
20
|
/**
|
|
21
21
|
* Creates a new FinTS client
|
|
22
22
|
* @param config - the configuration for the client, use the static factory methods FinTSConfig.forFirstTimeUse or FinTSConfig.fromBankingInformation to create a configuration
|
|
@@ -51,21 +51,9 @@ export class FinTSClient {
|
|
|
51
51
|
* @returns the synchronization response
|
|
52
52
|
*/
|
|
53
53
|
async synchronize() {
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
return syncResponse;
|
|
58
|
-
}
|
|
59
|
-
if (this.config.selectedTanMethod && this.config.isTransactionSupported(HKTAB.Id)) {
|
|
60
|
-
const tanMediaResponse = await dialog.startCustomerOrderInteraction(new TanMediaInteraction());
|
|
61
|
-
let tanMethod = this.config.selectedTanMethod;
|
|
62
|
-
if (tanMethod) {
|
|
63
|
-
tanMethod.activeTanMedia = tanMediaResponse.tanMediaList;
|
|
64
|
-
}
|
|
65
|
-
syncResponse.bankAnswers.push(...tanMediaResponse.bankAnswers);
|
|
66
|
-
}
|
|
67
|
-
await dialog.end();
|
|
68
|
-
return syncResponse;
|
|
54
|
+
this.currentDialog = new Dialog(this.config, true);
|
|
55
|
+
const responses = await this.currentDialog.start();
|
|
56
|
+
return responses.get(HKIDN.Id);
|
|
69
57
|
}
|
|
70
58
|
/**
|
|
71
59
|
* Continues the synchronization transaction when a TAN is required
|
|
@@ -74,7 +62,7 @@ export class FinTSClient {
|
|
|
74
62
|
* @returns the synchronization response
|
|
75
63
|
*/
|
|
76
64
|
async synchronizeWithTan(tanReference, tan) {
|
|
77
|
-
return this.continueCustomerInteractionWithTan(tanReference, tan);
|
|
65
|
+
return await this.continueCustomerInteractionWithTan([HKIDN.Id], tanReference, tan);
|
|
78
66
|
}
|
|
79
67
|
/**
|
|
80
68
|
* Checks if the bank supports fetching an account balance in general or for the given account number when provided
|
|
@@ -92,7 +80,8 @@ export class FinTSClient {
|
|
|
92
80
|
* @returns the account balance response
|
|
93
81
|
*/
|
|
94
82
|
async getAccountBalance(accountNumber) {
|
|
95
|
-
|
|
83
|
+
const response = await this.startCustomerOrderInteraction(new BalanceInteraction(accountNumber));
|
|
84
|
+
return response;
|
|
96
85
|
}
|
|
97
86
|
/**
|
|
98
87
|
* Continues the account balance fetching when a TAN is required
|
|
@@ -101,7 +90,7 @@ export class FinTSClient {
|
|
|
101
90
|
* @returns the account balance response
|
|
102
91
|
*/
|
|
103
92
|
async getAccountBalanceWithTan(tanReference, tan) {
|
|
104
|
-
return this.continueCustomerInteractionWithTan(tanReference, tan);
|
|
93
|
+
return (await this.continueCustomerInteractionWithTan([HKSAL.Id], tanReference, tan));
|
|
105
94
|
}
|
|
106
95
|
/**
|
|
107
96
|
* Checks if the bank supports fetching account statements in general or for the given account number when provided
|
|
@@ -109,19 +98,39 @@ export class FinTSClient {
|
|
|
109
98
|
* @returns true if the bank (and account) supports fetching account statements
|
|
110
99
|
*/
|
|
111
100
|
canGetAccountStatements(accountNumber) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
101
|
+
if (accountNumber) {
|
|
102
|
+
// Check if either CAMT or MT940 is supported for this account
|
|
103
|
+
return (this.config.isAccountTransactionSupported(accountNumber, HKCAZ.Id) ||
|
|
104
|
+
this.config.isAccountTransactionSupported(accountNumber, HKKAZ.Id));
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
// Check if either CAMT or MT940 is supported by the bank
|
|
108
|
+
return this.config.isTransactionSupported(HKCAZ.Id) || this.config.isTransactionSupported(HKKAZ.Id);
|
|
109
|
+
}
|
|
115
110
|
}
|
|
116
111
|
/**
|
|
117
112
|
* Fetches the account statements for the given account number
|
|
118
113
|
* @param accountNumber - the account number to fetch the statements for, must be an account available in the config.baningInformation.UPD.accounts
|
|
119
114
|
* @param from - an optional start date of the period to fetch the statements for
|
|
120
115
|
* @param to - an optional end date of the period to fetch the statements for
|
|
116
|
+
* @param preferCamt - whether to prefer CAMT format over MT940 when both are supported (default: true)
|
|
121
117
|
* @returns an account statements response containing an array of statements
|
|
122
118
|
*/
|
|
123
|
-
async getAccountStatements(accountNumber, from, to) {
|
|
124
|
-
|
|
119
|
+
async getAccountStatements(accountNumber, from, to, preferCamt = true) {
|
|
120
|
+
// Check what formats the bank supports
|
|
121
|
+
const camtSupported = this.config.isAccountTransactionSupported(accountNumber, 'HKCAZ');
|
|
122
|
+
const mt940Supported = this.config.isAccountTransactionSupported(accountNumber, 'HKKAZ');
|
|
123
|
+
if (!camtSupported && !mt940Supported) {
|
|
124
|
+
throw Error(`Account ${accountNumber} does not support account statements`);
|
|
125
|
+
}
|
|
126
|
+
// Choose format based on support and preference
|
|
127
|
+
const useCAMT = (preferCamt && camtSupported) || (!mt940Supported && camtSupported);
|
|
128
|
+
if (useCAMT) {
|
|
129
|
+
return (await this.startCustomerOrderInteraction(new StatementInteractionCAMT(accountNumber, from, to)));
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
return (await this.startCustomerOrderInteraction(new StatementInteractionMT940(accountNumber, from, to)));
|
|
133
|
+
}
|
|
125
134
|
}
|
|
126
135
|
/**
|
|
127
136
|
* Continues the account statements fetching when a TAN is required
|
|
@@ -130,7 +139,7 @@ export class FinTSClient {
|
|
|
130
139
|
* @returns an account statements response containing an array of statements
|
|
131
140
|
*/
|
|
132
141
|
async getAccountStatementsWithTan(tanReference, tan) {
|
|
133
|
-
return this.continueCustomerInteractionWithTan(tanReference, tan);
|
|
142
|
+
return (await this.continueCustomerInteractionWithTan([HKCAZ.Id, HKKAZ.Id], tanReference, tan));
|
|
134
143
|
}
|
|
135
144
|
/**
|
|
136
145
|
* Checks if the bank supports fetching portfolio information in general or for the given account number when provided
|
|
@@ -151,17 +160,17 @@ export class FinTSClient {
|
|
|
151
160
|
* @returns a portfolio response containing holdings and total value
|
|
152
161
|
*/
|
|
153
162
|
async getPortfolio(accountNumber, currency, priceQuality, maxEntries) {
|
|
154
|
-
return this.startCustomerOrderInteraction(new PortfolioInteraction(accountNumber, currency, priceQuality, maxEntries));
|
|
163
|
+
return (await this.startCustomerOrderInteraction(new PortfolioInteraction(accountNumber, currency, priceQuality, maxEntries)));
|
|
155
164
|
}
|
|
156
165
|
/**
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
+
*/
|
|
163
172
|
async getPortfolioWithTan(tanReference, tan) {
|
|
164
|
-
return this.continueCustomerInteractionWithTan(tanReference, tan);
|
|
173
|
+
return (await this.continueCustomerInteractionWithTan([HKWPD.Id], tanReference, tan));
|
|
165
174
|
}
|
|
166
175
|
/**
|
|
167
176
|
* Checks if the bank supports fetching credit card statements in general or for the given account number
|
|
@@ -182,7 +191,7 @@ export class FinTSClient {
|
|
|
182
191
|
* @returns an account statements response containing an array of statements
|
|
183
192
|
*/
|
|
184
193
|
async getCreditCardStatements(accountNumber, from) {
|
|
185
|
-
return this.startCustomerOrderInteraction(new CreditCardStatementInteraction(accountNumber, from));
|
|
194
|
+
return (await this.startCustomerOrderInteraction(new CreditCardStatementInteraction(accountNumber, from)));
|
|
186
195
|
}
|
|
187
196
|
/**
|
|
188
197
|
* Continues the credit card statements fetching when a TAN is required
|
|
@@ -191,57 +200,31 @@ export class FinTSClient {
|
|
|
191
200
|
* @returns a credit card statements response containing an array of statements
|
|
192
201
|
*/
|
|
193
202
|
async getCreditCardStatementsWithTan(tanReference, tan) {
|
|
194
|
-
return this.continueCustomerInteractionWithTan(tanReference, tan);
|
|
203
|
+
return (await this.continueCustomerInteractionWithTan([DKKKU.Id], tanReference, tan));
|
|
195
204
|
}
|
|
196
205
|
async startCustomerOrderInteraction(interaction) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (clientResponse.requiresTan) {
|
|
204
|
-
this.openCustomerInteractions.set(clientResponse.tanReference, interaction);
|
|
206
|
+
this.currentDialog = new Dialog(this.config, false);
|
|
207
|
+
this.currentDialog.addCustomerInteraction(interaction);
|
|
208
|
+
const responses = await this.currentDialog.start();
|
|
209
|
+
const response = responses.get(interaction.segId);
|
|
210
|
+
if (response) {
|
|
211
|
+
return response;
|
|
205
212
|
}
|
|
206
213
|
else {
|
|
207
|
-
|
|
214
|
+
return [...responses.values()].at(-1);
|
|
208
215
|
}
|
|
209
|
-
return clientResponse;
|
|
210
216
|
}
|
|
211
|
-
async continueCustomerInteractionWithTan(tanReference, tan) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
throw new Error('No open customer interaction found for TAN reference: ' + tanReference);
|
|
215
|
-
}
|
|
216
|
-
const dialog = interaction.dialog;
|
|
217
|
-
let responseMessage = await dialog.sendTanMessage(interaction.segId, tanReference, tan);
|
|
218
|
-
let clientResponse = interaction.getClientResponse(responseMessage);
|
|
219
|
-
this.openCustomerInteractions.delete(tanReference);
|
|
220
|
-
if (!clientResponse.success) {
|
|
221
|
-
await dialog.end();
|
|
222
|
-
return clientResponse;
|
|
217
|
+
async continueCustomerInteractionWithTan(segIds, tanReference, tan) {
|
|
218
|
+
if (!this.currentDialog) {
|
|
219
|
+
throw new Error('no customer dialog was started which can continue');
|
|
223
220
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (initDialogInteraction.followUpInteraction) {
|
|
230
|
-
clientResponse = await dialog.startCustomerOrderInteraction(initDialogInteraction.followUpInteraction);
|
|
231
|
-
if (clientResponse.requiresTan) {
|
|
232
|
-
this.openCustomerInteractions.set(clientResponse.tanReference, initDialogInteraction.followUpInteraction);
|
|
233
|
-
return clientResponse;
|
|
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;
|
|
234
226
|
}
|
|
235
227
|
}
|
|
236
|
-
|
|
237
|
-
return clientResponse;
|
|
238
|
-
}
|
|
239
|
-
async initDialog(dialog, syncSystemId = false, followUpInteraction) {
|
|
240
|
-
const interaction = new InitDialogInteraction(this.config, syncSystemId, followUpInteraction);
|
|
241
|
-
const initResponse = await dialog.initialize(interaction);
|
|
242
|
-
if (initResponse.requiresTan) {
|
|
243
|
-
this.openCustomerInteractions.set(initResponse.tanReference, interaction);
|
|
244
|
-
}
|
|
245
|
-
return initResponse;
|
|
228
|
+
return [...responses.values()].at(-1);
|
|
246
229
|
}
|
|
247
230
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AlphaNumeric } from '../dataElements/AlphaNumeric.js';
|
|
2
|
+
import { DataGroup } from './DataGroup.js';
|
|
3
|
+
import { Identification } from '../dataElements/Identification.js';
|
|
4
|
+
import { BankIdentification } from './BankIdentification.js';
|
|
5
|
+
import { YesNo } from '../dataElements/YesNo.js';
|
|
6
|
+
export class SepaAccountGroup extends DataGroup {
|
|
7
|
+
constructor(name, minCount = 0, maxCount = 1, minVersion, maxVersion) {
|
|
8
|
+
super(name, [
|
|
9
|
+
new YesNo('isSepaAccount', 1, 1),
|
|
10
|
+
new AlphaNumeric('iban', 0, 1, 34),
|
|
11
|
+
new AlphaNumeric('bic', 0, 1, 11),
|
|
12
|
+
new Identification('accountNumber', 1, 1),
|
|
13
|
+
new Identification('subAccountId', 0, 1),
|
|
14
|
+
new BankIdentification('bank', 1, 1),
|
|
15
|
+
], minCount, maxCount, minVersion, maxVersion);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { YesNo } from '../dataElements/YesNo.js';
|
|
2
|
+
import { Numeric } from '../dataElements/Numeric.js';
|
|
3
|
+
import { AlphaNumeric } from '../dataElements/AlphaNumeric.js';
|
|
4
|
+
import { DataGroup } from './DataGroup.js';
|
|
5
|
+
export class SepaAccountParametersGroup extends DataGroup {
|
|
6
|
+
constructor(name, minCount = 1, maxCount = 1, minVersion, maxVersion) {
|
|
7
|
+
super(name, [
|
|
8
|
+
new YesNo('individualAccountRetrievalAllowed', 1, 1),
|
|
9
|
+
new YesNo('nationalAccountAllowed', 1, 1),
|
|
10
|
+
new YesNo('structuredPurposeAllowed', 1, 1),
|
|
11
|
+
new YesNo('maxEntriesAllowed', 1, 1, 2), // version 2+
|
|
12
|
+
new Numeric('reservedPurposePositions', 1, 1, 2, 3), // version 3+
|
|
13
|
+
new AlphaNumeric('supportedSepaFormats', 0, 99, 256), // optional, up to 99 entries
|
|
14
|
+
], minCount, maxCount, minVersion, maxVersion);
|
|
15
|
+
}
|
|
16
|
+
}
|
package/dist/dialog.js
CHANGED
|
@@ -1,47 +1,142 @@
|
|
|
1
1
|
import { TanMediaRequirement, TanProcess } from './codes.js';
|
|
2
2
|
import { HttpClient } from './httpClient.js';
|
|
3
3
|
import { CustomerMessage, CustomerOrderMessage } from './message.js';
|
|
4
|
-
import { HKEND } from './segments/HKEND.js';
|
|
5
4
|
import { HKIDN } from './segments/HKIDN.js';
|
|
6
5
|
import { HKTAN } from './segments/HKTAN.js';
|
|
7
6
|
import { HNHBK } from './segments/HNHBK.js';
|
|
8
7
|
import { decode } from './segment.js';
|
|
9
8
|
import { PARTED } from './partedSegment.js';
|
|
9
|
+
import { CustomerOrderInteraction } from './interactions/customerInteraction.js';
|
|
10
|
+
import { InitDialogInteraction } from './interactions/initDialogInteraction.js';
|
|
11
|
+
import { EndDialogInteraction } from './interactions/endDialogInteraction.js';
|
|
10
12
|
export class Dialog {
|
|
11
13
|
config;
|
|
12
14
|
dialogId = '0';
|
|
13
15
|
lastMessageNumber = 0;
|
|
16
|
+
interactions = [];
|
|
17
|
+
responses = new Map();
|
|
18
|
+
currentInteractionIndex = 0;
|
|
14
19
|
isInitialized = false;
|
|
15
20
|
hasEnded = false;
|
|
16
21
|
httpClient;
|
|
17
|
-
constructor(config) {
|
|
22
|
+
constructor(config, syncSystemId = false) {
|
|
18
23
|
this.config = config;
|
|
19
24
|
if (!this.config) {
|
|
20
25
|
throw new Error('configuration must be provided');
|
|
21
26
|
}
|
|
22
27
|
this.httpClient = this.getHttpClient();
|
|
28
|
+
this.interactions.push(new InitDialogInteraction(this.config, syncSystemId));
|
|
29
|
+
this.interactions.push(new EndDialogInteraction());
|
|
30
|
+
this.interactions.forEach((interaction) => {
|
|
31
|
+
interaction.dialog = this;
|
|
32
|
+
});
|
|
23
33
|
}
|
|
24
|
-
|
|
34
|
+
get currentInteraction() {
|
|
35
|
+
return this.interactions[this.currentInteractionIndex];
|
|
36
|
+
}
|
|
37
|
+
async start() {
|
|
25
38
|
if (this.isInitialized) {
|
|
26
39
|
throw new Error('dialog has already been initialized');
|
|
27
40
|
}
|
|
28
41
|
if (this.hasEnded) {
|
|
29
|
-
throw Error('cannot
|
|
42
|
+
throw Error('cannot start a dialog that has already ended');
|
|
30
43
|
}
|
|
31
44
|
if (this.lastMessageNumber > 0) {
|
|
32
|
-
throw new Error('dialog
|
|
45
|
+
throw new Error('dialog start can only be called on a new dialog');
|
|
46
|
+
}
|
|
47
|
+
let clientResponse;
|
|
48
|
+
do {
|
|
49
|
+
const message = this.createCurrentCustomerMessage();
|
|
50
|
+
const responseMessage = await this.httpClient.sendMessage(message);
|
|
51
|
+
await this.handlePartedMessages(message, responseMessage, this.currentInteraction);
|
|
52
|
+
clientResponse = this.currentInteraction.handleClientResponse(responseMessage);
|
|
53
|
+
this.checkEnded(clientResponse);
|
|
54
|
+
this.dialogId = clientResponse.dialogId;
|
|
55
|
+
this.responses.set(this.currentInteraction.segId, clientResponse);
|
|
56
|
+
if (clientResponse.success && !clientResponse.requiresTan) {
|
|
57
|
+
this.currentInteractionIndex++;
|
|
58
|
+
if (this.currentInteractionIndex > 0) {
|
|
59
|
+
this.isInitialized = true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} while (!this.hasEnded &&
|
|
63
|
+
this.currentInteractionIndex < this.interactions.length &&
|
|
64
|
+
clientResponse.success &&
|
|
65
|
+
!clientResponse.requiresTan);
|
|
66
|
+
return this.responses;
|
|
67
|
+
}
|
|
68
|
+
async continue(tanOrderReference, tan) {
|
|
69
|
+
if (!tanOrderReference) {
|
|
70
|
+
throw Error('tanOrderReference must be provided to continue a customer order with a TAN');
|
|
71
|
+
}
|
|
72
|
+
if (!this.config.selectedTanMethod?.isDecoupled && !tan) {
|
|
73
|
+
throw Error('TAN must be provided for non-decoupled TAN methods');
|
|
74
|
+
}
|
|
75
|
+
if (this.hasEnded) {
|
|
76
|
+
throw Error('cannot continue a customer order when dialog has already ended');
|
|
77
|
+
}
|
|
78
|
+
if (!this.currentInteraction) {
|
|
79
|
+
throw new Error('there is no running customer interaction in this dialog to continue');
|
|
80
|
+
}
|
|
81
|
+
let clientResponse;
|
|
82
|
+
let isFirstMessage = true;
|
|
83
|
+
do {
|
|
84
|
+
const message = isFirstMessage
|
|
85
|
+
? this.createCurrentTanMessage(tanOrderReference, tan)
|
|
86
|
+
: this.createCurrentCustomerMessage();
|
|
87
|
+
const responseMessage = await this.httpClient.sendMessage(message);
|
|
88
|
+
await this.handlePartedMessages(message, responseMessage, this.currentInteraction);
|
|
89
|
+
clientResponse = this.currentInteraction.handleClientResponse(responseMessage);
|
|
90
|
+
this.checkEnded(clientResponse);
|
|
91
|
+
this.dialogId = clientResponse.dialogId;
|
|
92
|
+
this.responses.set(this.currentInteraction.segId, clientResponse);
|
|
93
|
+
if (clientResponse.success && !clientResponse.requiresTan) {
|
|
94
|
+
this.currentInteractionIndex++;
|
|
95
|
+
if (this.currentInteractionIndex > 0) {
|
|
96
|
+
this.isInitialized = true;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
isFirstMessage = false;
|
|
100
|
+
} while (!this.hasEnded &&
|
|
101
|
+
this.currentInteractionIndex < this.interactions.length &&
|
|
102
|
+
clientResponse.success &&
|
|
103
|
+
!clientResponse.requiresTan);
|
|
104
|
+
return this.responses;
|
|
105
|
+
}
|
|
106
|
+
addCustomerInteraction(interaction, afterCurrent = false) {
|
|
107
|
+
if (this.hasEnded) {
|
|
108
|
+
throw Error('cannot queue another customer interaction when dialog has already ended');
|
|
109
|
+
}
|
|
110
|
+
const isCustomerOrder = interaction instanceof CustomerOrderInteraction;
|
|
111
|
+
if (isCustomerOrder && !this.config.isTransactionSupported(interaction.segId)) {
|
|
112
|
+
throw Error(`customer order transaction ${interaction.segId} is not supported according to the BPD`);
|
|
33
113
|
}
|
|
34
114
|
interaction.dialog = this;
|
|
115
|
+
if (afterCurrent) {
|
|
116
|
+
this.interactions.splice(this.currentInteractionIndex + 1, 0, interaction);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
this.interactions.splice(this.interactions.length - 1, 0, interaction);
|
|
120
|
+
}
|
|
121
|
+
createCurrentCustomerMessage() {
|
|
35
122
|
this.lastMessageNumber++;
|
|
36
|
-
const
|
|
123
|
+
const isCustomerOrder = this.currentInteraction instanceof CustomerOrderInteraction;
|
|
124
|
+
const message = isCustomerOrder
|
|
125
|
+
? new CustomerOrderMessage(this.currentInteraction.segId, this.currentInteraction.responseSegId, this.dialogId, this.lastMessageNumber)
|
|
126
|
+
: new CustomerMessage(this.dialogId, this.lastMessageNumber);
|
|
37
127
|
const tanMethod = this.config.selectedTanMethod;
|
|
38
|
-
|
|
128
|
+
let isScaSupported = tanMethod && tanMethod.version >= 6;
|
|
129
|
+
let isTanMethodNeeded = isScaSupported;
|
|
130
|
+
if (isCustomerOrder) {
|
|
131
|
+
const bankTransaction = this.config.bankingInformation.bpd?.allowedTransactions.find((t) => t.transId === this.currentInteraction.segId);
|
|
132
|
+
isTanMethodNeeded = isScaSupported && bankTransaction?.tanRequired;
|
|
133
|
+
}
|
|
39
134
|
if (this.config.userId && this.config.pin) {
|
|
40
135
|
message.sign(this.config.countryCode, this.config.bankId, this.config.userId, this.config.pin, this.config.bankingInformation.systemId, isScaSupported ? this.config.tanMethodId : undefined);
|
|
41
136
|
}
|
|
42
|
-
const segments =
|
|
137
|
+
const segments = this.currentInteraction.getSegments(this.config);
|
|
43
138
|
segments.forEach((segment) => message.addSegment(segment));
|
|
44
|
-
if (this.config.userId && this.config.pin &&
|
|
139
|
+
if (this.config.userId && this.config.pin && isTanMethodNeeded) {
|
|
45
140
|
const hktan = {
|
|
46
141
|
header: { segId: HKTAN.Id, segNr: 0, version: tanMethod.version },
|
|
47
142
|
tanProcess: TanProcess.Process4,
|
|
@@ -49,76 +144,35 @@ export class Dialog {
|
|
|
49
144
|
};
|
|
50
145
|
message.addSegment(hktan);
|
|
51
146
|
}
|
|
52
|
-
|
|
53
|
-
const clientResponse = interaction.getClientResponse(initResponse);
|
|
54
|
-
this.dialogId = clientResponse.dialogId;
|
|
55
|
-
if (clientResponse.success) {
|
|
56
|
-
this.isInitialized = true;
|
|
57
|
-
}
|
|
58
|
-
this.checkEnded(initResponse);
|
|
59
|
-
return clientResponse;
|
|
147
|
+
return message;
|
|
60
148
|
}
|
|
61
|
-
|
|
62
|
-
if (!this.isInitialized || this.hasEnded) {
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
const tanMethod = this.config.selectedTanMethod;
|
|
66
|
-
const isScaSupported = tanMethod && tanMethod.version >= 6;
|
|
67
|
-
if (this.config.tanMethodId && !tanMethod) {
|
|
68
|
-
throw new Error('given tanMethodId is not available according to the BPD');
|
|
69
|
-
}
|
|
149
|
+
createCurrentTanMessage(tanOrderReference, tan) {
|
|
70
150
|
this.lastMessageNumber++;
|
|
71
151
|
const message = new CustomerMessage(this.dialogId, this.lastMessageNumber);
|
|
72
152
|
if (this.config.userId && this.config.pin) {
|
|
73
|
-
message.sign(this.config.countryCode, this.config.bankId, this.config.userId, this.config.pin, this.config.bankingInformation.systemId,
|
|
74
|
-
}
|
|
75
|
-
const hkend = {
|
|
76
|
-
header: { segId: HKEND.Id, segNr: 0, version: HKEND.Version },
|
|
77
|
-
dialogId: this.dialogId,
|
|
78
|
-
};
|
|
79
|
-
message.addSegment(hkend);
|
|
80
|
-
const responseMessage = await this.httpClient.sendMessage(message);
|
|
81
|
-
this.checkEnded(responseMessage);
|
|
82
|
-
return this.hasEnded;
|
|
83
|
-
}
|
|
84
|
-
async startCustomerOrderInteraction(interaction) {
|
|
85
|
-
if (!this.isInitialized) {
|
|
86
|
-
throw Error('dialog must be initialized before sending a customer order');
|
|
87
|
-
}
|
|
88
|
-
if (this.hasEnded) {
|
|
89
|
-
throw Error('cannot send a customer message when dialog has already ended');
|
|
90
|
-
}
|
|
91
|
-
const bankTransaction = this.config.bankingInformation.bpd?.allowedTransactions.find((t) => t.transId === interaction.segId);
|
|
92
|
-
if (!bankTransaction) {
|
|
93
|
-
throw Error(`transaction ${interaction.segId} is not supported according to the BPD`);
|
|
94
|
-
}
|
|
95
|
-
interaction.dialog = this;
|
|
96
|
-
this.lastMessageNumber++;
|
|
97
|
-
const message = new CustomerOrderMessage(interaction.segId, interaction.responseSegId, this.dialogId, this.lastMessageNumber);
|
|
98
|
-
if (this.config.userId && this.config.pin) {
|
|
99
|
-
message.sign(this.config.countryCode, this.config.bankId, this.config.userId, this.config.pin, this.config.bankingInformation.systemId, this.config.selectedTanMethod && (bankTransaction.tanRequired || this.config.selectedTanMethod.version >= 6)
|
|
100
|
-
? this.config.tanMethodId
|
|
101
|
-
: undefined);
|
|
153
|
+
message.sign(this.config.countryCode, this.config.bankId, this.config.userId, this.config.pin, this.config.bankingInformation.systemId, this.config.tanMethodId, tan);
|
|
102
154
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
155
|
+
if (this.config.userId && this.config.pin && this.config.tanMethodId) {
|
|
156
|
+
const hktan = {
|
|
157
|
+
header: { segId: HKTAN.Id, segNr: 0, version: this.config.selectedTanMethod.version },
|
|
158
|
+
tanProcess: this.config.selectedTanMethod?.isDecoupled ? TanProcess.Status : TanProcess.Process2,
|
|
159
|
+
segId: this.currentInteraction.segId,
|
|
160
|
+
orderRef: tanOrderReference,
|
|
161
|
+
nextTan: false,
|
|
162
|
+
tanMedia: this.config.selectedTanMethod.tanMediaRequirement >= TanMediaRequirement.Optional
|
|
163
|
+
? this.config.tanMediaName
|
|
164
|
+
: undefined,
|
|
165
|
+
};
|
|
166
|
+
message.addSegment(hktan);
|
|
115
167
|
}
|
|
116
|
-
|
|
168
|
+
return message;
|
|
169
|
+
}
|
|
170
|
+
async handlePartedMessages(message, responseMessage, interaction) {
|
|
117
171
|
let partedSegment = responseMessage.findSegment(PARTED.Id);
|
|
118
172
|
if (partedSegment) {
|
|
119
173
|
while (responseMessage.hasReturnCode(3040)) {
|
|
120
174
|
const answers = responseMessage.getBankAnswers();
|
|
121
|
-
const segmentWithContinuation = segments.find((s) => s.header.segId === interaction.segId);
|
|
175
|
+
const segmentWithContinuation = message.segments.find((s) => s.header.segId === interaction.segId);
|
|
122
176
|
if (!segmentWithContinuation) {
|
|
123
177
|
throw new Error(`Response contains segment with further information, but corresponding segment could not be found or is not specified`);
|
|
124
178
|
}
|
|
@@ -137,46 +191,10 @@ export class Dialog {
|
|
|
137
191
|
const index = responseMessage.segments.indexOf(partedSegment);
|
|
138
192
|
responseMessage.segments.splice(index, 1, completeSegment);
|
|
139
193
|
}
|
|
140
|
-
this.checkEnded(responseMessage);
|
|
141
|
-
return interaction.getClientResponse(responseMessage);
|
|
142
|
-
}
|
|
143
|
-
async sendTanMessage(refSegId, tanOrderReference, tan) {
|
|
144
|
-
if (!refSegId || !tanOrderReference) {
|
|
145
|
-
throw Error('refSegId and tanOrderReference must be provided to send a TAN message');
|
|
146
|
-
}
|
|
147
|
-
if (!this.config.selectedTanMethod?.isDecoupled && !tan) {
|
|
148
|
-
throw Error('TAN must be provided for non-decoupled TAN methods');
|
|
149
|
-
}
|
|
150
|
-
if (!this.isInitialized) {
|
|
151
|
-
throw Error('dialog must be initialized before sending a TAN message');
|
|
152
|
-
}
|
|
153
|
-
if (this.hasEnded) {
|
|
154
|
-
throw Error('cannot send a TAN message when dialog has alreay ended');
|
|
155
|
-
}
|
|
156
|
-
this.lastMessageNumber++;
|
|
157
|
-
const message = new CustomerMessage(this.dialogId, this.lastMessageNumber);
|
|
158
|
-
if (this.config.userId && this.config.pin) {
|
|
159
|
-
message.sign(this.config.countryCode, this.config.bankId, this.config.userId, this.config.pin, this.config.bankingInformation.systemId, this.config.tanMethodId, tan);
|
|
160
|
-
}
|
|
161
|
-
if (this.config.userId && this.config.pin && this.config.tanMethodId) {
|
|
162
|
-
const hktan = {
|
|
163
|
-
header: { segId: HKTAN.Id, segNr: 0, version: this.config.selectedTanMethod.version },
|
|
164
|
-
tanProcess: this.config.selectedTanMethod?.isDecoupled ? TanProcess.Status : TanProcess.Process2,
|
|
165
|
-
segId: refSegId,
|
|
166
|
-
orderRef: tanOrderReference,
|
|
167
|
-
nextTan: false,
|
|
168
|
-
tanMedia: this.config.selectedTanMethod.tanMediaRequirement >= TanMediaRequirement.Optional
|
|
169
|
-
? this.config.tanMediaName
|
|
170
|
-
: undefined,
|
|
171
|
-
};
|
|
172
|
-
message.addSegment(hktan);
|
|
173
|
-
}
|
|
174
|
-
const responseMessage = await this.httpClient.sendMessage(message);
|
|
175
|
-
this.checkEnded(responseMessage);
|
|
176
|
-
return responseMessage;
|
|
177
194
|
}
|
|
178
|
-
checkEnded(
|
|
179
|
-
if (
|
|
195
|
+
checkEnded(response) {
|
|
196
|
+
if (response.bankAnswers.some((answer) => answer.code === 100) ||
|
|
197
|
+
response.bankAnswers.some((answer) => answer.code === 9000)) {
|
|
180
198
|
this.hasEnded = true;
|
|
181
199
|
}
|
|
182
200
|
}
|
package/dist/httpClient.js
CHANGED
|
@@ -2,15 +2,23 @@ import { Message } from './message.js';
|
|
|
2
2
|
export class HttpClient {
|
|
3
3
|
url;
|
|
4
4
|
debug;
|
|
5
|
-
|
|
5
|
+
debugRaw;
|
|
6
|
+
constructor(url, debug = false, debugRaw = false) {
|
|
6
7
|
this.url = url;
|
|
7
8
|
this.debug = debug;
|
|
9
|
+
this.debugRaw = debugRaw;
|
|
8
10
|
}
|
|
9
11
|
async sendMessage(message) {
|
|
10
12
|
const encodedMessage = message.encode();
|
|
11
13
|
const requestBuffer = Buffer.from(encodedMessage);
|
|
12
14
|
if (this.debug) {
|
|
13
|
-
console.log('Request Message:\n'
|
|
15
|
+
console.log('Request Message:\n');
|
|
16
|
+
if (this.debugRaw) {
|
|
17
|
+
console.log(encodedMessage.split("'").join('\n'));
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
console.log(message.toString());
|
|
21
|
+
}
|
|
14
22
|
}
|
|
15
23
|
const response = await fetch(this.url, {
|
|
16
24
|
method: 'POST',
|
|
@@ -24,7 +32,13 @@ export class HttpClient {
|
|
|
24
32
|
const customerOrderMessage = message;
|
|
25
33
|
const responseMessage = Message.decode(responseText, customerOrderMessage.supportsPartedResponseSegments ? customerOrderMessage.orderResponseSegId : undefined);
|
|
26
34
|
if (this.debug) {
|
|
27
|
-
console.log('Response Message:\n'
|
|
35
|
+
console.log('Response Message:\n');
|
|
36
|
+
if (this.debugRaw) {
|
|
37
|
+
console.log(responseText.split("'").join('\n'));
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
console.log('Response Message:\n' + responseMessage.toString(true) + '\n');
|
|
41
|
+
}
|
|
28
42
|
}
|
|
29
43
|
return responseMessage;
|
|
30
44
|
}
|
package/dist/index.js
CHANGED
|
@@ -13,11 +13,11 @@ export class BalanceInteraction extends CustomerOrderInteraction {
|
|
|
13
13
|
if (!init.isAccountTransactionSupported(this.accountNumber, this.segId)) {
|
|
14
14
|
throw Error(`Account ${this.accountNumber} does not support business transaction '${this.segId}'`);
|
|
15
15
|
}
|
|
16
|
-
const account = { ...bankAccount, iban: undefined };
|
|
17
16
|
const version = init.getMaxSupportedTransactionVersion(HKSAL.Id);
|
|
18
17
|
if (!version) {
|
|
19
18
|
throw Error(`There is no supported version for business transaction '${HKSAL.Id}`);
|
|
20
19
|
}
|
|
20
|
+
const account = version <= 6 ? { ...bankAccount, iban: undefined, bic: undefined } : bankAccount;
|
|
21
21
|
const hksal = {
|
|
22
22
|
header: { segId: HKSAL.Id, segNr: 0, version: version },
|
|
23
23
|
account,
|