lib-fints 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -40
- package/dist/camtParser.js +412 -0
- package/dist/client.js +58 -80
- 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 +28 -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 +21 -16
- 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 +4 -4
- 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 +13 -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 +7 -4
- 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,8 @@ export class FinTSClient {
|
|
|
74
62
|
* @returns the synchronization response
|
|
75
63
|
*/
|
|
76
64
|
async synchronizeWithTan(tanReference, tan) {
|
|
77
|
-
|
|
65
|
+
const responses = await this.continueCustomerInteractionWithTan(tanReference, tan);
|
|
66
|
+
return responses.get(HKIDN.Id);
|
|
78
67
|
}
|
|
79
68
|
/**
|
|
80
69
|
* Checks if the bank supports fetching an account balance in general or for the given account number when provided
|
|
@@ -92,7 +81,8 @@ export class FinTSClient {
|
|
|
92
81
|
* @returns the account balance response
|
|
93
82
|
*/
|
|
94
83
|
async getAccountBalance(accountNumber) {
|
|
95
|
-
|
|
84
|
+
const responses = await this.startCustomerOrderInteraction(new BalanceInteraction(accountNumber));
|
|
85
|
+
return responses.get(HKSAL.Id);
|
|
96
86
|
}
|
|
97
87
|
/**
|
|
98
88
|
* Continues the account balance fetching when a TAN is required
|
|
@@ -101,7 +91,8 @@ export class FinTSClient {
|
|
|
101
91
|
* @returns the account balance response
|
|
102
92
|
*/
|
|
103
93
|
async getAccountBalanceWithTan(tanReference, tan) {
|
|
104
|
-
|
|
94
|
+
const responses = await this.continueCustomerInteractionWithTan(tanReference, tan);
|
|
95
|
+
return responses.get(HKSAL.Id);
|
|
105
96
|
}
|
|
106
97
|
/**
|
|
107
98
|
* Checks if the bank supports fetching account statements in general or for the given account number when provided
|
|
@@ -109,19 +100,41 @@ export class FinTSClient {
|
|
|
109
100
|
* @returns true if the bank (and account) supports fetching account statements
|
|
110
101
|
*/
|
|
111
102
|
canGetAccountStatements(accountNumber) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
103
|
+
if (accountNumber) {
|
|
104
|
+
// Check if either CAMT or MT940 is supported for this account
|
|
105
|
+
return (this.config.isAccountTransactionSupported(accountNumber, HKCAZ.Id) ||
|
|
106
|
+
this.config.isAccountTransactionSupported(accountNumber, HKKAZ.Id));
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// Check if either CAMT or MT940 is supported by the bank
|
|
110
|
+
return this.config.isTransactionSupported(HKCAZ.Id) || this.config.isTransactionSupported(HKKAZ.Id);
|
|
111
|
+
}
|
|
115
112
|
}
|
|
116
113
|
/**
|
|
117
114
|
* Fetches the account statements for the given account number
|
|
118
115
|
* @param accountNumber - the account number to fetch the statements for, must be an account available in the config.baningInformation.UPD.accounts
|
|
119
116
|
* @param from - an optional start date of the period to fetch the statements for
|
|
120
117
|
* @param to - an optional end date of the period to fetch the statements for
|
|
118
|
+
* @param preferCamt - whether to prefer CAMT format over MT940 when both are supported (default: true)
|
|
121
119
|
* @returns an account statements response containing an array of statements
|
|
122
120
|
*/
|
|
123
|
-
async getAccountStatements(accountNumber, from, to) {
|
|
124
|
-
|
|
121
|
+
async getAccountStatements(accountNumber, from, to, preferCamt = true) {
|
|
122
|
+
// Check what formats the bank supports
|
|
123
|
+
const camtSupported = this.config.isAccountTransactionSupported(accountNumber, 'HKCAZ');
|
|
124
|
+
const mt940Supported = this.config.isAccountTransactionSupported(accountNumber, 'HKKAZ');
|
|
125
|
+
if (!camtSupported && !mt940Supported) {
|
|
126
|
+
throw Error(`Account ${accountNumber} does not support account statements`);
|
|
127
|
+
}
|
|
128
|
+
// Choose format based on support and preference
|
|
129
|
+
const useCAMT = (preferCamt && camtSupported) || (!mt940Supported && camtSupported);
|
|
130
|
+
if (useCAMT) {
|
|
131
|
+
const responses = await this.startCustomerOrderInteraction(new StatementInteractionCAMT(accountNumber, from, to));
|
|
132
|
+
return responses.get(HKCAZ.Id);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
const responses = await this.startCustomerOrderInteraction(new StatementInteractionMT940(accountNumber, from, to));
|
|
136
|
+
return responses.get(HKKAZ.Id);
|
|
137
|
+
}
|
|
125
138
|
}
|
|
126
139
|
/**
|
|
127
140
|
* Continues the account statements fetching when a TAN is required
|
|
@@ -130,7 +143,8 @@ export class FinTSClient {
|
|
|
130
143
|
* @returns an account statements response containing an array of statements
|
|
131
144
|
*/
|
|
132
145
|
async getAccountStatementsWithTan(tanReference, tan) {
|
|
133
|
-
|
|
146
|
+
const responses = await this.continueCustomerInteractionWithTan(tanReference, tan);
|
|
147
|
+
return responses.get(HKKAZ.Id);
|
|
134
148
|
}
|
|
135
149
|
/**
|
|
136
150
|
* Checks if the bank supports fetching portfolio information in general or for the given account number when provided
|
|
@@ -151,7 +165,8 @@ export class FinTSClient {
|
|
|
151
165
|
* @returns a portfolio response containing holdings and total value
|
|
152
166
|
*/
|
|
153
167
|
async getPortfolio(accountNumber, currency, priceQuality, maxEntries) {
|
|
154
|
-
|
|
168
|
+
const responses = await this.startCustomerOrderInteraction(new PortfolioInteraction(accountNumber, currency, priceQuality, maxEntries));
|
|
169
|
+
return responses.get(HKWPD.Id);
|
|
155
170
|
}
|
|
156
171
|
/**
|
|
157
172
|
* Continues the portfolio fetching when a TAN is required
|
|
@@ -161,7 +176,8 @@ export class FinTSClient {
|
|
|
161
176
|
* @returns a portfolio response containing holdings and total value
|
|
162
177
|
*/
|
|
163
178
|
async getPortfolioWithTan(tanReference, tan) {
|
|
164
|
-
|
|
179
|
+
const responses = await this.continueCustomerInteractionWithTan(tanReference, tan);
|
|
180
|
+
return responses.get(HKWPD.Id);
|
|
165
181
|
}
|
|
166
182
|
/**
|
|
167
183
|
* Checks if the bank supports fetching credit card statements in general or for the given account number
|
|
@@ -182,7 +198,8 @@ export class FinTSClient {
|
|
|
182
198
|
* @returns an account statements response containing an array of statements
|
|
183
199
|
*/
|
|
184
200
|
async getCreditCardStatements(accountNumber, from) {
|
|
185
|
-
|
|
201
|
+
const responses = await this.startCustomerOrderInteraction(new CreditCardStatementInteraction(accountNumber, from));
|
|
202
|
+
return responses.get(DKKKU.Id);
|
|
186
203
|
}
|
|
187
204
|
/**
|
|
188
205
|
* Continues the credit card statements fetching when a TAN is required
|
|
@@ -191,57 +208,18 @@ export class FinTSClient {
|
|
|
191
208
|
* @returns a credit card statements response containing an array of statements
|
|
192
209
|
*/
|
|
193
210
|
async getCreditCardStatementsWithTan(tanReference, tan) {
|
|
194
|
-
|
|
211
|
+
const responses = await this.continueCustomerInteractionWithTan(tanReference, tan);
|
|
212
|
+
return responses.get(DKKKU.Id);
|
|
195
213
|
}
|
|
196
214
|
async startCustomerOrderInteraction(interaction) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
return syncResponse;
|
|
201
|
-
}
|
|
202
|
-
const clientResponse = await dialog.startCustomerOrderInteraction(interaction);
|
|
203
|
-
if (clientResponse.requiresTan) {
|
|
204
|
-
this.openCustomerInteractions.set(clientResponse.tanReference, interaction);
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
await dialog.end();
|
|
208
|
-
}
|
|
209
|
-
return clientResponse;
|
|
215
|
+
this.currentDialog = new Dialog(this.config, false);
|
|
216
|
+
this.currentDialog.addCustomerInteraction(interaction);
|
|
217
|
+
return await this.currentDialog.start();
|
|
210
218
|
}
|
|
211
219
|
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;
|
|
223
|
-
}
|
|
224
|
-
if (clientResponse.requiresTan) {
|
|
225
|
-
this.openCustomerInteractions.set(clientResponse.tanReference, interaction);
|
|
226
|
-
return clientResponse;
|
|
227
|
-
}
|
|
228
|
-
const initDialogInteraction = interaction;
|
|
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;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
await dialog.end();
|
|
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);
|
|
220
|
+
if (!this.currentDialog) {
|
|
221
|
+
throw new Error('no customer dialog was started which can continue');
|
|
244
222
|
}
|
|
245
|
-
return
|
|
223
|
+
return await this.currentDialog.continue(tanReference, tan);
|
|
246
224
|
}
|
|
247
225
|
}
|
|
@@ -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,
|