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
117 changes: 117 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# FinTS Banking Protocol Library - AI Coding Guidelines

This TypeScript library implements the German FinTS 3.0 banking protocol for secure online banking communication with PIN/TAN authentication.

## Architecture Overview

### Core Components

- **`FinTSClient`**: Main API - handles account balances, statements, portfolios, and credit card data
- **`FinTSConfig`**: Configuration factory with two modes: `forFirstTimeUse()` and `fromBankingInformation()`
- **Segments**: FinTS protocol message units (see `src/segments/`) - each has request/response pairs (e.g., `HKSAL`/`HISAL`)
- **Interactions**: High-level transaction orchestrators (see `src/interactions/`) that combine multiple segments
- **Dialog**: Session management for multi-step TAN authentication flows

### Type System Pattern

The library implements a sophisticated encoding/decoding system:

- **DataElements** (`src/dataElements/`): Primitive types (Amount, Digits, Text, Binary, etc.)
- **DataGroups** (`src/dataGroups/`): Composite types (Account, Balance, Money, etc.)
- **Segments**: Protocol message definitions using DataElement/DataGroup compositions

### Critical Workflow: TAN Authentication

All transactions may require two-step TAN process:

```typescript
let response = await client.getAccountStatements(account);
if (response.requiresTan) {
response = await client.getAccountStatementsWithTan(response.tanReference, userTAN);
}
```

## Development Patterns

### Segment Registration

All segments must be registered in `src/segments/registry.ts` - the `registerSegments()` call in `index.ts` is critical for protocol functionality.

### DataElement maxCount Rules

**Critical encoding constraint**: DataElements with `maxCount > 1` can only be the **last element** in a DataGroup or segment. This is a FinTS protocol requirement for proper parsing.

**Correct patterns**:

```typescript
// ✅ DataGroup with maxCount=1, internal element has maxCount>1 and is last
new DataGroup('acceptedFormats', [new Text('format', 1, 99)], 1, 1);

// ✅ Direct element with maxCount>1 as last element in segment
elements = [
new Text('someField', 1, 1),
new Binary('transactions', 0, 10000), // Last element can have maxCount>1
];
```

**Incorrect patterns**:

```typescript
// ❌ DataElement with maxCount>1 not as last element
elements = [
new Text('formats', 1, 99), // maxCount>1 but not last!
new YesNo('someFlag', 1, 1), // This breaks parsing
];
```

### Testing Approach

- Use Vitest with mock patterns for `Dialog.prototype` methods
- Mock external HTTP communication, not internal protocol logic
- Test files follow `*.test.ts` naming in `src/tests/`
- Run tests: `pnpm test run`

### Error Handling

Bank communication errors come via `response.bankAnswers` array with numeric codes. Success/failure is indicated by `response.success` boolean, not exceptions.

### State Management

- `bankingInformation` contains session state (BPD/UPD) and should be persisted between sessions
- Check `bankingInformationUpdated` flag in responses and re-persist when true
- `systemId` assignment is permanent per bank relationship

## Key Integration Points

### Bank Communication Flow

1. **Initialization**: Create config → Initialize dialog → Send HKIDN/HKVVB/HKSYN segments
2. **TAN Method Selection**: Required before most transactions - available methods in BPD
3. **Transaction Execution**: Send business segment → Handle TAN challenge → Complete with TAN
4. **Session Cleanup**: Always call `dialog.end()` to properly close bank sessions

### Protocol Encoding Rules

- Messages use specific separators: `'` (segments), `+` (elements), `:` (sub-elements)
- Binary data uses `@length@data` format
- Escape character is `?` for literal separators
- See `parser.ts` and `encoder.ts`/`decoder.ts` for implementation details

### Transaction Support Matrix

Check capability with `can*()` methods (e.g., `canGetAccountBalance()`). Not all banks support all transactions.

## Development Commands

- **Build**: TypeScript compilation to ES2022
- **Test**: `pnpm test` (Vitest)
- **Dependencies**: Zero runtime dependencies - self-contained protocol implementation

## Important Constraints

- **FinTS 3.0 only** - older protocol versions not supported
- **PIN/TAN authentication only** - no certificate-based auth
- **No payment transactions** - read-only operations (balances, statements, portfolios)
- **German banking focus** - country code defaults to 280

When extending functionality, follow the established segment definition pattern and ensure proper registration in the registry.
83 changes: 83 additions & 0 deletions .github/prompts/plan-camtStatementRetrieval.prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Plan: Implement CAMT based statement retrieval with intelligent fallback

Enhance existing statement retrieval methods to automatically prefer CAMT format when supported by the bank, falling back to MT940 (HKKAZ) when CAMT is not available or when CAMT parsing fails. This maintains the current API while providing better data quality when possible.

## Steps

1. **Create CAMT segment definitions** - Implement [`HKCAZ`](src/segments/HKCAZ.ts), [`HICAZ`](src/segments/HICAZ.ts), and [`HICAZS`](src/segments/HICAZS.ts) following the same patterns as [`HKKAZ`](src/segments/HKKAZ.ts)/[`HIKAZ`](src/segments/HIKAZ.ts)/[`HIKAZS`](src/segments/HIKAZS.ts)

2. **Build CAMT XML parser** - Create [`camtParser.ts`](src/camtParser.ts) using built-in `DOMParser` that implements the same `parse(): Statement[]` interface as [`Mt940Parser`](src/mt940parser.ts), mapping CAMT.053 XML to existing `Statement`, `Transaction`, and `Balance` interfaces

3. **Enhance StatementInteraction with format selection** - Modify [`statementInteraction.ts`](src/interactions/statementInteraction.ts) to check CAMT support using `config.isAccountTransactionSupported(accountNumber, HKCAZ.Id)`, preferring CAMT when available and falling back to MT940 when not supported or parsing fails

4. **Update capability checking** - Modify [`canGetAccountStatements()`](src/client.ts) method to return true if either CAMT or MT940 is supported using `config.isTransactionSupported()` and `config.isAccountTransactionSupported()`, maintaining backward compatibility

5. **Register segments and exports** - Add CAMT segments to [`registry.ts`](src/segments/registry.ts) and export new CAMT parser from [`index.ts`](src/index.ts) for consistency

6. **Add comprehensive tests** - Create test files for segments, parser, and enhanced interaction logic with format fallback scenarios including XML parsing error handling

## Further Considerations

The CAMT parser will map XML elements to existing fields (e.g., CAMT's structured creditor/debtor information to `remoteName`/`remoteAccountNumber`, ISO references to `e2eReference`/`mandateReference`) ensuring users get richer data when available while maintaining identical API surface.

## Implementation Details

### CAMT Segment Structure

- **HKCAZ**: Request segment similar to HKKAZ but for CAMT format
- Same elements: account, date range, allAccounts, maxEntries, continuationMark
- Different segment ID to indicate CAMT format preference
- **HICAZ**: Response segment containing CAMT.053 XML data
- Binary field with CAMT XML content instead of MT940 text
- **HICAZS**: Business transaction parameters for HKCAZ
- Similar to HIKAZS with CAMT-specific capabilities

### Format Selection Logic

```typescript
// In StatementInteraction.createSegments()
const supportsCamt = config.isAccountTransactionSupported(accountNumber, HKCAZ.Id);
const supportsMt940 = config.isAccountTransactionSupported(accountNumber, HKKAZ.Id);

if (supportsCamt) {
// Use HKCAZ segment
} else if (supportsMt940) {
// Use HKKAZ segment
} else {
// Throw error - no statement format supported
}
```

### Error Handling Strategy

```typescript
// In StatementInteraction.handleResponse()
if (hicaz) {
try {
const parser = new CamtParser(hicaz.camtData);
clientResponse.statements = parser.parse();
} catch (error) {
// If CAMT parsing fails and MT940 is supported, fallback
if (config.isAccountTransactionSupported(accountNumber, HKKAZ.Id)) {
// Retry with MT940 format
} else {
throw error;
}
}
}
```

### CAMT to Statement Mapping

- **Statement level**: Map CAMT account statement to `Statement`
- **Transaction level**: Map CAMT transaction entries to `Transaction[]`
- **Balance level**: Map CAMT balance information to `Balance`
- **Party information**: Map structured CAMT party data to `remoteName`/`remoteAccountNumber`
- **Reference handling**: Map CAMT references to existing reference fields

### Backward Compatibility

- Existing `canGetAccountStatements()` returns true if either format is supported
- `StatementResponse.statements` remains `Statement[]` type
- No new client methods - transparent upgrade
- All existing MT940 functionality preserved as fallback
83 changes: 43 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ In theory the library is compatible with a browser environment, but communicatin

### Installing

Installation is straight forward by simply adding the npm package. The package has no further dependencies on other packages.
Installation is straight forward by simply adding the npm package.

```
npm i lib-fints
```

**Dependencies**: The library includes the `fast-xml-parser` package for robust CAMT statement parsing but has no other runtime dependencies.

### Sample Usage

The main public API of this library is the `FinTSClient` class and `FinTSConfig` class. In order to instantiate the client you need to provide a configuration instance. There are basically two ways to initialize a configuration object, one is when you communicate with a bank for the first time and the other when you already have banking information from a prevous session available (more on that later).
Expand All @@ -59,10 +61,10 @@ If the call is successfull the response will contain a `bankingInformation` obje

```typescript
export type BankingInformation = {
systemId: string;
bpd?: BPD;
upd?: UPD;
bankMessages: BankMessage[];
systemId: string;
bpd?: BPD;
upd?: UPD;
bankMessages: BankMessage[];
};
```

Expand Down Expand Up @@ -104,22 +106,22 @@ Most transactions may require authorization with a two step TAN process. As ment
```typescript
// we use the node readline interface later to ask the user for a TAN
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
input: process.stdin,
output: process.stdout,
});

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

if (!response.success) {
return;
return;
}

// need to check if a TAN is required to continue the transaction
if (response.requiresTan) {
// asking the user for the TAN, using the tanChallenge property
const tan = await rl.question(response.tanChallenge + ': ');
// continue the transaction by providing the tanReference from the response and the entered TAN
response = await client.getAccountStatementsWithTan(response.tanReference!, tan);
// asking the user for the TAN, using the tanChallenge property
const tan = await rl.question(response.tanChallenge + ': ');
// continue the transaction by providing the tanReference from the response and the entered TAN
response = await client.getAccountStatementsWithTan(response.tanReference!, tan);
}
```

Expand All @@ -140,13 +142,13 @@ This not only saves you from making the same synchronization requests every time

```typescript
const config = FinTSConfig.fromBankingInformation(
productId,
productVersion,
bankingInformation,
userId,
pin,
tanMethodId,
tanMediaName // when also needed (see below)
productId,
productVersion,
bankingInformation,
userId,
pin,
tanMethodId,
tanMediaName // when also needed (see below)
);
const client = new FinTSClient(config);
```
Expand All @@ -161,12 +163,12 @@ You can get a list of all available TAN methods from the `config.availableTanMet

```typescript
export type TanMethod = {
id: number;
name: string;
version: number;
activeTanMediaCount: number;
activeTanMedia: string[];
tanMediaRequirement: TanMediaRequirement;
id: number;
name: string;
version: number;
activeTanMediaCount: number;
activeTanMedia: string[];
tanMediaRequirement: TanMediaRequirement;
};
```

Expand All @@ -192,26 +194,26 @@ This will print out all sent messages and received responses to the console in a

The following table shows all transactions supported by the FinTSClient interface:

| 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 | HKKAZ | ✓ | ✓ |
| **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 | ✓ | ✓ |
| **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 | - | ❌ | ❌ |
| 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 | ✓ | ✓ |
| **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 | - | ❌ | ❌ |

### Transaction Support Checking

For each account-specific transaction, the client provides corresponding `can*` methods to check if the bank or specific account supports the transaction:

| Support Check Method | Purpose |
| -------------------------------------------- | ------------------------------------------------------ |
| `canGetAccountBalance(accountNumber?)` | Checks if account balance fetching is supported |
| `canGetAccountStatements(accountNumber?)` | Checks if account statements fetching is supported |
| `canGetPortfolio(accountNumber?)` | Checks if portfolio information fetching is supported |
| `canGetCreditCardStatements(accountNumber?)` | Checks if credit card statements fetching is supported |
| 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 |

### TAN Continuation Methods

Expand Down Expand Up @@ -242,6 +244,7 @@ Implementing further transactions should be straight forward and contributions a
## Built With

- [Typescript](https://www.typescriptlang.org/) - Programming Language
- [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) - XML parsing for CAMT statements
- [Vitest](https://vitest.dev/) - Testing Framework
- [pnpm](https://pnpm.io/) - Package manager

Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,8 @@
"@types/node": "^20.19.26",
"typescript": "^5.3.3",
"vitest": "^1.6.0"
},
"dependencies": {
"fast-xml-parser": "^5.3.3"
}
}
Loading