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.
Files changed (90) hide show
  1. package/README.md +43 -40
  2. package/dist/camtParser.js +412 -0
  3. package/dist/client.js +58 -80
  4. package/dist/dataGroups/SepaAccount.js +17 -0
  5. package/dist/dataGroups/SepaAccountParameters.js +16 -0
  6. package/dist/dialog.js +126 -108
  7. package/dist/httpClient.js +17 -3
  8. package/dist/index.js +1 -0
  9. package/dist/interactions/balanceInteraction.js +1 -1
  10. package/dist/interactions/customerInteraction.js +28 -3
  11. package/dist/interactions/endDialogInteraction.js +17 -0
  12. package/dist/interactions/initDialogInteraction.js +18 -8
  13. package/dist/interactions/sepaAccountInteraction.js +48 -0
  14. package/dist/interactions/statementInteractionCAMT.js +54 -0
  15. package/dist/interactions/{statementInteraction.js → statementInteractionMT940.js} +11 -8
  16. package/dist/interactions/tanMediaInteraction.js +4 -0
  17. package/dist/segments/HICAZ.js +22 -0
  18. package/dist/segments/HICAZS.js +19 -0
  19. package/dist/segments/HISPA.js +14 -0
  20. package/dist/segments/HISPAS.js +14 -0
  21. package/dist/segments/HKCAZ.js +28 -0
  22. package/dist/segments/HKSPA.js +21 -0
  23. package/dist/segments/registry.js +16 -2
  24. package/dist/statement.js +1 -0
  25. package/dist/tests/HICAZS.test.js +18 -0
  26. package/dist/tests/HKCAZ.test.js +47 -0
  27. package/dist/tests/camtParser.test.js +673 -0
  28. package/dist/tests/client.test.js +21 -16
  29. package/dist/tests/dialog.test.js +234 -0
  30. package/dist/types/bankAccount.d.ts +2 -3
  31. package/dist/types/bankAccount.d.ts.map +1 -1
  32. package/dist/types/camtParser.d.ts +31 -0
  33. package/dist/types/camtParser.d.ts.map +1 -0
  34. package/dist/types/client.d.ts +4 -4
  35. package/dist/types/client.d.ts.map +1 -1
  36. package/dist/types/config.d.ts +2 -1
  37. package/dist/types/config.d.ts.map +1 -1
  38. package/dist/types/dataGroups/SepaAccount.d.ts +11 -0
  39. package/dist/types/dataGroups/SepaAccount.d.ts.map +1 -0
  40. package/dist/types/dataGroups/SepaAccountParameters.d.ts +13 -0
  41. package/dist/types/dataGroups/SepaAccountParameters.d.ts.map +1 -0
  42. package/dist/types/dialog.d.ts +12 -8
  43. package/dist/types/dialog.d.ts.map +1 -1
  44. package/dist/types/httpClient.d.ts +2 -1
  45. package/dist/types/httpClient.d.ts.map +1 -1
  46. package/dist/types/index.d.ts +2 -1
  47. package/dist/types/index.d.ts.map +1 -1
  48. package/dist/types/interactions/customerInteraction.d.ts +13 -3
  49. package/dist/types/interactions/customerInteraction.d.ts.map +1 -1
  50. package/dist/types/interactions/endDialogInteraction.d.ts +13 -0
  51. package/dist/types/interactions/endDialogInteraction.d.ts.map +1 -0
  52. package/dist/types/interactions/initDialogInteraction.d.ts +2 -3
  53. package/dist/types/interactions/initDialogInteraction.d.ts.map +1 -1
  54. package/dist/types/interactions/sepaAccountInteraction.d.ts +17 -0
  55. package/dist/types/interactions/sepaAccountInteraction.d.ts.map +1 -0
  56. package/dist/types/interactions/{statementInteraction.d.ts → statementInteractionCAMT.d.ts} +4 -7
  57. package/dist/types/interactions/statementInteractionCAMT.d.ts.map +1 -0
  58. package/dist/types/interactions/statementInteractionMT940.d.ts +13 -0
  59. package/dist/types/interactions/statementInteractionMT940.d.ts.map +1 -0
  60. package/dist/types/interactions/tanMediaInteraction.d.ts.map +1 -1
  61. package/dist/types/mt940parser.d.ts +2 -38
  62. package/dist/types/mt940parser.d.ts.map +1 -1
  63. package/dist/types/segments/HICAZ.d.ts +22 -0
  64. package/dist/types/segments/HICAZ.d.ts.map +1 -0
  65. package/dist/types/segments/HICAZS.d.ts +17 -0
  66. package/dist/types/segments/HICAZS.d.ts.map +1 -0
  67. package/dist/types/segments/HISPA.d.ts +17 -0
  68. package/dist/types/segments/HISPA.d.ts.map +1 -0
  69. package/dist/types/segments/HISPAS.d.ts +14 -0
  70. package/dist/types/segments/HISPAS.d.ts.map +1 -0
  71. package/dist/types/segments/HKCAZ.d.ts +27 -0
  72. package/dist/types/segments/HKCAZ.d.ts.map +1 -0
  73. package/dist/types/segments/HKKAZ.d.ts +1 -1
  74. package/dist/types/segments/HKSAL.d.ts +1 -1
  75. package/dist/types/segments/HKSPA.d.ts +21 -0
  76. package/dist/types/segments/HKSPA.d.ts.map +1 -0
  77. package/dist/types/segments/HKTAN.d.ts +1 -1
  78. package/dist/types/segments/registry.d.ts.map +1 -1
  79. package/dist/types/statement.d.ts +39 -0
  80. package/dist/types/statement.d.ts.map +1 -0
  81. package/dist/types/tests/HICAZS.test.d.ts +2 -0
  82. package/dist/types/tests/HICAZS.test.d.ts.map +1 -0
  83. package/dist/types/tests/HKCAZ.test.d.ts +2 -0
  84. package/dist/types/tests/HKCAZ.test.d.ts.map +1 -0
  85. package/dist/types/tests/camtParser.test.d.ts +2 -0
  86. package/dist/types/tests/camtParser.test.d.ts.map +1 -0
  87. package/dist/types/tests/dialog.test.d.ts +2 -0
  88. package/dist/types/tests/dialog.test.d.ts.map +1 -0
  89. package/package.json +7 -4
  90. 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 { HKTAB } from './segments/HKTAB.js';
3
- import { StatementInteraction } from './interactions/statementInteraction.js';
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 { InitDialogInteraction } from './interactions/initDialogInteraction.js';
13
- import { CreditCardStatementInteraction } from "./interactions/creditcardStatementInteraction.js";
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
- openCustomerInteractions = new Map();
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
- const dialog = new Dialog(this.config);
55
- const syncResponse = await this.initDialog(dialog, true);
56
- if (!syncResponse.success || syncResponse.requiresTan) {
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
- return this.continueCustomerInteractionWithTan(tanReference, tan);
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
- return this.startCustomerOrderInteraction(new BalanceInteraction(accountNumber));
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
- return this.continueCustomerInteractionWithTan(tanReference, tan);
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
- return accountNumber
113
- ? this.config.isAccountTransactionSupported(accountNumber, HKKAZ.Id)
114
- : this.config.isTransactionSupported(HKKAZ.Id);
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
- return this.startCustomerOrderInteraction(new StatementInteraction(accountNumber, from, to));
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
- return this.continueCustomerInteractionWithTan(tanReference, tan);
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
- return this.startCustomerOrderInteraction(new PortfolioInteraction(accountNumber, currency, priceQuality, maxEntries));
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
- return this.continueCustomerInteractionWithTan(tanReference, tan);
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
- return this.startCustomerOrderInteraction(new CreditCardStatementInteraction(accountNumber, from));
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
- return this.continueCustomerInteractionWithTan(tanReference, tan);
211
+ const responses = await this.continueCustomerInteractionWithTan(tanReference, tan);
212
+ return responses.get(DKKKU.Id);
195
213
  }
196
214
  async startCustomerOrderInteraction(interaction) {
197
- const dialog = new Dialog(this.config);
198
- const syncResponse = await this.initDialog(dialog, false, interaction);
199
- if (!syncResponse.success || syncResponse.requiresTan) {
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
- const interaction = this.openCustomerInteractions.get(tanReference);
213
- if (!interaction) {
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 initResponse;
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
- async initialize(interaction) {
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 initialize a dialog that has already ended');
42
+ throw Error('cannot start a dialog that has already ended');
30
43
  }
31
44
  if (this.lastMessageNumber > 0) {
32
- throw new Error('dialog initialization must be the first message in a 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 message = new CustomerMessage(this.dialogId, this.lastMessageNumber);
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
- const isScaSupported = tanMethod && tanMethod.version >= 6;
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 = interaction.getSegments(this.config);
137
+ const segments = this.currentInteraction.getSegments(this.config);
43
138
  segments.forEach((segment) => message.addSegment(segment));
44
- if (this.config.userId && this.config.pin && isScaSupported) {
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
- const initResponse = await this.httpClient.sendMessage(message);
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
- async end() {
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, isScaSupported ? this.config.tanMethodId : undefined);
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
- const segments = interaction.getSegments(this.config);
104
- segments.forEach((segment) => message.addSegment(segment));
105
- if (bankTransaction.tanRequired) {
106
- if (this.config.userId && this.config.pin && this.config.tanMethodId) {
107
- const hktan = {
108
- header: { segId: HKTAN.Id, segNr: 0, version: this.config.selectedTanMethod.version },
109
- tanProcess: TanProcess.Process4,
110
- segId: interaction.segId,
111
- tanMedia: this.config.tanMediaName,
112
- };
113
- message.addSegment(hktan);
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
- let responseMessage = await this.httpClient.sendMessage(message);
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(initResponse) {
179
- if (initResponse.hasReturnCode(100) || initResponse.hasReturnCode(9800)) {
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
  }
@@ -2,15 +2,23 @@ import { Message } from './message.js';
2
2
  export class HttpClient {
3
3
  url;
4
4
  debug;
5
- constructor(url, debug = false) {
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' + message.toString());
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' + responseMessage.toString(true) + '\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
@@ -12,5 +12,6 @@ export * from './bankingInformation.js';
12
12
  export * from './accountBalance.js';
13
13
  export * from './bpd.js';
14
14
  export * from './upd.js';
15
+ export * from './statement.js';
15
16
  export * from './mt940parser.js';
16
17
  export * from './mt535parser.js';
@@ -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,