Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 19 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,17 @@ Finally you can start fetching balances or statements:
// for simplicity, use the first account
const account = syncResponse.bankingInformation.upd.bankAccounts[0];

// Account-specific methods accept either an account number or a BankAccount.
// Pass the account object when multiple accounts share the same number.

// fetch the current balance
const balanceResponse = await client.getAccountBalance(account.accountNumber);
const balanceResponse = await client.getAccountBalance(account);

// fetch all available statements
const statementResponse = await client.getAccountStatements(account.accountNumber);
const statementResponse = await client.getAccountStatements(account);

// or fetch portfolio from a securities account
client.getPortfolio(account.accountNumber);
client.getPortfolio(account);
```

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.
Expand All @@ -110,7 +113,7 @@ const rl = readline.createInterface({
output: process.stdout,
});

let response = await client.getAccountStatements(account.accountNumber);
let response = await client.getAccountStatements(account);

if (!response.success) {
return;
Expand Down Expand Up @@ -197,11 +200,11 @@ The following table shows all transactions supported by the FinTSClient interfac
| Transaction | Method | Description | FinTS Segment(s) | TAN Support | Account-Specific |
| -------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------- | -------------------------- | ----------- | ---------------- |
| **Synchronization** | `synchronize()` | Synchronizes bank and account information, updating config.bankingInformation | HKIDN, HKVVB, HKSYN, HKTAB | ✓ | ❌ |
| **Account Balance** | `getAccountBalance(accountNumber)` | Fetches the current balance for a specific account | HKSAL | ✓ | ✓ |
| **Account Statements** | `getAccountStatements(accountNumber, from?, to?)` | Fetches account transactions/statements for a date range (MT940 or CAMT format) | HKKAZ, HKCAZ | ✓ | ✓ |
| **Portfolio** | `getPortfolio(accountNumber, currency?, priceQuality?, maxEntries?)` | Fetches securities portfolio information for depot accounts | HKWPD | ✓ | ✓ |
| **Credit Card Statements** | `getCreditCardStatements(accountNumber, from?)` | Fetches credit card statements for credit card accounts | DKKKU | ✓ | ✓ |
| **Electronic Statements** | `getElectronicStatements(accountNumber, options?)` | Fetches the statement document from the electronic mailbox, usually a PDF | HKEKA | ✓ | ✓ |
| **Account Balance** | `getAccountBalance(account: AccountRef)` | Fetches the current balance for a specific account | HKSAL | ✓ | ✓ |
| **Account Statements** | `getAccountStatements(account: AccountRef, from?, to?)` | Fetches account transactions/statements for a date range (MT940 or CAMT format) | HKKAZ, HKCAZ | ✓ | ✓ |
| **Portfolio** | `getPortfolio(account: AccountRef, currency?, priceQuality?, maxEntries?)` | Fetches securities portfolio information for depot accounts | HKWPD | ✓ | ✓ |
| **Credit Card Statements** | `getCreditCardStatements(account: AccountRef, from?)` | Fetches credit card statements for credit card accounts | DKKKU | ✓ | ✓ |
| **Electronic Statements** | `getElectronicStatements(account: AccountRef, options?)` | Fetches the statement document from the electronic mailbox, usually a PDF | HKEKA | ✓ | ✓ |
| **TAN Method Selection** | `selectTanMethod(tanMethodId)` | Selects a TAN method by ID from available methods | - | ❌ | ❌ |
| **TAN Media Selection** | `selectTanMedia(tanMediaName)` | Selects a specific TAN media device by name | - | ❌ | ❌ |

Expand All @@ -211,11 +214,11 @@ For each account-specific transaction, the client provides corresponding `can*`

| Support Check Method | Purpose |
| -------------------------------------------- | --------------------------------------------------------------- |
| `canGetAccountBalance(accountNumber?)` | Checks if account balance fetching is supported |
| `canGetAccountStatements(accountNumber?)` | Checks if account statements fetching is supported (MT940/CAMT) |
| `canGetPortfolio(accountNumber?)` | Checks if portfolio information fetching is supported |
| `canGetCreditCardStatements(accountNumber?)` | Checks if credit card statements fetching is supported |
| `canGetElectronicStatements(accountNumber?)` | Checks if electronic account statements fetching is supported |
| `canGetAccountBalance(account?: AccountRef)` | Checks if account balance fetching is supported |
| `canGetAccountStatements(account?: AccountRef)` | Checks if account statements fetching is supported (MT940/CAMT) |
| `canGetPortfolio(account?: AccountRef)` | Checks if portfolio information fetching is supported |
| `canGetCreditCardStatements(account?: AccountRef)` | Checks if credit card statements fetching is supported |
| `canGetElectronicStatements(account?: AccountRef)` | Checks if electronic account statements fetching is supported |

### Transaction Parameters

Expand Down Expand Up @@ -246,12 +249,12 @@ if (config.isTransactionSupported('HKWPD')) {
}
```

#### `config.isAccountTransactionSupported(accountNumber: string, transId: string): boolean`
#### `config.isAccountTransactionSupported(account: AccountRef, transId: string): boolean`

Checks whether a specific transaction type is supported for a particular account.

```typescript
if (config.isAccountTransactionSupported('1234567890', 'HKWPD')) {
if (config.isAccountTransactionSupported(account, 'HKWPD')) {
console.log('Account supports portfolio requests');
}
```
Expand Down
20 changes: 20 additions & 0 deletions src/bankAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,26 @@ export type BankAccount = SepaAccount & {
allowedTransactions?: AllowedTransactions[];
};

/**
* How a caller names an account.
*
* An account number is not by itself unique: FinTS identifies an account by number
* *and* sub-account id together, and banks use that — a securities account and the
* current account it settles through commonly share a number and differ only in the
* sub-account id. Where that happens, a number alone cannot say which one is meant,
* so the account itself can be passed instead. Take it from
* `config.bankingInformation.upd.bankAccounts`.
*/
export type AccountRef = string | BankAccount;

/** How an account reference reads in an error message. */
export function describeAccount(account: AccountRef): string {
if (typeof account === 'string') return account;
return account.subAccountId
? `${account.accountNumber} (${account.subAccountId})`
: account.accountNumber;
}

export function finTsAccountTypeToEnum(accountType: number): AccountType {
if (accountType >= 1 && accountType <= 9) return AccountType.CheckingAccount;
if (accountType >= 10 && accountType <= 19) return AccountType.SavingsAccount;
Expand Down
83 changes: 41 additions & 42 deletions src/client.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { type AccountRef, describeAccount } from './bankAccount.js';
import { FinTSConfig } from './config.js';
import { Dialog } from './dialog.js';
import {
Expand Down Expand Up @@ -92,24 +93,22 @@ export class FinTSClient {

/**
* Checks if the bank supports fetching an account balance in general or for the given account number when provided
* @param accountNumber when the account number is provided, checks if the account supports fetching the balance
* @param account when the account number is provided, checks if the account supports fetching the balance
* @returns true if the bank (and account) supports fetching the account balance
*/
canGetAccountBalance(accountNumber?: string): boolean {
return accountNumber
? this.config.isAccountTransactionSupported(accountNumber, HKSAL.Id)
canGetAccountBalance(account?: AccountRef): boolean {
return account
? this.config.isAccountTransactionSupported(account, HKSAL.Id)
: this.config.isTransactionSupported(HKSAL.Id);
}

/**
* Fetches the account balance for the given account number
* @param accountNumber - the account number to fetch the balance for, must be an account available in the config.baningInformation.UPD.accounts
* @param account - the account number to fetch the balance for, must be an account available in the config.baningInformation.UPD.accounts
* @returns the account balance response
*/
async getAccountBalance(accountNumber: string): Promise<AccountBalanceResponse> {
const response = await this.startCustomerOrderInteraction(
new BalanceInteraction(accountNumber),
);
async getAccountBalance(account: AccountRef): Promise<AccountBalanceResponse> {
const response = await this.startCustomerOrderInteraction(new BalanceInteraction(account));
return response as AccountBalanceResponse;
}

Expand All @@ -132,15 +131,15 @@ export class FinTSClient {

/**
* Checks if the bank supports fetching account statements in general or for the given account number when provided
* @param accountNumber when the account number is provided, checks if the account supports fetching of statements
* @param account when the account number is provided, checks if the account supports fetching of statements
* @returns true if the bank (and account) supports fetching account statements
*/
canGetAccountStatements(accountNumber?: string): boolean {
if (accountNumber) {
canGetAccountStatements(account?: AccountRef): boolean {
if (account) {
// Check if either CAMT or MT940 is supported for this account
return (
this.config.isAccountTransactionSupported(accountNumber, HKCAZ.Id) ||
this.config.isAccountTransactionSupported(accountNumber, HKKAZ.Id)
this.config.isAccountTransactionSupported(account, HKCAZ.Id) ||
this.config.isAccountTransactionSupported(account, HKKAZ.Id)
);
} else {
// Check if either CAMT or MT940 is supported by the bank
Expand All @@ -152,36 +151,36 @@ export class FinTSClient {

/**
* Fetches the account statements for the given account number
* @param accountNumber - the account number to fetch the statements for, must be an account available in the config.baningInformation.UPD.accounts
* @param account - the account number to fetch the statements for, must be an account available in the config.baningInformation.UPD.accounts
* @param from - an optional start date of the period to fetch the statements for
* @param to - an optional end date of the period to fetch the statements for
* @param preferCamt - whether to prefer CAMT format over MT940 when both are supported (default: true)
* @returns an account statements response containing an array of statements
*/
async getAccountStatements(
accountNumber: string,
account: AccountRef,
from?: Date,
to?: Date,
preferCamt: boolean = true,
): Promise<StatementResponse> {
// Check what formats the bank supports
const camtSupported = this.config.isAccountTransactionSupported(accountNumber, 'HKCAZ');
const mt940Supported = this.config.isAccountTransactionSupported(accountNumber, 'HKKAZ');
const camtSupported = this.config.isAccountTransactionSupported(account, 'HKCAZ');
const mt940Supported = this.config.isAccountTransactionSupported(account, 'HKKAZ');

if (!camtSupported && !mt940Supported) {
throw Error(`Account ${accountNumber} does not support account statements`);
throw Error(`Account ${describeAccount(account)} does not support account statements`);
}

// Choose format based on support and preference
const useCAMT = (preferCamt && camtSupported) || (!mt940Supported && camtSupported);

if (useCAMT) {
return (await this.startCustomerOrderInteraction(
new StatementInteractionCAMT(accountNumber, from, to),
new StatementInteractionCAMT(account, from, to),
)) as StatementResponse;
} else {
return (await this.startCustomerOrderInteraction(
new StatementInteractionMT940(accountNumber, from, to),
new StatementInteractionMT940(account, from, to),
)) as StatementResponse;
}
}
Expand All @@ -205,31 +204,31 @@ export class FinTSClient {

/**
* Checks if the bank supports fetching portfolio information in general or for the given account number when provided
* @param accountNumber when the account number is provided, checks if the account supports fetching of portfolio information
* @param account when the account number is provided, checks if the account supports fetching of portfolio information
* @returns true if the bank (and account) supports fetching portfolio information
*/
canGetPortfolio(accountNumber?: string): boolean {
return accountNumber
? this.config.isAccountTransactionSupported(accountNumber, HKWPD.Id)
canGetPortfolio(account?: AccountRef): boolean {
return account
? this.config.isAccountTransactionSupported(account, HKWPD.Id)
: this.config.isTransactionSupported(HKWPD.Id);
}

/**
* Fetches the portfolio information for the given depot account number
* @param accountNumber - the depot account number to fetch the portfolio for, must be an account available in the config.bankingInformation.UPD.accounts
* @param account - the depot account number to fetch the portfolio for, must be an account available in the config.bankingInformation.UPD.accounts
* @param currency - optional currency filter for the portfolio statement
* @param priceQuality - optional price quality filter ('1' for real-time, '2' for delayed)
* @param maxEntries - optional maximum number of entries to retrieve
* @returns a portfolio response containing holdings and total value
*/
async getPortfolio(
accountNumber: string,
account: AccountRef,
currency?: string,
priceQuality?: '1' | '2',
maxEntries?: number,
): Promise<PortfolioResponse> {
return (await this.startCustomerOrderInteraction(
new PortfolioInteraction(accountNumber, currency, priceQuality, maxEntries),
new PortfolioInteraction(account, currency, priceQuality, maxEntries),
)) as PortfolioResponse;
}

Expand All @@ -250,26 +249,26 @@ export class FinTSClient {

/**
* Checks if the bank supports fetching credit card statements in general or for the given account number
* @param accountNumber when the account number is provided, checks if the account supports fetching of statements
* @param account when the account number is provided, checks if the account supports fetching of statements
* @returns true if the bank (and account) supports fetching credit card statements
*/
canGetCreditCardStatements(accountNumber?: string): boolean {
return accountNumber
? this.config.isAccountTransactionSupported(accountNumber, DKKKU.Id)
canGetCreditCardStatements(account?: AccountRef): boolean {
return account
? this.config.isAccountTransactionSupported(account, DKKKU.Id)
: this.config.isTransactionSupported(DKKKU.Id);
}

/**
* Fetches the credit card statements for the given account number
* @param accountNumber - the account number to fetch the statements for, must be a credit card account available
* @param account - the account number to fetch the statements for, must be a credit card account available
* in the config.baningInformation.UPD.accounts
* @param from - an optional start date of the period to fetch the statements for
* @param to - an optional end date of the period to fetch the statements for
* @returns an account statements response containing an array of statements
*/
async getCreditCardStatements(accountNumber: string, from?: Date): Promise<StatementResponse> {
async getCreditCardStatements(account: AccountRef, from?: Date): Promise<StatementResponse> {
return (await this.startCustomerOrderInteraction(
new CreditCardStatementInteraction(accountNumber, from),
new CreditCardStatementInteraction(account, from),
)) as StatementResponse;
}

Expand All @@ -292,12 +291,12 @@ export class FinTSClient {

/**
* Checks if the bank supports fetching electronic account statements in general or for the given account number
* @param accountNumber when the account number is provided, checks if the account supports fetching of electronic statements
* @param account when the account number is provided, checks if the account supports fetching of electronic statements
* @returns true if the bank (and account) supports fetching electronic account statements
*/
canGetElectronicStatements(accountNumber?: string): boolean {
return accountNumber
? this.config.isAccountTransactionSupported(accountNumber, HKEKA.Id)
canGetElectronicStatements(account?: AccountRef): boolean {
return account
? this.config.isAccountTransactionSupported(account, HKEKA.Id)
: this.config.isTransactionSupported(HKEKA.Id);
}

Expand All @@ -310,16 +309,16 @@ export class FinTSClient {
* fetch the next one. Banks that set `receiptRequired` in their HIEKAS parameters keep
* offering a statement until it has been acknowledged with its receipt.
*
* @param accountNumber - the account number to fetch the statement for, must be an account available in the config.bankingInformation.upd.accounts
* @param account - the account number to fetch the statement for, must be an account available in the config.bankingInformation.upd.accounts
* @param options - optional format, statement number and year, entry limit and offset
* @returns a response containing the statement documents and the offset of a waiting successor
*/
async getElectronicStatements(
accountNumber: string,
account: AccountRef,
options?: ElectronicStatementOptions,
): Promise<ElectronicStatementResponse> {
return (await this.startCustomerOrderInteraction(
new ElectronicStatementInteraction(accountNumber, options),
new ElectronicStatementInteraction(account, options),
)) as ElectronicStatementResponse;
}

Expand Down
Loading
Loading