@coderoftime/fints 0.0.0-stage → 0.1.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/CHANGELOG.md +11 -0
- package/LICENSE +28 -0
- package/README.md +156 -2
- package/dist/core/account.d.ts +140 -0
- package/dist/core/account.d.ts.map +1 -0
- package/dist/core/account.js +174 -0
- package/dist/core/account.js.map +1 -0
- package/dist/core/amount.d.ts +62 -0
- package/dist/core/amount.d.ts.map +1 -0
- package/dist/core/amount.js +189 -0
- package/dist/core/amount.js.map +1 -0
- package/dist/core/balance.d.ts +54 -0
- package/dist/core/balance.d.ts.map +1 -0
- package/dist/core/balance.js +53 -0
- package/dist/core/balance.js.map +1 -0
- package/dist/core/bank-info.d.ts +49 -0
- package/dist/core/bank-info.d.ts.map +1 -0
- package/dist/core/bank-info.js +52 -0
- package/dist/core/bank-info.js.map +1 -0
- package/dist/core/client.d.ts +126 -0
- package/dist/core/client.d.ts.map +1 -0
- package/dist/core/client.js +2 -0
- package/dist/core/client.js.map +1 -0
- package/dist/core/config.d.ts +68 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +113 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/errors.d.ts +91 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +182 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/feedback.d.ts +69 -0
- package/dist/core/feedback.d.ts.map +1 -0
- package/dist/core/feedback.js +124 -0
- package/dist/core/feedback.js.map +1 -0
- package/dist/core/transaction.d.ts +98 -0
- package/dist/core/transaction.d.ts.map +1 -0
- package/dist/core/transaction.js +91 -0
- package/dist/core/transaction.js.map +1 -0
- package/dist/core/transport.d.ts +37 -0
- package/dist/core/transport.d.ts.map +1 -0
- package/dist/core/transport.js +92 -0
- package/dist/core/transport.js.map +1 -0
- package/dist/formats/camt.d.ts +12 -0
- package/dist/formats/camt.d.ts.map +1 -0
- package/dist/formats/camt.js +159 -0
- package/dist/formats/camt.js.map +1 -0
- package/dist/formats/mt940.d.ts +42 -0
- package/dist/formats/mt940.d.ts.map +1 -0
- package/dist/formats/mt940.js +297 -0
- package/dist/formats/mt940.js.map +1 -0
- package/dist/formats/readers.d.ts +11 -0
- package/dist/formats/readers.d.ts.map +1 -0
- package/dist/formats/readers.js +47 -0
- package/dist/formats/readers.js.map +1 -0
- package/dist/formats/sepa-purpose.d.ts +22 -0
- package/dist/formats/sepa-purpose.d.ts.map +1 -0
- package/dist/formats/sepa-purpose.js +68 -0
- package/dist/formats/sepa-purpose.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/session/app-approval.d.ts +60 -0
- package/dist/session/app-approval.d.ts.map +1 -0
- package/dist/session/app-approval.js +215 -0
- package/dist/session/app-approval.js.map +1 -0
- package/dist/session/bank-connection.d.ts +28 -0
- package/dist/session/bank-connection.d.ts.map +1 -0
- package/dist/session/bank-connection.js +48 -0
- package/dist/session/bank-connection.js.map +1 -0
- package/dist/session/dialog-runner.d.ts +34 -0
- package/dist/session/dialog-runner.d.ts.map +1 -0
- package/dist/session/dialog-runner.js +112 -0
- package/dist/session/dialog-runner.js.map +1 -0
- package/dist/session/login-guard.d.ts +73 -0
- package/dist/session/login-guard.d.ts.map +1 -0
- package/dist/session/login-guard.js +109 -0
- package/dist/session/login-guard.js.map +1 -0
- package/dist/session/parameter-data.d.ts +83 -0
- package/dist/session/parameter-data.d.ts.map +1 -0
- package/dist/session/parameter-data.js +68 -0
- package/dist/session/parameter-data.js.map +1 -0
- package/dist/session/pin-tan-client.d.ts +54 -0
- package/dist/session/pin-tan-client.d.ts.map +1 -0
- package/dist/session/pin-tan-client.js +153 -0
- package/dist/session/pin-tan-client.js.map +1 -0
- package/dist/session/pin-tan-dialog.d.ts +71 -0
- package/dist/session/pin-tan-dialog.d.ts.map +1 -0
- package/dist/session/pin-tan-dialog.js +124 -0
- package/dist/session/pin-tan-dialog.js.map +1 -0
- package/dist/session/protocol.d.ts +209 -0
- package/dist/session/protocol.d.ts.map +1 -0
- package/dist/session/protocol.js +75 -0
- package/dist/session/protocol.js.map +1 -0
- package/dist/session/response-handler.d.ts +48 -0
- package/dist/session/response-handler.d.ts.map +1 -0
- package/dist/session/response-handler.js +121 -0
- package/dist/session/response-handler.js.map +1 -0
- package/dist/session/sepa-accounts.d.ts +34 -0
- package/dist/session/sepa-accounts.d.ts.map +1 -0
- package/dist/session/sepa-accounts.js +96 -0
- package/dist/session/sepa-accounts.js.map +1 -0
- package/dist/session/session-setup.d.ts +72 -0
- package/dist/session/session-setup.d.ts.map +1 -0
- package/dist/session/session-setup.js +160 -0
- package/dist/session/session-setup.js.map +1 -0
- package/dist/session/session-state.d.ts +47 -0
- package/dist/session/session-state.d.ts.map +1 -0
- package/dist/session/session-state.js +147 -0
- package/dist/session/session-state.js.map +1 -0
- package/dist/session/tan-selection.d.ts +88 -0
- package/dist/session/tan-selection.d.ts.map +1 -0
- package/dist/session/tan-selection.js +178 -0
- package/dist/session/tan-selection.js.map +1 -0
- package/dist/session/user-prompts.d.ts +61 -0
- package/dist/session/user-prompts.d.ts.map +1 -0
- package/dist/session/user-prompts.js +164 -0
- package/dist/session/user-prompts.js.map +1 -0
- package/dist/tan/challenge.d.ts +114 -0
- package/dist/tan/challenge.d.ts.map +1 -0
- package/dist/tan/challenge.js +185 -0
- package/dist/tan/challenge.js.map +1 -0
- package/dist/tan/flicker.d.ts +36 -0
- package/dist/tan/flicker.d.ts.map +1 -0
- package/dist/tan/flicker.js +262 -0
- package/dist/tan/flicker.js.map +1 -0
- package/dist/tan/tan-image.d.ts +21 -0
- package/dist/tan/tan-image.d.ts.map +1 -0
- package/dist/tan/tan-image.js +41 -0
- package/dist/tan/tan-image.js.map +1 -0
- package/dist/tan/tan-models.d.ts +145 -0
- package/dist/tan/tan-models.d.ts.map +1 -0
- package/dist/tan/tan-models.js +148 -0
- package/dist/tan/tan-models.js.map +1 -0
- package/dist/util/async.d.ts +24 -0
- package/dist/util/async.d.ts.map +1 -0
- package/dist/util/async.js +49 -0
- package/dist/util/async.js.map +1 -0
- package/dist/util/bytes.d.ts +23 -0
- package/dist/util/bytes.d.ts.map +1 -0
- package/dist/util/bytes.js +136 -0
- package/dist/util/bytes.js.map +1 -0
- package/dist/util/call-context.d.ts +12 -0
- package/dist/util/call-context.d.ts.map +1 -0
- package/dist/util/call-context.js +29 -0
- package/dist/util/call-context.js.map +1 -0
- package/dist/util/dates.d.ts +62 -0
- package/dist/util/dates.d.ts.map +1 -0
- package/dist/util/dates.js +169 -0
- package/dist/util/dates.js.map +1 -0
- package/dist/util/errors.d.ts +9 -0
- package/dist/util/errors.d.ts.map +1 -0
- package/dist/util/errors.js +15 -0
- package/dist/util/errors.js.map +1 -0
- package/dist/util/immutable-map.d.ts +16 -0
- package/dist/util/immutable-map.d.ts.map +1 -0
- package/dist/util/immutable-map.js +38 -0
- package/dist/util/immutable-map.js.map +1 -0
- package/dist/util/json.d.ts +13 -0
- package/dist/util/json.d.ts.map +1 -0
- package/dist/util/json.js +44 -0
- package/dist/util/json.js.map +1 -0
- package/dist/util/text.d.ts +20 -0
- package/dist/util/text.d.ts.map +1 -0
- package/dist/util/text.js +78 -0
- package/dist/util/text.js.map +1 -0
- package/dist/util/xml.d.ts +35 -0
- package/dist/util/xml.d.ts.map +1 -0
- package/dist/util/xml.js +183 -0
- package/dist/util/xml.js.map +1 -0
- package/dist/v3/account-extension.d.ts +25 -0
- package/dist/v3/account-extension.d.ts.map +1 -0
- package/dist/v3/account-extension.js +176 -0
- package/dist/v3/account-extension.js.map +1 -0
- package/dist/v3/client.d.ts +24 -0
- package/dist/v3/client.d.ts.map +1 -0
- package/dist/v3/client.js +16 -0
- package/dist/v3/client.js.map +1 -0
- package/dist/v3/dialog.d.ts +78 -0
- package/dist/v3/dialog.d.ts.map +1 -0
- package/dist/v3/dialog.js +138 -0
- package/dist/v3/dialog.js.map +1 -0
- package/dist/v3/message.d.ts +72 -0
- package/dist/v3/message.d.ts.map +1 -0
- package/dist/v3/message.js +259 -0
- package/dist/v3/message.js.map +1 -0
- package/dist/v3/parameters.d.ts +79 -0
- package/dist/v3/parameters.d.ts.map +1 -0
- package/dist/v3/parameters.js +396 -0
- package/dist/v3/parameters.js.map +1 -0
- package/dist/v3/protocol.d.ts +68 -0
- package/dist/v3/protocol.d.ts.map +1 -0
- package/dist/v3/protocol.js +263 -0
- package/dist/v3/protocol.js.map +1 -0
- package/dist/v3/segments.d.ts +89 -0
- package/dist/v3/segments.d.ts.map +1 -0
- package/dist/v3/segments.js +283 -0
- package/dist/v3/segments.js.map +1 -0
- package/dist/v3/syntax.d.ts +76 -0
- package/dist/v3/syntax.d.ts.map +1 -0
- package/dist/v3/syntax.js +301 -0
- package/dist/v3/syntax.js.map +1 -0
- package/package.json +63 -4
- package/src/core/account.ts +250 -0
- package/src/core/amount.ts +209 -0
- package/src/core/balance.ts +84 -0
- package/src/core/bank-info.ts +66 -0
- package/src/core/client.ts +149 -0
- package/src/core/config.ts +141 -0
- package/src/core/errors.ts +203 -0
- package/src/core/feedback.ts +137 -0
- package/src/core/transaction.ts +156 -0
- package/src/core/transport.ts +114 -0
- package/src/formats/camt.ts +169 -0
- package/src/formats/mt940.ts +333 -0
- package/src/formats/readers.ts +56 -0
- package/src/formats/sepa-purpose.ts +75 -0
- package/src/index.ts +68 -0
- package/src/session/app-approval.ts +265 -0
- package/src/session/bank-connection.ts +51 -0
- package/src/session/dialog-runner.ts +119 -0
- package/src/session/login-guard.ts +135 -0
- package/src/session/parameter-data.ts +137 -0
- package/src/session/pin-tan-client.ts +186 -0
- package/src/session/pin-tan-dialog.ts +160 -0
- package/src/session/protocol.ts +311 -0
- package/src/session/response-handler.ts +130 -0
- package/src/session/sepa-accounts.ts +97 -0
- package/src/session/session-setup.ts +165 -0
- package/src/session/session-state.ts +170 -0
- package/src/session/tan-selection.ts +192 -0
- package/src/session/user-prompts.ts +174 -0
- package/src/tan/challenge.ts +252 -0
- package/src/tan/flicker.ts +270 -0
- package/src/tan/tan-image.ts +36 -0
- package/src/tan/tan-models.ts +247 -0
- package/src/util/async.ts +53 -0
- package/src/util/bytes.ts +135 -0
- package/src/util/call-context.ts +40 -0
- package/src/util/dates.ts +193 -0
- package/src/util/errors.ts +16 -0
- package/src/util/immutable-map.ts +47 -0
- package/src/util/json.ts +51 -0
- package/src/util/text.ts +82 -0
- package/src/util/xml.ts +172 -0
- package/src/v3/account-extension.ts +190 -0
- package/src/v3/client.ts +29 -0
- package/src/v3/dialog.ts +199 -0
- package/src/v3/message.ts +321 -0
- package/src/v3/parameters.ts +451 -0
- package/src/v3/protocol.ts +331 -0
- package/src/v3/segments.ts +331 -0
- package/src/v3/syntax.ts +307 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
## 0.1.0
|
|
2
|
+
|
|
3
|
+
First release: a TypeScript port of the FinTS 3.0 client of the Dart package `fints`.
|
|
4
|
+
|
|
5
|
+
- `FinTs3Client`: accounts, balances and transactions (camt.052 and MT940/MT942), with paging and SEPA account details.
|
|
6
|
+
- One-step and two-step TAN, PSD2 strong customer authentication and app approval (decoupled), timed by the bank's
|
|
7
|
+
parameters; TAN method and medium selection, chipTAN flicker codes and photoTAN images.
|
|
8
|
+
- Bank parameter data, customer system ID and the chosen TAN method in an exportable state string, in the layout of the
|
|
9
|
+
Dart package.
|
|
10
|
+
- A client never sends its PIN again once the bank has refused a login.
|
|
11
|
+
- Runs wherever `fetch`, Web Crypto and `TextDecoder` are available; no runtime dependencies.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Copyright 2026, the fints project authors.
|
|
2
|
+
|
|
3
|
+
Redistribution and use in source and binary forms, with or without
|
|
4
|
+
modification, are permitted provided that the following conditions are
|
|
5
|
+
met:
|
|
6
|
+
|
|
7
|
+
1. Redistributions of source code must retain the above copyright
|
|
8
|
+
notice, this list of conditions and the following disclaimer.
|
|
9
|
+
|
|
10
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
11
|
+
notice, this list of conditions and the following disclaimer in the
|
|
12
|
+
documentation and/or other materials provided with the distribution.
|
|
13
|
+
|
|
14
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
15
|
+
contributors may be used to endorse or promote products derived from
|
|
16
|
+
this software without specific prior written permission.
|
|
17
|
+
|
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
19
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
20
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
21
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
22
|
+
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
23
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
24
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
25
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
26
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
27
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,157 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @coderoftime/fints
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A FinTS 3.0 client for German banks, written in TypeScript, without runtime dependencies. It reads accounts, balances and transactions with PIN/TAN, including PSD2 strong customer authentication and app approval (pushTAN, SecureGo plus and the like).
|
|
4
|
+
|
|
5
|
+
It is a port of the Dart package [`fints`](https://pub.dev/packages/fints) and behaves the same: same session logic, same safeguards, same tests. FinTS 4.1, which hardly any bank offers, is not part of the port.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **PIN/TAN** with the one-step method (`999`) and two-step methods of process variant 2 (`HKTAN` process 4, then 2 or `S`).
|
|
10
|
+
- **Strong customer authentication** at login and per transaction where the bank asks for it, including exemptions (`3076`).
|
|
11
|
+
- **App approval** (decoupled), polled by the bank's own parameters; the client stops when the app is closed or the user cancels.
|
|
12
|
+
- **TAN media** lookup and selection, **chipTAN flicker codes** (HHD 1.3 and 1.4, as frames to animate) and **photoTAN/QR images** (as `data:` URLs).
|
|
13
|
+
- **Balances** (`HKSAL` 6–8) and **transactions** as camt.052 (`HKCAZ`) or MT940/MT942 (`HKKAZ` 6–7), with automatic paging.
|
|
14
|
+
- **IBAN and BIC** from the SEPA account list (`HKSPA`), and the account extension of the user parameter data.
|
|
15
|
+
- **Safe by default**: the PIN is never sent again after the bank refused a login, so a retrying app cannot lock the access; only what the bank's parameter data permit is sent; PIN and TAN are masked in every log.
|
|
16
|
+
- **Saved sessions**: bank and user parameters, customer system ID and the chosen TAN method in one state string, which spares the setup steps at the next start.
|
|
17
|
+
- Parsers for MT940 (German structured `:86:` fields, SEPA keywords) and camt.052/053.
|
|
18
|
+
|
|
19
|
+
## Requirements
|
|
20
|
+
|
|
21
|
+
1. **A FinTS product registration.** German banks only accept registered products. Register for free with the Deutsche Kreditwirtschaft at <https://www.fints.org/de/hersteller/produktregistrierung> and pass the ID as `productId`. Keep it out of your repository.
|
|
22
|
+
2. **Your bank's FinTS URL and bank code (BLZ)**, for example from the DK institute list.
|
|
23
|
+
3. **A runtime with `fetch`, Web Crypto and `TextDecoder`**: Node.js 22.12 or later, Deno, Bun, or a browser behind a server of your own (see [Using it with Vue](#using-it-with-vue)). The type declarations need TypeScript 5.6 or later.
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { FinTs3Client, TanAnswer } from '@coderoftime/fints';
|
|
29
|
+
|
|
30
|
+
const client = new FinTs3Client({
|
|
31
|
+
config: {
|
|
32
|
+
url: 'https://fints.example-bank.de/fints',
|
|
33
|
+
bankCode: '12345678',
|
|
34
|
+
userId: 'my-login',
|
|
35
|
+
productId: process.env.FINTS_PRODUCT_ID!,
|
|
36
|
+
productVersion: '1.0',
|
|
37
|
+
},
|
|
38
|
+
pin,
|
|
39
|
+
options: {
|
|
40
|
+
state: savedState, // from a previous session, or undefined
|
|
41
|
+
tanMethodSelector: (methods) => pickMethod(methods),
|
|
42
|
+
tanMediumSelector: (media) => pickMedium(media),
|
|
43
|
+
onBankMessage: (message) => showNotice(message.subject, message.text),
|
|
44
|
+
onWarning: (warning) => showNotice(warning.code, warning.text), // banks want code and text shown
|
|
45
|
+
tanCallback: async (challenge) => {
|
|
46
|
+
if (challenge.isDecoupled) {
|
|
47
|
+
// Close the prompt when challenge.resolved settles; offer "approved" only if challenge.manualConfirmation.
|
|
48
|
+
const confirmed = await showApprovalPrompt(challenge);
|
|
49
|
+
return confirmed ? TanAnswer.approved : TanAnswer.cancelled;
|
|
50
|
+
}
|
|
51
|
+
// Show challenge.plainMessage, challenge.image?.toDataUrl() or challenge.flickerCode?.frames().
|
|
52
|
+
const tan = await askForTan(challenge);
|
|
53
|
+
return tan === undefined ? TanAnswer.cancelled : TanAnswer.tan(tan);
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const accounts = await client.getAccounts();
|
|
60
|
+
const balance = await client.getBalance(accounts[0]!);
|
|
61
|
+
const transactions = await client.getTransactions(accounts[0]!, { from: new Date(2024, 0, 1) });
|
|
62
|
+
} finally {
|
|
63
|
+
saveEncrypted(client.state); // no PIN or TAN, but account data
|
|
64
|
+
await client.close();
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
All calls share one bank dialog, so the user approves once per session. Calls run one after another; a call from inside a callback would wait for itself, so on Node.js, Deno and Bun it fails with `FinTsStateError` (in browsers it is not detected).
|
|
69
|
+
|
|
70
|
+
Calendar dates, such as `Transaction.bookingDate`, are `Date`s at local midnight, of which only the day counts. Moments, such as `Balance.bookedAsOfTime`, are exact; the bank states them in German time.
|
|
71
|
+
|
|
72
|
+
## Using it with Vue
|
|
73
|
+
|
|
74
|
+
**A web page cannot talk to a bank's FinTS server directly.** Browsers only let a page call another origin if the server allows it (CORS), and bank servers do not. So some server of yours always sits between the browser and the bank, and **that server sees the PIN**: FinTS PIN/TAN messages carry it in plain text inside TLS. It must be a server you and your users trust.
|
|
75
|
+
|
|
76
|
+
There are three ways to set it up:
|
|
77
|
+
|
|
78
|
+
1. **The client runs on your server** (Nuxt server routes, Express, …). The Vue app calls your own API; the server keeps one `FinTs3Client` per session and passes the TAN challenges to the browser, for example with a pending request the browser answers. This keeps the bank traffic and the product ID on the server.
|
|
79
|
+
2. **The client runs in the browser, through a relay.** The library runs in the Vue app, so TAN prompts are plain local UI, and a small endpoint of yours forwards each message to the bank. Pass a transport that posts to it:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import type { FinTsTransport } from '@coderoftime/fints';
|
|
83
|
+
|
|
84
|
+
const relay: FinTsTransport = {
|
|
85
|
+
async post(url, body, contentType) {
|
|
86
|
+
const response = await fetch('/api/fints-relay', {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
headers: { 'Content-Type': contentType, 'X-FinTS-URL': url.href },
|
|
89
|
+
body,
|
|
90
|
+
});
|
|
91
|
+
if (!response.ok) throw new Error(`Relay: HTTP ${response.status}`);
|
|
92
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
93
|
+
},
|
|
94
|
+
close() {},
|
|
95
|
+
};
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The relay must forward only to the bank URLs of the DK institute list, never to any URL a caller names, or it is an open proxy. The product ID ends up in the page; anyone can read it there.
|
|
99
|
+
|
|
100
|
+
3. **The app is not a web page**: Electron, Tauri or Capacitor can send requests without CORS. Pass a `FinTsTransport` that uses the platform's HTTP client, or `HttpTransport` where `fetch` is not bound by CORS.
|
|
101
|
+
|
|
102
|
+
A TAN prompt in Vue fits the callback as a promise the dialog resolves:
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { ref } from 'vue';
|
|
106
|
+
import { TanAnswer, type TanChallenge } from '@coderoftime/fints';
|
|
107
|
+
|
|
108
|
+
export const pending = ref<{ challenge: TanChallenge; answer: (a: TanAnswer) => void }>();
|
|
109
|
+
|
|
110
|
+
export function tanCallback(challenge: TanChallenge): Promise<TanAnswer> {
|
|
111
|
+
return new Promise((answer) => {
|
|
112
|
+
pending.value = { challenge, answer };
|
|
113
|
+
void challenge.resolved?.then(() => (pending.value = undefined)); // the bank confirmed the app approval
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
// The dialog component shows pending.value.challenge and calls pending.value.answer(TanAnswer.tan(input)),
|
|
117
|
+
// TanAnswer.approved or TanAnswer.cancelled, then clears pending.value.
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Errors
|
|
121
|
+
|
|
122
|
+
| Error | Meaning |
|
|
123
|
+
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
124
|
+
| `FinTsPinError` | Wrong or blocked PIN. The client sends no PIN any more; create a new client to try again. |
|
|
125
|
+
| `FinTsTanError` | Wrong, expired or used TAN. The session stays usable. |
|
|
126
|
+
| `FinTsTanCancelledError` | The user cancelled the TAN or the app approval. |
|
|
127
|
+
| `FinTsDecoupledTimeoutError` | The app approval did not come within the bank's limit. |
|
|
128
|
+
| `FinTsLoginDataChangedError` | The bank issued new login data (`3072`); `loginName` tells it. |
|
|
129
|
+
| `FinTsDialogAbortedError` | The bank ended the dialog; the next call logs in anew. |
|
|
130
|
+
| `FinTsBankError` | Any other rejection; `feedback` holds the return codes. After a refused login, later calls fail with the same error without contacting the bank. |
|
|
131
|
+
| `FinTsUnsupportedError` | The bank or the user's parameter data do not offer the transaction; nothing was sent. |
|
|
132
|
+
| `FinTsProtocolError` | The bank's answer could not be read. |
|
|
133
|
+
| `FinTsTransportError` | No connection, an HTTP error or a timeout. |
|
|
134
|
+
| `FinTsStateError` | The client was closed, or called from inside one of its callbacks. |
|
|
135
|
+
| `TypeError` | An invalid argument, such as an empty PIN or a period that ends before it starts; the message never contains the PIN. |
|
|
136
|
+
|
|
137
|
+
The client never sends a message larger than the bank's maximum (`BankInfo.maxMessageSize`, in kilobytes), and never repeats a call by itself: the bank's answer cannot tell whether an order went through.
|
|
138
|
+
|
|
139
|
+
## Differences from the Dart package
|
|
140
|
+
|
|
141
|
+
- Errors are named `…Error` instead of `…Exception`; invalid arguments are `TypeError`s, malformed data `FormatError`s.
|
|
142
|
+
- `getTransactions(account, { from, to, format })` takes an options object; enums are string unions such as `'mt940'`.
|
|
143
|
+
- The polling parameters of `TanMethod` are in seconds: `decoupledFirstPollDelaySeconds`, `decoupledPollIntervalSeconds`.
|
|
144
|
+
- `HttpTransport` takes a `fetch` and `timeoutMs`; `TanImage.toDataUrl()` is new.
|
|
145
|
+
- The saved state has the same layout, so a state saved by one package can be restored by the other.
|
|
146
|
+
|
|
147
|
+
## Development
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
npm install
|
|
151
|
+
npm run check # type check, lint, format check and tests
|
|
152
|
+
npm run build
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
BSD 3-Clause, see [LICENSE](LICENSE). Commercial use is allowed; a product that ships the library must reproduce its copyright notice and license in its documentation or notices.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/** Account kind derived from the FinTS account type code. */
|
|
2
|
+
export type AccountCategory =
|
|
3
|
+
/** Current account (type codes 1 to 9). */
|
|
4
|
+
'checking'
|
|
5
|
+
/** Savings account (10 to 19). */
|
|
6
|
+
| 'savings'
|
|
7
|
+
/** Fixed-term deposit (20 to 29). */
|
|
8
|
+
| 'fixedDeposit'
|
|
9
|
+
/** Securities account (30 to 39). */
|
|
10
|
+
| 'securities'
|
|
11
|
+
/** Loan account (40 to 49). */
|
|
12
|
+
| 'loan'
|
|
13
|
+
/** Credit card account (50 to 59). */
|
|
14
|
+
| 'creditCard'
|
|
15
|
+
/** Fund deposit (60 to 69). */
|
|
16
|
+
| 'fund'
|
|
17
|
+
/** Home savings contract (70 to 79). */
|
|
18
|
+
| 'homeSavings'
|
|
19
|
+
/** Insurance (80 to 89). */
|
|
20
|
+
| 'insurance'
|
|
21
|
+
/** Any other type code. */
|
|
22
|
+
| 'other'
|
|
23
|
+
/** The bank states no type code. */
|
|
24
|
+
| 'unknown';
|
|
25
|
+
/** The category of the FinTS account type `code`, which is `undefined` if the bank states none. */
|
|
26
|
+
export declare function accountCategoryOf(code: number | undefined): AccountCategory;
|
|
27
|
+
/** What `Account` takes; the bank states the details it knows. */
|
|
28
|
+
export interface AccountInit {
|
|
29
|
+
accountNumber?: string | undefined;
|
|
30
|
+
subAccount?: string | undefined;
|
|
31
|
+
bankCode: string;
|
|
32
|
+
countryCode?: string | undefined;
|
|
33
|
+
iban?: string | undefined;
|
|
34
|
+
bic?: string | undefined;
|
|
35
|
+
customerId?: string | undefined;
|
|
36
|
+
currency?: string | undefined;
|
|
37
|
+
ownerName?: string | undefined;
|
|
38
|
+
productName?: string | undefined;
|
|
39
|
+
typeCode?: number | undefined;
|
|
40
|
+
allowedTransactions?: readonly string[] | undefined;
|
|
41
|
+
allowedTransactionsComplete?: boolean | undefined;
|
|
42
|
+
dataStatus?: AccountDataStatus | undefined;
|
|
43
|
+
}
|
|
44
|
+
/** A bank account as reported by the user parameter data or the SEPA account query. */
|
|
45
|
+
export declare class Account {
|
|
46
|
+
/** National account number. */
|
|
47
|
+
readonly accountNumber: string | undefined;
|
|
48
|
+
/** Sub-account characteristic (Unterkontomerkmal). */
|
|
49
|
+
readonly subAccount: string | undefined;
|
|
50
|
+
/** German bank code (BLZ) of the bank holding the account. */
|
|
51
|
+
readonly bankCode: string;
|
|
52
|
+
/** Numeric country code of the bank, `280` for Germany. */
|
|
53
|
+
readonly countryCode: string;
|
|
54
|
+
/** International bank account number. */
|
|
55
|
+
readonly iban: string | undefined;
|
|
56
|
+
/** Bank identifier code of the bank holding the account. */
|
|
57
|
+
readonly bic: string | undefined;
|
|
58
|
+
/** Customer ID the account is assigned to; may differ from the login name. */
|
|
59
|
+
readonly customerId: string | undefined;
|
|
60
|
+
/** Currency of the account, for example `EUR`. */
|
|
61
|
+
readonly currency: string | undefined;
|
|
62
|
+
/** Name of the account holder. */
|
|
63
|
+
readonly ownerName: string | undefined;
|
|
64
|
+
/** The bank's name for the account, for example `Girokonto`. */
|
|
65
|
+
readonly productName: string | undefined;
|
|
66
|
+
/** Raw FinTS account type (1–99); see `category`. */
|
|
67
|
+
readonly typeCode: number | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* The transactions the bank lists as permitted for this account, by their segment IDs, such as `HKSAL`.
|
|
70
|
+
*
|
|
71
|
+
* The client applies them itself: `getBalance` and `getTransactions` throw `FinTsUnsupportedError`, without asking
|
|
72
|
+
* the bank, where the parameter data rule the query out.
|
|
73
|
+
*/
|
|
74
|
+
readonly allowedTransactions: readonly string[];
|
|
75
|
+
/**
|
|
76
|
+
* Whether `allowedTransactions` is complete: the bank permits nothing it does not list (FinTS "UPD-Verwendung" 0).
|
|
77
|
+
* Otherwise the bank's lists say nothing about a transaction they leave out, and only the bank can tell.
|
|
78
|
+
*/
|
|
79
|
+
readonly allowedTransactionsComplete: boolean;
|
|
80
|
+
/** What the bank tells about the data it holds for the account, where it does. */
|
|
81
|
+
readonly dataStatus: AccountDataStatus | undefined;
|
|
82
|
+
constructor(init: AccountInit);
|
|
83
|
+
/** The kind of account, derived from `typeCode`. */
|
|
84
|
+
get category(): AccountCategory;
|
|
85
|
+
/**
|
|
86
|
+
* Whether `other` is the same account: by IBAN when both have one, otherwise by account number, sub-account and
|
|
87
|
+
* bank.
|
|
88
|
+
*
|
|
89
|
+
* Account numbers are compared without leading zeros, since banks pad them differently. An empty bank or country
|
|
90
|
+
* code, as of a balance record without bank identification, matches any.
|
|
91
|
+
*/
|
|
92
|
+
matches(other: Account): boolean;
|
|
93
|
+
/** This account with the given details replaced; a detail that is `undefined` stays as it is. */
|
|
94
|
+
copyWith(changes: Omit<Partial<AccountInit>, 'bankCode' | 'countryCode'>): Account;
|
|
95
|
+
/**
|
|
96
|
+
* The product name and the last digits of the IBAN or account number only, so that logs and crash reports do not
|
|
97
|
+
* collect account data.
|
|
98
|
+
*/
|
|
99
|
+
toString(): string;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* What the bank tells about the data it holds for an account, so that an app asks only for what has changed (FinTS 3.0
|
|
103
|
+
* user parameter data, "Erweiterung, kontobezogen", Formals E.3.1 and F).
|
|
104
|
+
*
|
|
105
|
+
* The rules are binding for an app that stores transactions; banks may refuse an app that ignores them:
|
|
106
|
+
* - Do not fetch the transactions again while the bank names the ID they were stored with (`mt940TransactionsId` or
|
|
107
|
+
* `camtTransactionsId`, for the format the app asked for, so pass `format` to `getTransactions` explicitly), or, in
|
|
108
|
+
* the older format, the timestamp `lastTransactions`.
|
|
109
|
+
* - Do not fetch them before `nextTransactionsAt`.
|
|
110
|
+
* - Do not ask for the balance separately where `transactionBalancesCurrent` says that the transactions carry the
|
|
111
|
+
* current one.
|
|
112
|
+
*/
|
|
113
|
+
export declare class AccountDataStatus {
|
|
114
|
+
/**
|
|
115
|
+
* IDs of the data the bank holds for the user, keyed by the segment ID of the query that fetches them, for example
|
|
116
|
+
* `HKKAZ` (transactions as MT940) or `HKCAZ` (as camt): the data have changed when the ID changes. An empty ID means
|
|
117
|
+
* that the user has no such data; a query the map leaves out, that the bank tells nothing about it.
|
|
118
|
+
*/
|
|
119
|
+
readonly inventory: ReadonlyMap<string, string>;
|
|
120
|
+
/**
|
|
121
|
+
* The timestamp of the last transactions the bank provided, in the older format of the extension (`umsltzt`);
|
|
122
|
+
* compared like an ID in `inventory`.
|
|
123
|
+
*/
|
|
124
|
+
readonly lastTransactions: string | undefined;
|
|
125
|
+
/** The moment before which the bank provides no new transactions. */
|
|
126
|
+
readonly nextTransactionsAt: Date | undefined;
|
|
127
|
+
/** Whether the balances the transactions carry are the current ones. */
|
|
128
|
+
readonly transactionBalancesCurrent: boolean;
|
|
129
|
+
constructor(init: {
|
|
130
|
+
inventory?: ReadonlyMap<string, string>;
|
|
131
|
+
lastTransactions?: string | undefined;
|
|
132
|
+
nextTransactionsAt?: Date | undefined;
|
|
133
|
+
transactionBalancesCurrent?: boolean;
|
|
134
|
+
});
|
|
135
|
+
/** The ID of the transactions the bank holds as MT940, its entry for `HKKAZ` in `inventory`. */
|
|
136
|
+
get mt940TransactionsId(): string | undefined;
|
|
137
|
+
/** The ID of the transactions the bank holds as camt, its entry for `HKCAZ` in `inventory`. */
|
|
138
|
+
get camtTransactionsId(): string | undefined;
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=account.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account.d.ts","sourceRoot":"","sources":["../../src/core/account.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { ImmutableMap } from '../util/immutable-map.js';
|
|
2
|
+
import { maskedId } from '../util/text.js';
|
|
3
|
+
import { defaultCountryCode } from './config.js';
|
|
4
|
+
const categoriesByTens = [
|
|
5
|
+
'checking',
|
|
6
|
+
'savings',
|
|
7
|
+
'fixedDeposit',
|
|
8
|
+
'securities',
|
|
9
|
+
'loan',
|
|
10
|
+
'creditCard',
|
|
11
|
+
'fund',
|
|
12
|
+
'homeSavings',
|
|
13
|
+
'insurance',
|
|
14
|
+
];
|
|
15
|
+
/** The category of the FinTS account type `code`, which is `undefined` if the bank states none. */
|
|
16
|
+
export function accountCategoryOf(code) {
|
|
17
|
+
if (code === undefined || code < 1)
|
|
18
|
+
return 'unknown';
|
|
19
|
+
return categoriesByTens[Math.floor(code / 10)] ?? 'other';
|
|
20
|
+
}
|
|
21
|
+
/** A bank account as reported by the user parameter data or the SEPA account query. */
|
|
22
|
+
export class Account {
|
|
23
|
+
/** National account number. */
|
|
24
|
+
accountNumber;
|
|
25
|
+
/** Sub-account characteristic (Unterkontomerkmal). */
|
|
26
|
+
subAccount;
|
|
27
|
+
/** German bank code (BLZ) of the bank holding the account. */
|
|
28
|
+
bankCode;
|
|
29
|
+
/** Numeric country code of the bank, `280` for Germany. */
|
|
30
|
+
countryCode;
|
|
31
|
+
/** International bank account number. */
|
|
32
|
+
iban;
|
|
33
|
+
/** Bank identifier code of the bank holding the account. */
|
|
34
|
+
bic;
|
|
35
|
+
/** Customer ID the account is assigned to; may differ from the login name. */
|
|
36
|
+
customerId;
|
|
37
|
+
/** Currency of the account, for example `EUR`. */
|
|
38
|
+
currency;
|
|
39
|
+
/** Name of the account holder. */
|
|
40
|
+
ownerName;
|
|
41
|
+
/** The bank's name for the account, for example `Girokonto`. */
|
|
42
|
+
productName;
|
|
43
|
+
/** Raw FinTS account type (1–99); see `category`. */
|
|
44
|
+
typeCode;
|
|
45
|
+
/**
|
|
46
|
+
* The transactions the bank lists as permitted for this account, by their segment IDs, such as `HKSAL`.
|
|
47
|
+
*
|
|
48
|
+
* The client applies them itself: `getBalance` and `getTransactions` throw `FinTsUnsupportedError`, without asking
|
|
49
|
+
* the bank, where the parameter data rule the query out.
|
|
50
|
+
*/
|
|
51
|
+
allowedTransactions;
|
|
52
|
+
/**
|
|
53
|
+
* Whether `allowedTransactions` is complete: the bank permits nothing it does not list (FinTS "UPD-Verwendung" 0).
|
|
54
|
+
* Otherwise the bank's lists say nothing about a transaction they leave out, and only the bank can tell.
|
|
55
|
+
*/
|
|
56
|
+
allowedTransactionsComplete;
|
|
57
|
+
/** What the bank tells about the data it holds for the account, where it does. */
|
|
58
|
+
dataStatus;
|
|
59
|
+
constructor(init) {
|
|
60
|
+
this.accountNumber = init.accountNumber;
|
|
61
|
+
this.subAccount = init.subAccount;
|
|
62
|
+
this.bankCode = init.bankCode;
|
|
63
|
+
this.countryCode = init.countryCode ?? defaultCountryCode;
|
|
64
|
+
this.iban = init.iban;
|
|
65
|
+
this.bic = init.bic;
|
|
66
|
+
this.customerId = init.customerId;
|
|
67
|
+
this.currency = init.currency;
|
|
68
|
+
this.ownerName = init.ownerName;
|
|
69
|
+
this.productName = init.productName;
|
|
70
|
+
this.typeCode = init.typeCode;
|
|
71
|
+
this.allowedTransactions = Object.freeze([...(init.allowedTransactions ?? [])]);
|
|
72
|
+
this.allowedTransactionsComplete = init.allowedTransactionsComplete ?? false;
|
|
73
|
+
this.dataStatus = init.dataStatus;
|
|
74
|
+
}
|
|
75
|
+
/** The kind of account, derived from `typeCode`. */
|
|
76
|
+
get category() {
|
|
77
|
+
return accountCategoryOf(this.typeCode);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Whether `other` is the same account: by IBAN when both have one, otherwise by account number, sub-account and
|
|
81
|
+
* bank.
|
|
82
|
+
*
|
|
83
|
+
* Account numbers are compared without leading zeros, since banks pad them differently. An empty bank or country
|
|
84
|
+
* code, as of a balance record without bank identification, matches any.
|
|
85
|
+
*/
|
|
86
|
+
matches(other) {
|
|
87
|
+
if (this.iban !== undefined && other.iban !== undefined)
|
|
88
|
+
return this.iban === other.iban;
|
|
89
|
+
const number = this.accountNumber?.replace(/^0+/, '');
|
|
90
|
+
return (number !== undefined &&
|
|
91
|
+
number === other.accountNumber?.replace(/^0+/, '') &&
|
|
92
|
+
(this.subAccount ?? '') === (other.subAccount ?? '') &&
|
|
93
|
+
sameOrUnstated(this.bankCode, other.bankCode) &&
|
|
94
|
+
sameOrUnstated(this.countryCode, other.countryCode));
|
|
95
|
+
}
|
|
96
|
+
/** This account with the given details replaced; a detail that is `undefined` stays as it is. */
|
|
97
|
+
copyWith(changes) {
|
|
98
|
+
return new Account({
|
|
99
|
+
accountNumber: changes.accountNumber ?? this.accountNumber,
|
|
100
|
+
subAccount: changes.subAccount ?? this.subAccount,
|
|
101
|
+
bankCode: this.bankCode,
|
|
102
|
+
countryCode: this.countryCode,
|
|
103
|
+
iban: changes.iban ?? this.iban,
|
|
104
|
+
bic: changes.bic ?? this.bic,
|
|
105
|
+
customerId: changes.customerId ?? this.customerId,
|
|
106
|
+
currency: changes.currency ?? this.currency,
|
|
107
|
+
ownerName: changes.ownerName ?? this.ownerName,
|
|
108
|
+
productName: changes.productName ?? this.productName,
|
|
109
|
+
typeCode: changes.typeCode ?? this.typeCode,
|
|
110
|
+
allowedTransactions: changes.allowedTransactions ?? this.allowedTransactions,
|
|
111
|
+
allowedTransactionsComplete: changes.allowedTransactionsComplete ?? this.allowedTransactionsComplete,
|
|
112
|
+
dataStatus: changes.dataStatus ?? this.dataStatus,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The product name and the last digits of the IBAN or account number only, so that logs and crash reports do not
|
|
117
|
+
* collect account data.
|
|
118
|
+
*/
|
|
119
|
+
toString() {
|
|
120
|
+
const id = this.iban ?? this.accountNumber;
|
|
121
|
+
const parts = [
|
|
122
|
+
...(id === undefined ? [] : [maskedId(id)]),
|
|
123
|
+
...(this.productName === undefined ? [] : [this.productName]),
|
|
124
|
+
];
|
|
125
|
+
return `Account(${parts.join(', ')})`;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function sameOrUnstated(a, b) {
|
|
129
|
+
return a === '' || b === '' || a === b;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* What the bank tells about the data it holds for an account, so that an app asks only for what has changed (FinTS 3.0
|
|
133
|
+
* user parameter data, "Erweiterung, kontobezogen", Formals E.3.1 and F).
|
|
134
|
+
*
|
|
135
|
+
* The rules are binding for an app that stores transactions; banks may refuse an app that ignores them:
|
|
136
|
+
* - Do not fetch the transactions again while the bank names the ID they were stored with (`mt940TransactionsId` or
|
|
137
|
+
* `camtTransactionsId`, for the format the app asked for, so pass `format` to `getTransactions` explicitly), or, in
|
|
138
|
+
* the older format, the timestamp `lastTransactions`.
|
|
139
|
+
* - Do not fetch them before `nextTransactionsAt`.
|
|
140
|
+
* - Do not ask for the balance separately where `transactionBalancesCurrent` says that the transactions carry the
|
|
141
|
+
* current one.
|
|
142
|
+
*/
|
|
143
|
+
export class AccountDataStatus {
|
|
144
|
+
/**
|
|
145
|
+
* IDs of the data the bank holds for the user, keyed by the segment ID of the query that fetches them, for example
|
|
146
|
+
* `HKKAZ` (transactions as MT940) or `HKCAZ` (as camt): the data have changed when the ID changes. An empty ID means
|
|
147
|
+
* that the user has no such data; a query the map leaves out, that the bank tells nothing about it.
|
|
148
|
+
*/
|
|
149
|
+
inventory;
|
|
150
|
+
/**
|
|
151
|
+
* The timestamp of the last transactions the bank provided, in the older format of the extension (`umsltzt`);
|
|
152
|
+
* compared like an ID in `inventory`.
|
|
153
|
+
*/
|
|
154
|
+
lastTransactions;
|
|
155
|
+
/** The moment before which the bank provides no new transactions. */
|
|
156
|
+
nextTransactionsAt;
|
|
157
|
+
/** Whether the balances the transactions carry are the current ones. */
|
|
158
|
+
transactionBalancesCurrent;
|
|
159
|
+
constructor(init) {
|
|
160
|
+
this.inventory = new ImmutableMap(init.inventory ?? []);
|
|
161
|
+
this.lastTransactions = init.lastTransactions;
|
|
162
|
+
this.nextTransactionsAt = init.nextTransactionsAt;
|
|
163
|
+
this.transactionBalancesCurrent = init.transactionBalancesCurrent ?? false;
|
|
164
|
+
}
|
|
165
|
+
/** The ID of the transactions the bank holds as MT940, its entry for `HKKAZ` in `inventory`. */
|
|
166
|
+
get mt940TransactionsId() {
|
|
167
|
+
return this.inventory.get('HKKAZ');
|
|
168
|
+
}
|
|
169
|
+
/** The ID of the transactions the bank holds as camt, its entry for `HKCAZ` in `inventory`. */
|
|
170
|
+
get camtTransactionsId() {
|
|
171
|
+
return this.inventory.get('HKCAZ');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=account.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account.js","sourceRoot":"","sources":["../../src/core/account.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** A money amount stored as an integer number of `scale`-digit minor units. */
|
|
2
|
+
export declare class Amount {
|
|
3
|
+
/** The amount in units of 10^-`scale` of the currency, negative for a debit: 1234 with scale 2 is 12.34. */
|
|
4
|
+
readonly minorUnits: number;
|
|
5
|
+
/** Number of decimal places `minorUnits` has, from 0 to 5. */
|
|
6
|
+
readonly scale: number;
|
|
7
|
+
/** ISO 4217 currency code, for example `EUR`. */
|
|
8
|
+
readonly currency: string;
|
|
9
|
+
/**
|
|
10
|
+
* An amount of `minorUnits` with `scale` decimal places; throws `RangeError` for a `scale` outside 0 to 5, and for
|
|
11
|
+
* `minorUnits` that are no integer or beyond 2^53 - 1 in either direction, about 90 trillion at two decimal places.
|
|
12
|
+
*/
|
|
13
|
+
constructor(minorUnits: number, scale: number, currency: string);
|
|
14
|
+
/**
|
|
15
|
+
* Parses a number as banks send it: digits with at most one decimal separator, `,` (FinTS 3.0, MT940) or `.` (camt),
|
|
16
|
+
* and no thousands separators.
|
|
17
|
+
*
|
|
18
|
+
* The amount has the decimal places of the `currency`'s minor unit (ISO 4217): two for most currencies, none for
|
|
19
|
+
* `JPY`, three for `KWD`. It has more where the value has more significant digits. So `100,` in `EUR` is `100.00`, in
|
|
20
|
+
* `JPY` `100`.
|
|
21
|
+
*
|
|
22
|
+
* Throws `FormatError` for anything else, so that `1.234,56` in bank data is an error, not a guess, and for more than
|
|
23
|
+
* five decimal places (trailing zeros aside) or more than 15 digits.
|
|
24
|
+
*/
|
|
25
|
+
static parseDecimal(value: string, currency: string): Amount;
|
|
26
|
+
/** The amount as a floating-point number, which cannot hold every decimal fraction exactly; do not calculate with it. */
|
|
27
|
+
get value(): number;
|
|
28
|
+
/** Whether the amount is below zero. */
|
|
29
|
+
get isNegative(): boolean;
|
|
30
|
+
/** Whether the amount is zero. */
|
|
31
|
+
get isZero(): boolean;
|
|
32
|
+
/** This amount with the opposite sign. */
|
|
33
|
+
negate(): Amount;
|
|
34
|
+
/** This amount without its sign. */
|
|
35
|
+
abs(): Amount;
|
|
36
|
+
/**
|
|
37
|
+
* The sum with `other`, at the larger of the two scales; throws `TypeError` for another currency and `RangeError` for
|
|
38
|
+
* a sum beyond what an amount holds.
|
|
39
|
+
*/
|
|
40
|
+
add(other: Amount): Amount;
|
|
41
|
+
/** The difference to `other`; throws `TypeError` for another currency. */
|
|
42
|
+
subtract(other: Amount): Amount;
|
|
43
|
+
/** Compares by value, whatever the scales; throws `TypeError` for another currency. */
|
|
44
|
+
compareTo(other: Amount): number;
|
|
45
|
+
/** Whether `other` is the same amount in the same currency, whatever the `scale`: 1.50 equals 1.500. */
|
|
46
|
+
equals(other: unknown): boolean;
|
|
47
|
+
/** Formats the amount without currency, for example `-12.50`. */
|
|
48
|
+
toDecimalString(decimalSeparator?: string): string;
|
|
49
|
+
toString(): string;
|
|
50
|
+
/** The minor units of both amounts at the larger scale, as `bigint`s, since the factor could leave the safe range. */
|
|
51
|
+
private atCommonScale;
|
|
52
|
+
private rescale;
|
|
53
|
+
private compareValue;
|
|
54
|
+
private checkCurrency;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* `amount` as a credit (unchanged) or a debit (negated), by the bank's credit/debit `mark`.
|
|
58
|
+
*
|
|
59
|
+
* Throws `FormatError` for any other `mark`, `undefined` included: a missing mark must not be read as a credit.
|
|
60
|
+
*/
|
|
61
|
+
export declare function withMark(amount: Amount, mark: string | undefined, credit: string, debit: string): Amount;
|
|
62
|
+
//# sourceMappingURL=amount.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"amount.d.ts","sourceRoot":"","sources":["../../src/core/amount.ts"],"names":[],"mappings":"AAmDA,+EAA+E;AAC/E,qBAAa,MAAM;IACjB,4GAA4G;IAC5G,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAE5B,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAE1B;;;OAGG;gBACS,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM;IAW/D;;;;;;;;;;OAUG;IACH,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;IAmB5D,yHAAyH;IACzH,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,wCAAwC;IACxC,IAAI,UAAU,IAAI,OAAO,CAExB;IAED,kCAAkC;IAClC,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,0CAA0C;IAC1C,MAAM,IAAI,MAAM;IAIhB,oCAAoC;IACpC,GAAG,IAAI,MAAM;IAIb;;;OAGG;IACH,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAU1B,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAI/B,uFAAuF;IACvF,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAKhC,wGAAwG;IACxG,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO;IAI/B,iEAAiE;IACjE,eAAe,CAAC,gBAAgB,SAAM,GAAG,MAAM;IAU/C,QAAQ,IAAI,MAAM;IAIlB,sHAAsH;IACtH,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,aAAa;CAItB;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAIxG"}
|