lib-fints 1.1.2 → 1.2.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.
Files changed (36) hide show
  1. package/README.md +5 -0
  2. package/dist/client.js +33 -0
  3. package/dist/dialog.js +1 -7
  4. package/dist/index.js +1 -0
  5. package/dist/interactions/portfolioInteraction.js +57 -0
  6. package/dist/mt535parser.js +196 -0
  7. package/dist/segments/HIWPD.js +15 -0
  8. package/dist/segments/HKWPD.js +23 -0
  9. package/dist/segments/registry.js +4 -0
  10. package/dist/tests/HIWPD.test.js +25 -0
  11. package/dist/tests/HKWPD.test.js +38 -0
  12. package/dist/tests/mt535parser.test.js +157 -0
  13. package/dist/types/client.d.ts +24 -0
  14. package/dist/types/client.d.ts.map +1 -1
  15. package/dist/types/dialog.d.ts.map +1 -1
  16. package/dist/types/index.d.ts +2 -0
  17. package/dist/types/index.d.ts.map +1 -1
  18. package/dist/types/interactions/portfolioInteraction.d.ts +39 -0
  19. package/dist/types/interactions/portfolioInteraction.d.ts.map +1 -0
  20. package/dist/types/mt535parser.d.ts +44 -0
  21. package/dist/types/mt535parser.d.ts.map +1 -0
  22. package/dist/types/segments/HIWPD.d.ts +22 -0
  23. package/dist/types/segments/HIWPD.d.ts.map +1 -0
  24. package/dist/types/segments/HKWPD.d.ts +43 -0
  25. package/dist/types/segments/HKWPD.d.ts.map +1 -0
  26. package/dist/types/segments/registry.d.ts.map +1 -1
  27. package/dist/types/tests/HIWPD.test.d.ts +2 -0
  28. package/dist/types/tests/HIWPD.test.d.ts.map +1 -0
  29. package/dist/types/tests/HKWPD.test.d.ts +2 -0
  30. package/dist/types/tests/HKWPD.test.d.ts.map +1 -0
  31. package/dist/types/tests/mt535parser.test.d.ts +2 -0
  32. package/dist/types/tests/mt535parser.test.d.ts.map +1 -0
  33. package/package.json +7 -7
  34. package/dist/httpClientNode.js +0 -59
  35. package/dist/types/httpClientNode.d.ts +0 -8
  36. package/dist/types/httpClientNode.d.ts.map +0 -1
package/README.md CHANGED
@@ -1,3 +1,4 @@
1
+ [![Node.js CI with pnpm](https://github.com/robocode13/lib-fints/actions/workflows/build-test.yml/badge.svg?branch=main)](https://github.com/robocode13/lib-fints/actions/workflows/build-test.yml)
1
2
  # Lib-FinTS
2
3
 
3
4
  A Typescript/Javascript client library for Online-Banking via the FinTS 3.0 protocol with PIN/TAN, supporting PSD2 and decoupled TAN methods. The library has no dependencies on other libraries
@@ -86,6 +87,9 @@ const balanceResponse = await client.getAccountBalance(account.accountNumber);
86
87
 
87
88
  // fetch all available statements
88
89
  const statementResponse = await client.getAccountStatements(account.accountNumber);
90
+
91
+ // or fetch portfolio from a securities account
92
+ client.getPortfolio(account.accountNumber);
89
93
  ```
90
94
 
91
95
  These are only the most basic steps needed to retrieve information from the bank. There are still some unanswered questions like "how to handle TANs" or "how to avoid synchronizations every time you start a new session". These are explained in the corresponding sections below.
@@ -191,6 +195,7 @@ This will print out all sent messages and received responses to the console in a
191
195
  - Synchronize bank and account information
192
196
  - Fetching account balances
193
197
  - Fetching account statements
198
+ - Fetching the portfolio of a securities account
194
199
 
195
200
  Implementing further transactions should be straight forward and contributions are highly appreciated
196
201
 
package/dist/client.js CHANGED
@@ -2,10 +2,12 @@ import { Dialog } from './dialog.js';
2
2
  import { HKTAB } from './segments/HKTAB.js';
3
3
  import { StatementInteraction } from './interactions/statementInteraction.js';
4
4
  import { BalanceInteraction } from './interactions/balanceInteraction.js';
5
+ import { PortfolioInteraction } from './interactions/portfolioInteraction.js';
5
6
  import { FinTSConfig } from './config.js';
6
7
  import { TanMediaInteraction } from './interactions/tanMediaInteraction.js';
7
8
  import { HKSAL } from './segments/HKSAL.js';
8
9
  import { HKKAZ } from './segments/HKKAZ.js';
10
+ import { HKWPD } from './segments/HKWPD.js';
9
11
  import { InitDialogInteraction } from './interactions/initDialogInteraction.js';
10
12
  /**
11
13
  * A client to communicate with a bank over the FinTS protocol
@@ -128,6 +130,37 @@ export class FinTSClient {
128
130
  async getAccountStatementsWithTan(tanReference, tan) {
129
131
  return this.continueCustomerInteractionWithTan(tanReference, tan);
130
132
  }
133
+ /**
134
+ * Checks if the bank supports fetching portfolio information in general or for the given account number when provided
135
+ * @param accountNumber when the account number is provided, checks if the account supports fetching of portfolio information
136
+ * @returns true if the bank (and account) supports fetching portfolio information
137
+ */
138
+ canGetPortfolio(accountNumber) {
139
+ return accountNumber
140
+ ? this.config.isAccountTransactionSupported(accountNumber, HKWPD.Id)
141
+ : this.config.isTransactionSupported(HKWPD.Id);
142
+ }
143
+ /**
144
+ * Fetches the portfolio information for the given depot account number
145
+ * @param accountNumber - the depot account number to fetch the portfolio for, must be an account available in the config.bankingInformation.UPD.accounts
146
+ * @param currency - optional currency filter for the portfolio statement
147
+ * @param priceQuality - optional price quality filter ('1' for real-time, '2' for delayed)
148
+ * @param maxEntries - optional maximum number of entries to retrieve
149
+ * @returns a portfolio response containing holdings and total value
150
+ */
151
+ async getPortfolio(accountNumber, currency, priceQuality, maxEntries) {
152
+ return this.startCustomerOrderInteraction(new PortfolioInteraction(accountNumber, currency, priceQuality, maxEntries));
153
+ }
154
+ /**
155
+ * Continues the portfolio fetching when a TAN is required
156
+ * @param tanReference The TAN reference provided in the first call's response
157
+
158
+ * @param tan The TAN entered by the user, can be omitted if a decoupled TAN method is used
159
+ * @returns a portfolio response containing holdings and total value
160
+ */
161
+ async getPortfolioWithTan(tanReference, tan) {
162
+ return this.continueCustomerInteractionWithTan(tanReference, tan);
163
+ }
131
164
  async startCustomerOrderInteraction(interaction) {
132
165
  const dialog = new Dialog(this.config);
133
166
  const syncResponse = await this.initDialog(dialog, false, interaction);
package/dist/dialog.js CHANGED
@@ -7,7 +7,6 @@ import { HKTAN } from './segments/HKTAN.js';
7
7
  import { HNHBK } from './segments/HNHBK.js';
8
8
  import { decode } from './segment.js';
9
9
  import { PARTED } from './partedSegment.js';
10
- import { HttpClientNode } from './httpClientNode.js';
11
10
  export class Dialog {
12
11
  config;
13
12
  dialogId = '0';
@@ -182,11 +181,6 @@ export class Dialog {
182
181
  }
183
182
  }
184
183
  getHttpClient() {
185
- if (typeof fetch === 'function') {
186
- return new HttpClient(this.config.bankingUrl, this.config.debugEnabled);
187
- }
188
- else {
189
- return new HttpClientNode(this.config.bankingUrl, this.config.debugEnabled);
190
- }
184
+ return new HttpClient(this.config.bankingUrl, this.config.debugEnabled);
191
185
  }
192
186
  }
package/dist/index.js CHANGED
@@ -13,3 +13,4 @@ export * from './accountBalance.js';
13
13
  export * from './bpd.js';
14
14
  export * from './upd.js';
15
15
  export * from './mt940parser.js';
16
+ export * from './mt535parser.js';
@@ -0,0 +1,57 @@
1
+ import { CustomerOrderInteraction, } from './customerInteraction.js';
2
+ import { HKWPD } from '../segments/HKWPD.js';
3
+ import { HIWPD } from '../segments/HIWPD.js';
4
+ import { Mt535Parser } from '../mt535parser.js';
5
+ /**
6
+ * Interaction for requesting and parsing stock portfolio information (HKWPD/HIWPD)
7
+ */
8
+ export class PortfolioInteraction extends CustomerOrderInteraction {
9
+ accountNumber;
10
+ currency;
11
+ priceQuality;
12
+ maxEntries;
13
+ paginationMarker;
14
+ constructor(accountNumber, currency, priceQuality, maxEntries, paginationMarker) {
15
+ super(HKWPD.Id, HIWPD.Id);
16
+ this.accountNumber = accountNumber;
17
+ this.currency = currency;
18
+ this.priceQuality = priceQuality;
19
+ this.maxEntries = maxEntries;
20
+ this.paginationMarker = paginationMarker;
21
+ }
22
+ createSegments(config) {
23
+ const bankAccount = config.getBankAccount(this.accountNumber);
24
+ if (!config.isAccountTransactionSupported(this.accountNumber, this.segId)) {
25
+ throw Error(`Account ${this.accountNumber} does not support business transaction '${this.segId}'`);
26
+ }
27
+ const depotAccount = { ...bankAccount, iban: undefined }; // HKWPD uses KTV which doesn't have IBAN
28
+ const version = config.getMaxSupportedTransactionVersion(HKWPD.Id);
29
+ if (!version) {
30
+ throw Error(`There is no supported version for business transaction '${HKWPD.Id}'`);
31
+ }
32
+ const hkwpd = {
33
+ header: { segId: HKWPD.Id, segNr: 0, version: version },
34
+ depot: depotAccount,
35
+ currency: this.currency,
36
+ priceQuality: this.priceQuality,
37
+ maxEntries: this.maxEntries,
38
+ paginationMarker: this.paginationMarker,
39
+ };
40
+ return [hkwpd];
41
+ }
42
+ handleResponse(response, clientResponse) {
43
+ const hiwpdSegment = response.findSegment(HIWPD.Id);
44
+ if (hiwpdSegment?.portfolioStatement) {
45
+ try {
46
+ // Parse the MT535 data
47
+ const parser = new Mt535Parser(hiwpdSegment.portfolioStatement);
48
+ clientResponse.portfolioStatement = parser.parse();
49
+ }
50
+ catch (error) {
51
+ console.warn('Failed to parse MT535 portfolio statement:', error);
52
+ // Fallback: provide raw data if parsing fails
53
+ clientResponse.rawMT535Data = hiwpdSegment.portfolioStatement;
54
+ }
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,196 @@
1
+ export var TokenType535;
2
+ (function (TokenType535) {
3
+ TokenType535["DepotValueBlock"] = "DepotValueBlock";
4
+ TokenType535["DepotValueCurrency"] = "DepotValueCurrency";
5
+ TokenType535["FinBlock"] = "FinBlock";
6
+ TokenType535["SecurityIdentification"] = "SecurityIdentification";
7
+ TokenType535["AcquisitionPrice"] = "AcquisitionPrice";
8
+ TokenType535["PriceBlock"] = "PriceBlock";
9
+ TokenType535["AmountBlock"] = "AmountBlock";
10
+ TokenType535["DateTimeBlock"] = "DateTimeBlock";
11
+ TokenType535["DateString"] = "DateString";
12
+ TokenType535["TimeString"] = "TimeString";
13
+ })(TokenType535 || (TokenType535 = {}));
14
+ const tokens535 = {
15
+ [TokenType535.DepotValueBlock]: /:16R:ADDINFO(.*?):16S:ADDINFO/ms,
16
+ [TokenType535.DepotValueCurrency]: /EUR(.*)/ms,
17
+ [TokenType535.FinBlock]: /:16R:FIN(.*?):16S:FIN/gms,
18
+ [TokenType535.SecurityIdentification]: /:35B:(.*?):/ms,
19
+ [TokenType535.AcquisitionPrice]: /:70E::HOLD\/\/\d*STK2(\d*),(\d*)\+([A-Z]{3})/ms,
20
+ [TokenType535.PriceBlock]: /:90([AB])::(.*?):/ms,
21
+ [TokenType535.AmountBlock]: /:93B::(.*?):/ms,
22
+ [TokenType535.DateTimeBlock]: /:98([AC])::(.*?):/ms,
23
+ [TokenType535.DateString]: /(\d{4})(\d{2})(\d{2})/,
24
+ [TokenType535.TimeString]: /^.{14}(\d{2})(\d{2})(\d{2})/ms,
25
+ };
26
+ export class Mt535Parser {
27
+ rawData;
28
+ cleanedRawData;
29
+ constructor(rawData) {
30
+ this.rawData = rawData;
31
+ // The divider can be either \r\n or @@
32
+ const crlfCount = (rawData.match(/\r\n-/g) || []).length;
33
+ const atAtCount = (rawData.match(/@@-/g) || []).length;
34
+ const divider = crlfCount > atAtCount ? '\r\n' : '@@';
35
+ // Remove dividers that are not followed by a colon (tag indicator)
36
+ const regex = new RegExp(divider + '([^:])', 'gms');
37
+ this.cleanedRawData = rawData.replace(regex, '$1');
38
+ }
39
+ parse() {
40
+ const result = {
41
+ holdings: [],
42
+ };
43
+ // Parse total depot value
44
+ result.totalValue = this.parseDepotValue();
45
+ // Parse individual holdings
46
+ result.holdings = this.parseHoldings();
47
+ return result;
48
+ }
49
+ parseDepotValue() {
50
+ const addInfoMatch = this.cleanedRawData.match(tokens535[TokenType535.DepotValueBlock]);
51
+ if (addInfoMatch) {
52
+ const eurMatch = addInfoMatch[1].match(tokens535[TokenType535.DepotValueCurrency]);
53
+ if (eurMatch) {
54
+ return parseFloat(eurMatch[1].replace(',', '.'));
55
+ }
56
+ }
57
+ return undefined;
58
+ }
59
+ parseHoldings() {
60
+ const holdings = [];
61
+ const finBlocks = this.cleanedRawData.match(tokens535[TokenType535.FinBlock]);
62
+ if (!finBlocks) {
63
+ return holdings;
64
+ }
65
+ for (const block of finBlocks) {
66
+ const holding = this.parseHolding(block);
67
+ if (holding) {
68
+ holdings.push(holding);
69
+ }
70
+ }
71
+ return holdings;
72
+ }
73
+ parseHolding(block) {
74
+ const holding = {};
75
+ // Parse ISIN, WKN & Name from :35B:
76
+ // :35B:ISIN DE0005190003/DE/519000BAY.MOTOREN WERKE AG ST
77
+ this.parseSecurityIdentification(block, holding);
78
+ // Parse acquisition price from :70E::HOLD//
79
+ this.parseAcquisitionPrice(block, holding);
80
+ // Parse current price from :90B: or :90A:
81
+ this.parsePrice(block, holding);
82
+ // Parse amount from :93B:
83
+ this.parseAmount(block, holding);
84
+ // Parse date/time from :98A: or :98C:
85
+ this.parseDateTime(block, holding);
86
+ // Calculate value if we have price and amount
87
+ if (holding.amount !== undefined && holding.price !== undefined) {
88
+ // For all currencies, value is price multiplied by amount
89
+ holding.value = holding.price * holding.amount;
90
+ }
91
+ return holding;
92
+ }
93
+ parseSecurityIdentification(block, holding) {
94
+ const match = block.match(tokens535[TokenType535.SecurityIdentification]);
95
+ if (match) {
96
+ const content = match[1];
97
+ // ISIN: characters 5-16 (12 chars)
98
+ const isinMatch = content.match(/^.{5}(.{12})/ms);
99
+ if (isinMatch) {
100
+ holding.isin = isinMatch[1];
101
+ }
102
+ // WKN: characters 21-26 (6 chars)
103
+ const wknMatch = content.match(/^.{21}(.{6})/ms);
104
+ if (wknMatch) {
105
+ holding.wkn = wknMatch[1];
106
+ }
107
+ // Name: everything from character 27 onwards
108
+ const nameMatch = content.match(/^.{27}(.*)/ms);
109
+ if (nameMatch) {
110
+ holding.name = nameMatch[1].trim();
111
+ }
112
+ }
113
+ }
114
+ parseAcquisitionPrice(block, holding) {
115
+ const match = block.match(tokens535[TokenType535.AcquisitionPrice]);
116
+ if (match) {
117
+ holding.acquisitionPrice = parseFloat(`${match[1]}.${match[2]}`);
118
+ if (!holding.currency) {
119
+ holding.currency = match[3];
120
+ }
121
+ }
122
+ }
123
+ parsePrice(block, holding) {
124
+ const match = block.match(tokens535[TokenType535.PriceBlock]);
125
+ if (match) {
126
+ const type = match[1];
127
+ const content = match[2];
128
+ if (type === 'B') {
129
+ // Currency from characters 11-13 (3 chars)
130
+ const currencyMatch = content.match(/^.{11}(.{3})/ms);
131
+ if (currencyMatch) {
132
+ holding.currency = currencyMatch[1];
133
+ }
134
+ // Price from character 14 onwards
135
+ const priceMatch = content.match(/^.{14}(.*)/ms);
136
+ if (priceMatch) {
137
+ holding.price = parseFloat(priceMatch[1].replace(',', '.'));
138
+ }
139
+ }
140
+ else if (type === 'A') {
141
+ holding.currency = '%';
142
+ // Price from character 11 onwards
143
+ const priceMatch = content.match(/^.{11}(.*)/ms);
144
+ if (priceMatch) {
145
+ holding.price = parseFloat(priceMatch[1].replace(',', '.')) / 100;
146
+ }
147
+ }
148
+ }
149
+ }
150
+ parseAmount(block, holding) {
151
+ const match = block.match(tokens535[TokenType535.AmountBlock]);
152
+ if (match) {
153
+ // Amount from character 11 onwards
154
+ const amountMatch = match[1].match(/^.{11}(.*)/ms);
155
+ if (amountMatch) {
156
+ holding.amount = parseFloat(amountMatch[1].replace(',', '.'));
157
+ }
158
+ }
159
+ }
160
+ parseDateTime(block, holding) {
161
+ const match = block.match(tokens535[TokenType535.DateTimeBlock]);
162
+ if (match) {
163
+ const type = match[1];
164
+ const content = match[2];
165
+ // Date from characters 6-13 (8 chars: YYYYMMDD)
166
+ const dateMatch = content.match(tokens535[TokenType535.DateString]);
167
+ if (dateMatch) {
168
+ const parsedDate = this.parseDate(dateMatch[0]);
169
+ if (type === 'C') {
170
+ // :98C: has a time component HHMMSS starting at character 14
171
+ const timeMatch = content.match(tokens535[TokenType535.TimeString]);
172
+ if (timeMatch) {
173
+ parsedDate.setHours(parseInt(timeMatch[1]), parseInt(timeMatch[2]), parseInt(timeMatch[3]));
174
+ }
175
+ }
176
+ else {
177
+ // :98A: time defaults to 00:00:00
178
+ parsedDate.setHours(0, 0, 0, 0);
179
+ }
180
+ holding.date = parsedDate;
181
+ }
182
+ }
183
+ }
184
+ parseDate(dateString) {
185
+ const match = dateString.match(tokens535[TokenType535.DateString]);
186
+ if (!match) {
187
+ throw new Error(`Invalid date format: ${dateString}`);
188
+ }
189
+ try {
190
+ return new Date(parseInt(match[1]), parseInt(match[2]) - 1, parseInt(match[3]));
191
+ }
192
+ catch (error) {
193
+ throw new Error(`Invalid date: ${dateString}`, { cause: error });
194
+ }
195
+ }
196
+ }
@@ -0,0 +1,15 @@
1
+ import { Binary } from '../dataElements/Binary.js';
2
+ import { SegmentDefinition } from '../segmentDefinition.js';
3
+ /**
4
+ * Geschäftsvorfälle: C.4.3.1 Kreditinstitutsrückmeldung
5
+ * Version: 6
6
+ */
7
+ export class HIWPD extends SegmentDefinition {
8
+ static Id = 'HIWPD';
9
+ static Version = 6;
10
+ constructor() {
11
+ super(HIWPD.Id);
12
+ }
13
+ version = HIWPD.Version;
14
+ elements = [new Binary('portfolioStatement', 1, 1)];
15
+ }
@@ -0,0 +1,23 @@
1
+ import { AccountGroup } from '../dataGroups/Account.js';
2
+ import { AlphaNumeric } from '../dataElements/AlphaNumeric.js';
3
+ import { Numeric } from '../dataElements/Numeric.js';
4
+ import { SegmentDefinition } from '../segmentDefinition.js';
5
+ /**
6
+ * Geschäftsvorfälle: C.4.3.1 Depotaufstellung
7
+ * Version: 6
8
+ */
9
+ export class HKWPD extends SegmentDefinition {
10
+ static Id = 'HKWPD';
11
+ static Version = 6;
12
+ constructor() {
13
+ super(HKWPD.Id);
14
+ }
15
+ version = HKWPD.Version;
16
+ elements = [
17
+ new AccountGroup('depot', 1, 1),
18
+ new AlphaNumeric('currency', 0, 1, 3),
19
+ new AlphaNumeric('priceQuality', 0, 1, 1),
20
+ new Numeric('maxEntries', 0, 1, 4),
21
+ new AlphaNumeric('paginationMarker', 0, 1, 35),
22
+ ];
23
+ }
@@ -27,6 +27,8 @@ import { HKKAZ } from './HKKAZ.js';
27
27
  import { HIKAZ } from './HIKAZ.js';
28
28
  import { HIKAZS } from './HIKAZS.js';
29
29
  import { HITAB } from './HITAB.js';
30
+ import { HKWPD } from './HKWPD.js';
31
+ import { HIWPD } from './HIWPD.js';
30
32
  import { UNKNOW } from '../unknownSegment.js';
31
33
  import { PARTED } from '../partedSegment.js';
32
34
  const registry = new Map();
@@ -60,6 +62,8 @@ export function registerSegments() {
60
62
  registerSegmentDefinition(new HKKAZ());
61
63
  registerSegmentDefinition(new HIKAZ());
62
64
  registerSegmentDefinition(new HIKAZS());
65
+ registerSegmentDefinition(new HKWPD());
66
+ registerSegmentDefinition(new HIWPD());
63
67
  registerSegmentDefinition(new UNKNOW());
64
68
  registerSegmentDefinition(new PARTED());
65
69
  }
@@ -0,0 +1,25 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { registerSegments } from '../segments/registry.js';
3
+ import { decode, encode } from '../segment.js';
4
+ registerSegments();
5
+ describe('HIWPD', () => {
6
+ it('decode and encode roundtrip matches', () => {
7
+ const portfolioData = 'U29tZSBTd2lmdCBNVCA1MzUvNTcxIGRhdGE=';
8
+ const text = `HIWPD:1:6+${portfolioData}'`;
9
+ const segment = decode(text);
10
+ expect(segment.portfolioStatement).toBe(portfolioData);
11
+ const expectedEncodedText = `HIWPD:1:6+@${portfolioData.length}@${portfolioData}'`;
12
+ expect(encode(segment)).toBe(expectedEncodedText);
13
+ });
14
+ it('handles empty portfolio data', () => {
15
+ const text = "HIWPD:1:6+@@'";
16
+ const segment = decode(text);
17
+ expect(segment.portfolioStatement).toBe('');
18
+ });
19
+ it('handles large portfolio data', () => {
20
+ const largeData = 'A'.repeat(1000);
21
+ const text = `HIWPD:1:6+${largeData}'`;
22
+ const segment = decode(text);
23
+ expect(segment.portfolioStatement).toBe(largeData);
24
+ });
25
+ });
@@ -0,0 +1,38 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { registerSegments } from '../segments/registry.js';
3
+ import { decode, encode } from '../segment.js';
4
+ registerSegments();
5
+ describe('HKWPD', () => {
6
+ it('decode and encode roundtrip matches with all fields', () => {
7
+ const text = "HKWPD:2:6+12345678901234567890:EUR:276:280+EUR+1+100+NextMarkerABC123'";
8
+ const segment = decode(text);
9
+ expect(segment.depot.accountNumber).toBe('12345678901234567890');
10
+ expect(segment.depot.subAccountId).toBe('EUR');
11
+ expect(segment.depot.bank.country).toBe(276);
12
+ expect(segment.depot.bank.bankId).toBe('280');
13
+ expect(segment.currency).toBe('EUR');
14
+ expect(segment.priceQuality).toBe('1');
15
+ expect(segment.maxEntries).toBe(100);
16
+ expect(segment.paginationMarker).toBe('NextMarkerABC123');
17
+ expect(encode(segment)).toBe(text);
18
+ });
19
+ it('decode and encode roundtrip matches with minimal fields', () => {
20
+ const text = "HKWPD:1:6+9876543210::276:280'";
21
+ const segment = decode(text);
22
+ expect(segment.depot.accountNumber).toBe('9876543210');
23
+ expect(segment.depot.subAccountId).toBeUndefined();
24
+ expect(segment.depot.bank.country).toBe(276);
25
+ expect(segment.depot.bank.bankId).toBe('280');
26
+ expect(segment.currency).toBeUndefined();
27
+ expect(segment.priceQuality).toBeUndefined();
28
+ expect(segment.maxEntries).toBeUndefined();
29
+ expect(segment.paginationMarker).toBeUndefined();
30
+ const encoded = encode(segment);
31
+ expect(encoded).toBe("HKWPD:1:6+9876543210::276:280'");
32
+ const decodedAgain = decode(encoded);
33
+ expect(decodedAgain.depot.accountNumber).toBe('9876543210');
34
+ expect(decodedAgain.depot.subAccountId).toBeUndefined();
35
+ expect(decodedAgain.depot.bank.country).toBe(276);
36
+ expect(decodedAgain.depot.bank.bankId).toBe('280');
37
+ });
38
+ });
@@ -0,0 +1,157 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { Mt535Parser } from '../mt535parser.js';
3
+ describe('Mt535Parser', () => {
4
+ it('parses a MT535 input string with depot value and multiple holdings', () => {
5
+ const input = ':16R:ADDINFO\r\n' +
6
+ 'EUR125000,50\r\n' +
7
+ ':16S:ADDINFO\r\n' +
8
+ // Holding 1 (BMW)
9
+ ':16R:FIN\r\n' +
10
+ ':35B:ISIN DE0005190003/DE/519000BAY.MOTOREN WERKE AG ST:\r\n' +
11
+ ':70E::HOLD//100STK275,30+EUR\r\n' +
12
+ ':90B::PREFIX_11_CEUR100,50:\r\n' +
13
+ ':93B::QTY/AVALBLX100,:\r\n' +
14
+ ':98A::SETT//DTE20231101:\r\n' +
15
+ ':16S:FIN\r\n' +
16
+ // Holding 2 (Apple)
17
+ ':16R:FIN\r\n' +
18
+ ':35B:ISIN US0378331005/US/037833Apple Inc. Common Stock:\r\n' +
19
+ ':70E::HOLD//50STK2150,75+USD\r\n' +
20
+ ':90A::PRIC/RATE_X85,50:\r\n' +
21
+ ':93B::QTY/AVALBLX50,:\r\n' +
22
+ ':98C::TRADTE20231101143000:\r\n' +
23
+ ':16S:FIN\r\n';
24
+ const parser = new Mt535Parser(input);
25
+ const statement = parser.parse();
26
+ // Test depot value
27
+ expect(statement.totalValue).toBe(125000.5);
28
+ expect(statement.holdings).toHaveLength(2);
29
+ // Test first holding (BMW)
30
+ const bmwHolding = statement.holdings[0];
31
+ expect(bmwHolding.isin).toBe('DE0005190003');
32
+ expect(bmwHolding.wkn).toBe('519000');
33
+ expect(bmwHolding.name).toBe('BAY.MOTOREN WERKE AG ST');
34
+ expect(bmwHolding.acquisitionPrice).toBe(75.3);
35
+ expect(bmwHolding.price).toBe(100.5);
36
+ expect(bmwHolding.currency).toBe('EUR');
37
+ expect(bmwHolding.amount).toBe(100);
38
+ expect(bmwHolding.value).toBe(10050);
39
+ expect(bmwHolding.date).toEqual(new Date(2023, 10, 1, 0, 0, 0));
40
+ // Test second holding (Apple)
41
+ const appleHolding = statement.holdings[1];
42
+ expect(appleHolding.isin).toBe('US0378331005');
43
+ expect(appleHolding.wkn).toBe('037833');
44
+ expect(appleHolding.name).toBe('Apple Inc. Common Stock');
45
+ expect(appleHolding.acquisitionPrice).toBe(150.75);
46
+ expect(appleHolding.price).toBe(0.855);
47
+ expect(appleHolding.currency).toBe('%');
48
+ expect(appleHolding.amount).toBe(50);
49
+ expect(appleHolding.value).toBe(42.75);
50
+ expect(appleHolding.date).toEqual(new Date(2023, 10, 1, 14, 30, 0));
51
+ });
52
+ it('parses MT535 with @@ dividers and different data points', () => {
53
+ const input = ':16R:ADDINFO@@' +
54
+ 'EUR50000,25@@' +
55
+ ':16S:ADDINFO@@' +
56
+ ':16R:FIN@@' +
57
+ ':35B:ISIN DE0007164600/DE/716460SAP SE:@@' +
58
+ ':70E::HOLD//25STK2120,80+EUR@@' +
59
+ ':90B::UNKNOWNVALXEUR115,75:@@' +
60
+ ':93B::SOMESTATUS_25,:@@' +
61
+ ':98A::REGD//DTE20231215:@@' +
62
+ ':16S:FIN@@';
63
+ const parser = new Mt535Parser(input);
64
+ const statement = parser.parse();
65
+ expect(statement.totalValue).toBe(50000.25);
66
+ expect(statement.holdings).toHaveLength(1);
67
+ const holding = statement.holdings[0];
68
+ expect(holding.isin).toBe('DE0007164600');
69
+ expect(holding.wkn).toBe('716460');
70
+ expect(holding.name).toBe('SAP SE');
71
+ expect(holding.acquisitionPrice).toBe(120.8);
72
+ expect(holding.price).toBe(115.75);
73
+ expect(holding.currency).toBe('EUR');
74
+ expect(holding.amount).toBe(25);
75
+ expect(holding.value).toBe(2893.75);
76
+ expect(holding.date).toEqual(new Date(2023, 11, 15));
77
+ });
78
+ it('handles empty input', () => {
79
+ const parser = new Mt535Parser('');
80
+ const statement = parser.parse();
81
+ expect(statement.totalValue).toBeUndefined();
82
+ expect(statement.holdings).toHaveLength(0);
83
+ });
84
+ it('handles input without depot value', () => {
85
+ const input = ':16R:FIN\r\n' +
86
+ ':35B:ISIN DE0005190003/DE/519000SOME OTHER AG ST:\r\n' +
87
+ ':90B::PREFIX_11_CEUR100,50:\r\n' +
88
+ ':93B::QTY/AVALBLX100,:\r\n' +
89
+ ':98A::SETT//DTE20231101:\r\n' +
90
+ ':16S:FIN\r\n';
91
+ const parser = new Mt535Parser(input);
92
+ const statement = parser.parse();
93
+ expect(statement.totalValue).toBeUndefined();
94
+ expect(statement.holdings).toHaveLength(1);
95
+ expect(statement.holdings[0].isin).toBe('DE0005190003');
96
+ expect(statement.holdings[0].name).toBe('SOME OTHER AG ST');
97
+ expect(statement.holdings[0].price).toBe(100.5);
98
+ expect(statement.holdings[0].amount).toBe(100);
99
+ });
100
+ it('handles input without holdings', () => {
101
+ const input = ':16R:ADDINFO\r\n' + 'EUR75000,00\r\n' + ':16S:ADDINFO\r\n';
102
+ const parser = new Mt535Parser(input);
103
+ const statement = parser.parse();
104
+ expect(statement.totalValue).toBe(75000.0);
105
+ expect(statement.holdings).toHaveLength(0);
106
+ });
107
+ it('handles holding with minimal data (only security identification)', () => {
108
+ const input = ':16R:FIN\r\n' +
109
+ ':35B:ISIN FR0000120271/FR/120271AIR LIQUIDE SA: \r\n' +
110
+ ':16S:FIN\r\n';
111
+ const parser = new Mt535Parser(input);
112
+ const statement = parser.parse();
113
+ expect(statement.holdings).toHaveLength(1);
114
+ const holding = statement.holdings[0];
115
+ expect(holding.isin).toBe('FR0000120271');
116
+ expect(holding.wkn).toBe('120271');
117
+ expect(holding.name).toBe('AIR LIQUIDE SA');
118
+ expect(holding.price).toBeUndefined();
119
+ expect(holding.amount).toBeUndefined();
120
+ expect(holding.value).toBeUndefined();
121
+ expect(holding.date).toBeUndefined();
122
+ });
123
+ it('calculates value correctly for percentage currency when price is from :90A', () => {
124
+ const input = ':16R:FIN\r\n' +
125
+ ':35B:ISIN US0378331005/US/037833Apple Inc. Common Stock:\r\n' +
126
+ ':90A::PRIC/RATE_X85,50:\r\n' +
127
+ ':93B::QTY/AVALBLX100,:\r\n' +
128
+ ':16S:FIN\r\n';
129
+ const parser = new Mt535Parser(input);
130
+ const statement = parser.parse();
131
+ const holding = statement.holdings[0];
132
+ expect(holding.currency).toBe('%');
133
+ expect(holding.price).toBe(0.855);
134
+ expect(holding.amount).toBe(100);
135
+ expect(holding.value).toBe(85.5);
136
+ });
137
+ it('correctly parses date and time for :98C', () => {
138
+ const input = ':16R:FIN\r\n' +
139
+ ':35B:ISIN DE000BASF111/DE/BASF11BASF SE:\r\n' +
140
+ ':98C::QUALIF20240115093045:\r\n' +
141
+ ':16S:FIN\r\n';
142
+ const parser = new Mt535Parser(input);
143
+ const statement = parser.parse();
144
+ const holding = statement.holdings[0];
145
+ expect(holding.date).toEqual(new Date(2024, 0, 15, 9, 30, 45));
146
+ });
147
+ it('correctly parses date for :98A (time defaults to 00:00:00)', () => {
148
+ const input = ':16R:FIN\r\n' +
149
+ ':35B:ISIN DE000BAY0017/DE/BAY001BAYER AG:\r\n' +
150
+ ':98A::SETT//DTE20231005:\r\n' +
151
+ ':16S:FIN\r\n';
152
+ const parser = new Mt535Parser(input);
153
+ const statement = parser.parse();
154
+ const holding = statement.holdings[0];
155
+ expect(holding.date).toEqual(new Date(2023, 9, 5, 0, 0, 0));
156
+ });
157
+ });
@@ -1,5 +1,6 @@
1
1
  import { StatementResponse } from './interactions/statementInteraction.js';
2
2
  import { AccountBalanceResponse } from './interactions/balanceInteraction.js';
3
+ import { PortfolioResponse } from './interactions/portfolioInteraction.js';
3
4
  import { FinTSConfig } from './config.js';
4
5
  import { TanMethod } from './tanMethod.js';
5
6
  import { InitResponse } from './interactions/initDialogInteraction.js';
@@ -79,6 +80,29 @@ export declare class FinTSClient {
79
80
  * @returns an account statements response containing an array of statements
80
81
  */
81
82
  getAccountStatementsWithTan(tanReference: string, tan?: string): Promise<StatementResponse>;
83
+ /**
84
+ * Checks if the bank supports fetching portfolio information in general or for the given account number when provided
85
+ * @param accountNumber when the account number is provided, checks if the account supports fetching of portfolio information
86
+ * @returns true if the bank (and account) supports fetching portfolio information
87
+ */
88
+ canGetPortfolio(accountNumber?: string): boolean;
89
+ /**
90
+ * Fetches the portfolio information for the given depot account number
91
+ * @param accountNumber - the depot account number to fetch the portfolio for, must be an account available in the config.bankingInformation.UPD.accounts
92
+ * @param currency - optional currency filter for the portfolio statement
93
+ * @param priceQuality - optional price quality filter ('1' for real-time, '2' for delayed)
94
+ * @param maxEntries - optional maximum number of entries to retrieve
95
+ * @returns a portfolio response containing holdings and total value
96
+ */
97
+ getPortfolio(accountNumber: string, currency?: string, priceQuality?: '1' | '2', maxEntries?: number): Promise<PortfolioResponse>;
98
+ /**
99
+ * Continues the portfolio fetching when a TAN is required
100
+ * @param tanReference The TAN reference provided in the first call's response
101
+
102
+ * @param tan The TAN entered by the user, can be omitted if a decoupled TAN method is used
103
+ * @returns a portfolio response containing holdings and total value
104
+ */
105
+ getPortfolioWithTan(tanReference: string, tan?: string): Promise<PortfolioResponse>;
82
106
  private startCustomerOrderInteraction;
83
107
  private continueCustomerInteractionWithTan;
84
108
  private initDialog;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAwB,MAAM,wCAAwC,CAAC;AACjG,OAAO,EAAE,sBAAsB,EAAsB,MAAM,sCAAsC,CAAC;AAClG,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAG3C,OAAO,EAAyB,YAAY,EAAE,MAAM,yCAAyC,CAAC;AAE9F,MAAM,WAAW,mBAAoB,SAAQ,YAAY;CAAG;AAE5D;;GAEG;AACH,qBAAa,WAAW;IAOJ,MAAM,EAAE,WAAW;IANtC,OAAO,CAAC,wBAAwB,CAA0C;IAE1E;;;OAGG;gBACgB,MAAM,EAAE,WAAW;IAUtC;;;;OAIG;IACH,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS;IAK/C;;;OAGG;IACH,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAI1C;;;OAGG;IACG,WAAW,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAwBjD;;;;;OAKG;IACG,kBAAkB,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAI1F;;;;OAIG;IACH,oBAAoB,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO;IAMrD;;;;OAIG;IACG,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAI/E;;;;;OAKG;IACG,wBAAwB,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAInG;;;;OAIG;IACH,uBAAuB,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO;IAMxD;;;;;;OAMG;IACG,oBAAoB,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAIrG;;;;;OAKG;IACG,2BAA2B,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;YAInF,6BAA6B;YAqB7B,kCAAkC;YA0ClC,UAAU;CAcxB"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAwB,MAAM,wCAAwC,CAAC;AACjG,OAAO,EAAE,sBAAsB,EAAsB,MAAM,sCAAsC,CAAC;AAClG,OAAO,EAAE,iBAAiB,EAAwB,MAAM,wCAAwC,CAAC;AACjG,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAI3C,OAAO,EAAyB,YAAY,EAAE,MAAM,yCAAyC,CAAC;AAE9F,MAAM,WAAW,mBAAoB,SAAQ,YAAY;CAAG;AAE5D;;GAEG;AACH,qBAAa,WAAW;IAOJ,MAAM,EAAE,WAAW;IANtC,OAAO,CAAC,wBAAwB,CAA0C;IAE1E;;;OAGG;gBACgB,MAAM,EAAE,WAAW;IAUtC;;;;OAIG;IACH,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS;IAK/C;;;OAGG;IACH,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAI1C;;;OAGG;IACG,WAAW,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAwBjD;;;;;OAKG;IACG,kBAAkB,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAI1F;;;;OAIG;IACH,oBAAoB,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO;IAMrD;;;;OAIG;IACG,iBAAiB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAI/E;;;;;OAKG;IACG,wBAAwB,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAInG;;;;OAIG;IACH,uBAAuB,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO;IAMxD;;;;;;OAMG;IACG,oBAAoB,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAIrG;;;;;OAKG;IACG,2BAA2B,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAIjG;;;;OAIG;IACH,eAAe,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO;IAMhD;;;;;;;OAOG;IACG,YAAY,CACjB,aAAa,EAAE,MAAM,EACrB,QAAQ,CAAC,EAAE,MAAM,EACjB,YAAY,CAAC,EAAE,GAAG,GAAG,GAAG,EACxB,UAAU,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,iBAAiB,CAAC;IAM7B;;;;;;OAMG;IACG,mBAAmB,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC;YAI3E,6BAA6B;YAqB7B,kCAAkC;YA0ClC,UAAU;CAcxB"}
@@ -1 +1 @@
1
- {"version":3,"file":"dialog.d.ts","sourceRoot":"","sources":["../../src/dialog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAyC,OAAO,EAAE,MAAM,cAAc,CAAC;AAQ9E,OAAO,EAAE,cAAc,EAAE,wBAAwB,EAAE,MAAM,uCAAuC,CAAC;AAEjG,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAC;AAE9F,qBAAa,MAAM;IAOC,MAAM,EAAE,WAAW;IANtC,QAAQ,EAAE,MAAM,CAAO;IACvB,iBAAiB,SAAK;IACtB,aAAa,UAAS;IACtB,QAAQ,UAAS;IACjB,UAAU,EAAE,UAAU,CAAC;gBAEJ,MAAM,EAAE,WAAW;IAQhC,UAAU,CAAC,WAAW,EAAE,qBAAqB,GAAG,OAAO,CAAC,YAAY,CAAC;IA0DrE,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;IAuCvB,6BAA6B,CAAC,eAAe,SAAS,cAAc,EACzE,WAAW,EAAE,wBAAwB,GACnC,OAAO,CAAC,eAAe,CAAC;IA+FrB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAuDjG,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,aAAa;CAOrB"}
1
+ {"version":3,"file":"dialog.d.ts","sourceRoot":"","sources":["../../src/dialog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAyC,OAAO,EAAE,MAAM,cAAc,CAAC;AAQ9E,OAAO,EAAE,cAAc,EAAE,wBAAwB,EAAE,MAAM,uCAAuC,CAAC;AACjG,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAC;AAE9F,qBAAa,MAAM;IAOC,MAAM,EAAE,WAAW;IANtC,QAAQ,EAAE,MAAM,CAAO;IACvB,iBAAiB,SAAK;IACtB,aAAa,UAAS;IACtB,QAAQ,UAAS;IACjB,UAAU,EAAE,UAAU,CAAC;gBAEJ,MAAM,EAAE,WAAW;IAQhC,UAAU,CAAC,WAAW,EAAE,qBAAqB,GAAG,OAAO,CAAC,YAAY,CAAC;IA0DrE,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;IAuCvB,6BAA6B,CAAC,eAAe,SAAS,cAAc,EACzE,WAAW,EAAE,wBAAwB,GACnC,OAAO,CAAC,eAAe,CAAC;IA+FrB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAuDjG,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,aAAa;CAGrB"}
@@ -3,6 +3,7 @@ export * from './config.js';
3
3
  export { ClientResponse } from './interactions/customerInteraction.js';
4
4
  export { AccountBalanceResponse } from './interactions/balanceInteraction.js';
5
5
  export { StatementResponse } from './interactions/statementInteraction.js';
6
+ export { PortfolioResponse } from './interactions/portfolioInteraction.js';
6
7
  export * from './segment.js';
7
8
  export * from './message.js';
8
9
  export * from './dialog.js';
@@ -14,4 +15,5 @@ export * from './accountBalance.js';
14
15
  export * from './bpd.js';
15
16
  export * from './upd.js';
16
17
  export * from './mt940parser.js';
18
+ export * from './mt535parser.js';
17
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAIA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AACvE,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAIA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AACvE,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { ClientResponse, CustomerOrderInteraction } from './customerInteraction.js';
2
+ import { FinTSConfig } from '../config.js';
3
+ import { Message } from '../message.js';
4
+ import { Segment } from '../segment.js';
5
+ import { StatementOfHoldings, Holding } from '../mt535parser.js';
6
+ /**
7
+ * Represents a single holding within a stock portfolio.
8
+ * This is an alias for the Holding interface from the MT535 parser.
9
+ */
10
+ export type PortfolioHolding = Holding;
11
+ /**
12
+ * Represents the structured portfolio data parsed from an MT535 message.
13
+ * This is an alias for the StatementOfHoldings interface from the MT535 parser.
14
+ */
15
+ export type ParsedPortfolioStatement = StatementOfHoldings;
16
+ export interface PortfolioResponse extends ClientResponse {
17
+ /**
18
+ * The parsed portfolio statement containing holdings and total value
19
+ */
20
+ portfolioStatement?: ParsedPortfolioStatement;
21
+ /**
22
+ * Raw MT535 data if parsing fails
23
+ */
24
+ rawMT535Data?: string;
25
+ }
26
+ /**
27
+ * Interaction for requesting and parsing stock portfolio information (HKWPD/HIWPD)
28
+ */
29
+ export declare class PortfolioInteraction extends CustomerOrderInteraction {
30
+ accountNumber: string;
31
+ private currency?;
32
+ private priceQuality?;
33
+ private maxEntries?;
34
+ private paginationMarker?;
35
+ constructor(accountNumber: string, currency?: string | undefined, priceQuality?: "1" | "2" | undefined, maxEntries?: number | undefined, paginationMarker?: string | undefined);
36
+ createSegments(config: FinTSConfig): Segment[];
37
+ handleResponse(response: Message, clientResponse: PortfolioResponse): void;
38
+ }
39
+ //# sourceMappingURL=portfolioInteraction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portfolioInteraction.d.ts","sourceRoot":"","sources":["../../../src/interactions/portfolioInteraction.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,wBAAwB,EACzB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAGxC,OAAO,EAAe,mBAAmB,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAE9E;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC;AAEvC;;;GAGG;AACH,MAAM,MAAM,wBAAwB,GAAG,mBAAmB,CAAC;AAE3D,MAAM,WAAW,iBAAkB,SAAQ,cAAc;IACvD;;OAEG;IACH,kBAAkB,CAAC,EAAE,wBAAwB,CAAC;IAC9C;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,wBAAwB;IAEvD,aAAa,EAAE,MAAM;IAC5B,OAAO,CAAC,QAAQ,CAAC;IACjB,OAAO,CAAC,YAAY,CAAC;IACrB,OAAO,CAAC,UAAU,CAAC;IACnB,OAAO,CAAC,gBAAgB,CAAC;gBAJlB,aAAa,EAAE,MAAM,EACpB,QAAQ,CAAC,oBAAQ,EACjB,YAAY,CAAC,uBAAW,EACxB,UAAU,CAAC,oBAAQ,EACnB,gBAAgB,CAAC,oBAAQ;IAKnC,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,EAAE;IA8B9C,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,cAAc,EAAE,iBAAiB,GAAG,IAAI;CAe3E"}
@@ -0,0 +1,44 @@
1
+ export interface StatementOfHoldings {
2
+ totalValue?: number;
3
+ currency?: string;
4
+ holdings: Holding[];
5
+ }
6
+ export interface Holding {
7
+ isin?: string;
8
+ wkn?: string;
9
+ name?: string;
10
+ amount?: number;
11
+ price?: number;
12
+ currency?: string;
13
+ value?: number;
14
+ acquisitionPrice?: number;
15
+ date?: Date;
16
+ }
17
+ export declare enum TokenType535 {
18
+ DepotValueBlock = "DepotValueBlock",
19
+ DepotValueCurrency = "DepotValueCurrency",
20
+ FinBlock = "FinBlock",
21
+ SecurityIdentification = "SecurityIdentification",
22
+ AcquisitionPrice = "AcquisitionPrice",
23
+ PriceBlock = "PriceBlock",
24
+ AmountBlock = "AmountBlock",
25
+ DateTimeBlock = "DateTimeBlock",
26
+ DateString = "DateString",
27
+ TimeString = "TimeString"
28
+ }
29
+ export declare class Mt535Parser {
30
+ private rawData;
31
+ private cleanedRawData;
32
+ constructor(rawData: string);
33
+ parse(): StatementOfHoldings;
34
+ private parseDepotValue;
35
+ private parseHoldings;
36
+ private parseHolding;
37
+ private parseSecurityIdentification;
38
+ private parseAcquisitionPrice;
39
+ private parsePrice;
40
+ private parseAmount;
41
+ private parseDateTime;
42
+ private parseDate;
43
+ }
44
+ //# sourceMappingURL=mt535parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mt535parser.d.ts","sourceRoot":"","sources":["../../src/mt535parser.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,IAAI,CAAC,EAAE,IAAI,CAAC;CACb;AAED,oBAAY,YAAY;IACtB,eAAe,oBAAoB;IACnC,kBAAkB,uBAAuB;IACzC,QAAQ,aAAa;IACrB,sBAAsB,2BAA2B;IACjD,gBAAgB,qBAAqB;IACrC,UAAU,eAAe;IACzB,WAAW,gBAAgB;IAC3B,aAAa,kBAAkB;IAC/B,UAAU,eAAe;IACzB,UAAU,eAAe;CAC1B;AAgBD,qBAAa,WAAW;IAGV,OAAO,CAAC,OAAO;IAF3B,OAAO,CAAC,cAAc,CAAS;gBAEX,OAAO,EAAE,MAAM;IAWnC,KAAK,IAAI,mBAAmB;IAc5B,OAAO,CAAC,eAAe;IAevB,OAAO,CAAC,aAAa;IAqBrB,OAAO,CAAC,YAAY;IA4BpB,OAAO,CAAC,2BAA2B;IAyBnC,OAAO,CAAC,qBAAqB;IAU7B,OAAO,CAAC,UAAU;IA8BlB,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,aAAa;IA8BrB,OAAO,CAAC,SAAS;CAgBlB"}
@@ -0,0 +1,22 @@
1
+ import { SegmentHeader } from '../segmentHeader.js';
2
+ import { Binary } from '../dataElements/Binary.js';
3
+ import { SegmentDefinition } from '../segmentDefinition.js';
4
+ export type HIWPDSegment = {
5
+ header: SegmentHeader;
6
+ /**
7
+ * Portfolio statement in S.W.I.F.T. format MT 535 or 571
8
+ */
9
+ portfolioStatement: string;
10
+ };
11
+ /**
12
+ * Geschäftsvorfälle: C.4.3.1 Kreditinstitutsrückmeldung
13
+ * Version: 6
14
+ */
15
+ export declare class HIWPD extends SegmentDefinition {
16
+ static Id: string;
17
+ static Version: number;
18
+ constructor();
19
+ version: number;
20
+ elements: Binary[];
21
+ }
22
+ //# sourceMappingURL=HIWPD.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HIWPD.d.ts","sourceRoot":"","sources":["../../../src/segments/HIWPD.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D,MAAM,MAAM,YAAY,GAAG;IAC1B,MAAM,EAAE,aAAa,CAAC;IACtB;;OAEG;IACH,kBAAkB,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF;;;GAGG;AACH,qBAAa,KAAM,SAAQ,iBAAiB;IAC3C,MAAM,CAAC,EAAE,SAAW;IACpB,MAAM,CAAC,OAAO,SAAK;;IAInB,OAAO,SAAiB;IACxB,QAAQ,WAA4C;CACpD"}
@@ -0,0 +1,43 @@
1
+ import { SegmentHeader } from '../segmentHeader.js';
2
+ import { Account, AccountGroup } from '../dataGroups/Account.js';
3
+ import { AlphaNumeric } from '../dataElements/AlphaNumeric.js';
4
+ import { Numeric } from '../dataElements/Numeric.js';
5
+ import { SegmentDefinition } from '../segmentDefinition.js';
6
+ export type HKWPDSegment = {
7
+ header: SegmentHeader;
8
+ /**
9
+ * Depot
10
+ */
11
+ depot: Account;
12
+ /**
13
+ * Optional: Currency of the portfolio statement
14
+ */
15
+ currency?: string;
16
+ /**
17
+ * Optional: Price quality
18
+ * 1 = Current prices (Realtime)
19
+ * 2 = Delayed prices (Delayed)
20
+ */
21
+ priceQuality?: '1' | '2';
22
+ /**
23
+ * Optional: Maximum number of entries
24
+ */
25
+ maxEntries?: number;
26
+ /**
27
+ * Pagination marker
28
+ * Optional: If a pagination marker was previously returned
29
+ */
30
+ paginationMarker?: string;
31
+ };
32
+ /**
33
+ * Geschäftsvorfälle: C.4.3.1 Depotaufstellung
34
+ * Version: 6
35
+ */
36
+ export declare class HKWPD extends SegmentDefinition {
37
+ static Id: string;
38
+ static Version: number;
39
+ constructor();
40
+ version: number;
41
+ elements: (AlphaNumeric | AccountGroup | Numeric)[];
42
+ }
43
+ //# sourceMappingURL=HKWPD.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HKWPD.d.ts","sourceRoot":"","sources":["../../../src/segments/HKWPD.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D,MAAM,MAAM,YAAY,GAAG;IAC1B,MAAM,EAAE,aAAa,CAAC;IACtB;;OAEG;IACH,KAAK,EAAE,OAAO,CAAC;IACf;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,YAAY,CAAC,EAAE,GAAG,GAAG,GAAG,CAAC;IACzB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF;;;GAGG;AACH,qBAAa,KAAM,SAAQ,iBAAiB;IAC3C,MAAM,CAAC,EAAE,SAAW;IACpB,MAAM,CAAC,OAAO,SAAK;;IAInB,OAAO,SAAiB;IACxB,QAAQ,4CAMN;CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/segments/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAmC5D,wBAAgB,gBAAgB,SAgC/B;AAED,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS,CAE9E"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/segments/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAqC5D,wBAAgB,gBAAgB,SAkC/B;AAED,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,MAAM,GACT,iBAAiB,GAAG,SAAS,CAE/B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=HIWPD.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HIWPD.test.d.ts","sourceRoot":"","sources":["../../../src/tests/HIWPD.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=HKWPD.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HKWPD.test.d.ts","sourceRoot":"","sources":["../../../src/tests/HKWPD.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=mt535parser.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mt535parser.test.d.ts","sourceRoot":"","sources":["../../../src/tests/mt535parser.test.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lib-fints",
3
- "version": "1.1.2",
3
+ "version": "1.2.1",
4
4
  "description": "Typescript/Javascript client library for Online-Banking via the FinTS 3.0 protocol with PIN/TAN",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
@@ -19,6 +19,11 @@
19
19
  }
20
20
  }
21
21
  },
22
+ "scripts": {
23
+ "build": "rm -rf ./dist && tsc",
24
+ "pack": "npm run build && npm pack --pack-destination ~/npm-repo",
25
+ "test": "vitest"
26
+ },
22
27
  "keywords": [
23
28
  "FinTS",
24
29
  "HBCI",
@@ -36,10 +41,5 @@
36
41
  "@types/node": "^20.10.6",
37
42
  "typescript": "^5.3.3",
38
43
  "vitest": "^1.6.0"
39
- },
40
- "scripts": {
41
- "build": "rm -rf ./dist && tsc",
42
- "pack": "npm run build && npm pack --pack-destination ~/npm-repo",
43
- "test": "vitest"
44
44
  }
45
- }
45
+ }
@@ -1,59 +0,0 @@
1
- import { Message } from './message.js';
2
- import * as https from 'https';
3
- export class HttpClientNode {
4
- url;
5
- debug;
6
- constructor(url, debug = false) {
7
- this.url = url;
8
- this.debug = debug;
9
- }
10
- async sendMessage(message) {
11
- const encodedMessage = message.encode();
12
- const requestBuffer = Buffer.from(encodedMessage).toString('base64');
13
- if (this.debug) {
14
- console.log('Request Message:\n' + message.toString());
15
- }
16
- const requestOptions = {
17
- method: 'POST',
18
- headers: { 'Content-Type': 'text/plain' },
19
- };
20
- return new Promise((resolve, reject) => {
21
- const req = https.request(this.url, requestOptions, (res) => {
22
- let data = '';
23
- res.on('data', (chunk) => {
24
- data += chunk;
25
- });
26
- res.on('end', () => {
27
- if (res.statusCode === 200) {
28
- const responseBuffer = Buffer.from(data, 'base64');
29
- const responseText = responseBuffer.toString('latin1');
30
- try {
31
- const customerOrderMessage = message;
32
- const responseMessage = Message.decode(responseText, customerOrderMessage.supportsPartedResponseSegments
33
- ? customerOrderMessage.orderResponseSegId
34
- : undefined);
35
- if (this.debug) {
36
- console.log('Response Message:\n' + responseMessage.toString(true) + '\n');
37
- }
38
- resolve(responseMessage);
39
- }
40
- catch (error) {
41
- console.error('Error decoding response message:', error);
42
- console.error('Response Message Content:\n', responseText.split("'").join('\n'));
43
- reject(error);
44
- }
45
- }
46
- else {
47
- reject(new Error(`Request failed with status code ${res.statusCode}: ${data}`));
48
- }
49
- });
50
- });
51
- req.on('error', (error) => {
52
- console.error('Request error:', error);
53
- reject(error);
54
- });
55
- req.write(requestBuffer);
56
- req.end();
57
- });
58
- }
59
- }
@@ -1,8 +0,0 @@
1
- import { CustomerMessage, Message } from './message.js';
2
- export declare class HttpClientNode {
3
- url: string;
4
- debug: boolean;
5
- constructor(url: string, debug?: boolean);
6
- sendMessage(message: CustomerMessage): Promise<Message>;
7
- }
8
- //# sourceMappingURL=httpClientNode.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"httpClientNode.d.ts","sourceRoot":"","sources":["../../src/httpClientNode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAwB,OAAO,EAAE,MAAM,cAAc,CAAC;AAG9E,qBAAa,cAAc;IACN,GAAG,EAAE,MAAM;IAAS,KAAK;gBAAzB,GAAG,EAAE,MAAM,EAAS,KAAK,UAAQ;IAE9C,WAAW,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC;CA2D9D"}