279 lines
6.8 KiB
Markdown
279 lines
6.8 KiB
Markdown
# Plugin Development Guide
|
|
|
|
## Overview
|
|
|
|
This guide explains how to develop custom plugin adapters for integrating core banking systems with the DBIS Nostro/Vostro API.
|
|
|
|
## Plugin Architecture
|
|
|
|
### Base Adapter Interface
|
|
|
|
All plugins must implement the `IPluginAdapter` interface:
|
|
|
|
```typescript
|
|
interface IPluginAdapter {
|
|
getName(): string;
|
|
getVersion(): string;
|
|
isAvailable(): Promise<boolean>;
|
|
mapParticipant(internalData: unknown): ParticipantCreateRequest;
|
|
mapAccount(internalData: unknown): AccountCreateRequest;
|
|
mapTransfer(internalData: unknown): TransferCreateRequest;
|
|
mapTransferToInternal(transfer: NostroVostroTransfer): unknown;
|
|
mapAccountToInternal(account: NostroVostroAccount): unknown;
|
|
postTransfer(transfer: NostroVostroTransfer): Promise<{success: boolean; internalId?: string; error?: string}>;
|
|
getAccountBalance(accountId: string): Promise<{balance: string; available: string; hold: string}>;
|
|
reconcile(accountId: string, asOfDate: Date): Promise<{matched: number; breaks: unknown[]}>;
|
|
}
|
|
```
|
|
|
|
### Base Class
|
|
|
|
Extend `BasePluginAdapter` for common functionality:
|
|
|
|
```typescript
|
|
import { BasePluginAdapter } from '@/integration/plugins/generic-adapter';
|
|
|
|
export class MyCustomAdapter extends BasePluginAdapter {
|
|
constructor(config: Record<string, unknown> = {}) {
|
|
super('MyCustomAdapter', '1.0.0', config);
|
|
}
|
|
|
|
// Implement required methods
|
|
}
|
|
```
|
|
|
|
## Implementation Steps
|
|
|
|
### 1. Create Adapter Class
|
|
|
|
```typescript
|
|
// src/integration/plugins/my-custom-adapter.ts
|
|
import { BasePluginAdapter } from './generic-adapter';
|
|
import {
|
|
ParticipantCreateRequest,
|
|
AccountCreateRequest,
|
|
TransferCreateRequest,
|
|
NostroVostroTransfer,
|
|
NostroVostroAccount,
|
|
} from '@/core/nostro-vostro/nostro-vostro.types';
|
|
|
|
export class MyCustomAdapter extends BasePluginAdapter {
|
|
private apiEndpoint: string;
|
|
private apiKey: string;
|
|
|
|
constructor(config: Record<string, unknown> = {}) {
|
|
super('MyCustomAdapter', '1.0.0', config);
|
|
this.apiEndpoint = config.apiEndpoint as string;
|
|
this.apiKey = config.apiKey as string;
|
|
}
|
|
|
|
async isAvailable(): Promise<boolean> {
|
|
// Check connectivity to your system
|
|
try {
|
|
const response = await fetch(`${this.apiEndpoint}/health`, {
|
|
headers: { 'Authorization': `Bearer ${this.apiKey}` }
|
|
});
|
|
return response.ok;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Implement other methods...
|
|
}
|
|
```
|
|
|
|
### 2. Implement Mapping Methods
|
|
|
|
#### Participant Mapping
|
|
|
|
```typescript
|
|
mapParticipant(internalData: unknown): ParticipantCreateRequest {
|
|
const customer = internalData as YourInternalCustomer;
|
|
|
|
return {
|
|
participantId: customer.id,
|
|
name: customer.name,
|
|
bic: customer.bic,
|
|
lei: customer.lei,
|
|
country: customer.countryCode,
|
|
regulatoryTier: this.mapRegulatoryTier(customer.category),
|
|
metadata: {
|
|
internalId: customer.id,
|
|
// Additional metadata
|
|
},
|
|
};
|
|
}
|
|
```
|
|
|
|
#### Account Mapping
|
|
|
|
```typescript
|
|
mapAccount(internalData: unknown): AccountCreateRequest {
|
|
const account = internalData as YourInternalAccount;
|
|
|
|
return {
|
|
ownerParticipantId: account.customerId,
|
|
counterpartyParticipantId: account.correspondentId,
|
|
ibanOrLocalAccount: account.accountNumber,
|
|
currency: account.currency,
|
|
accountType: account.type === 'NOSTRO' ? 'NOSTRO' : 'VOSTRO',
|
|
metadata: {
|
|
internalAccountId: account.id,
|
|
},
|
|
};
|
|
}
|
|
```
|
|
|
|
#### Transfer Mapping
|
|
|
|
```typescript
|
|
mapTransfer(internalData: unknown): TransferCreateRequest {
|
|
const transaction = internalData as YourInternalTransaction;
|
|
|
|
return {
|
|
fromAccountId: transaction.debitAccount,
|
|
toAccountId: transaction.creditAccount,
|
|
amount: transaction.amount.toString(),
|
|
currency: transaction.currency,
|
|
valueDate: transaction.valueDate,
|
|
reference: transaction.reference,
|
|
metadata: {
|
|
internalTransactionId: transaction.id,
|
|
},
|
|
};
|
|
}
|
|
```
|
|
|
|
### 3. Implement Posting Methods
|
|
|
|
```typescript
|
|
async postTransfer(transfer: NostroVostroTransfer): Promise<{success: boolean; internalId?: string; error?: string}> {
|
|
try {
|
|
const internalTransaction = this.mapTransferToInternal(transfer);
|
|
|
|
const response = await fetch(`${this.apiEndpoint}/transactions`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Authorization': `Bearer ${this.apiKey}`,
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(internalTransaction)
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
const result = await response.json();
|
|
return { success: true, internalId: result.transactionId };
|
|
} catch (error) {
|
|
return this.handleError(error, 'postTransfer');
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4. Register Plugin
|
|
|
|
```typescript
|
|
import { pluginRegistry } from '@/integration/plugins/plugin-registry';
|
|
import { MyCustomAdapter } from './my-custom-adapter';
|
|
|
|
// Register during application startup
|
|
pluginRegistry.register('my-custom', new MyCustomAdapter({
|
|
apiEndpoint: process.env.MY_CUSTOM_API_ENDPOINT,
|
|
apiKey: process.env.MY_CUSTOM_API_KEY,
|
|
}));
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### 1. Error Handling
|
|
|
|
Always use the base class error handling:
|
|
|
|
```typescript
|
|
try {
|
|
// Your code
|
|
} catch (error) {
|
|
return this.handleError(error, 'methodName');
|
|
}
|
|
```
|
|
|
|
### 2. Idempotency
|
|
|
|
Support idempotency keys in your internal system:
|
|
|
|
```typescript
|
|
mapTransferToInternal(transfer: NostroVostroTransfer): unknown {
|
|
return {
|
|
transactionId: transfer.transferId,
|
|
idempotencyKey: transfer.idempotencyKey, // Pass through
|
|
// ... other fields
|
|
};
|
|
}
|
|
```
|
|
|
|
### 3. Validation
|
|
|
|
Validate data before mapping:
|
|
|
|
```typescript
|
|
mapTransfer(internalData: unknown): TransferCreateRequest {
|
|
const tx = internalData as YourTransaction;
|
|
|
|
if (!tx.amount || tx.amount <= 0) {
|
|
throw new Error('Invalid amount');
|
|
}
|
|
|
|
// Continue mapping...
|
|
}
|
|
```
|
|
|
|
### 4. Logging
|
|
|
|
Log important operations:
|
|
|
|
```typescript
|
|
async postTransfer(transfer: NostroVostroTransfer): Promise<...> {
|
|
console.log(`[${this.name}] Posting transfer ${transfer.transferId}`);
|
|
// ... implementation
|
|
}
|
|
```
|
|
|
|
## Testing
|
|
|
|
### Unit Tests
|
|
|
|
```typescript
|
|
describe('MyCustomAdapter', () => {
|
|
let adapter: MyCustomAdapter;
|
|
|
|
beforeEach(() => {
|
|
adapter = new MyCustomAdapter({
|
|
apiEndpoint: 'http://localhost:3000',
|
|
apiKey: 'test-key',
|
|
});
|
|
});
|
|
|
|
it('should map participant correctly', () => {
|
|
const internal = { id: '123', name: 'Test Bank', ... };
|
|
const result = adapter.mapParticipant(internal);
|
|
expect(result.name).toBe('Test Bank');
|
|
});
|
|
});
|
|
```
|
|
|
|
### Integration Tests
|
|
|
|
Test against a sandbox environment of your core banking system.
|
|
|
|
## Reference Implementations
|
|
|
|
See existing adapters for examples:
|
|
- `temenos-adapter.ts` - Temenos T24 integration
|
|
- `flexcube-adapter.ts` - Oracle Flexcube integration
|
|
- `swift-adapter.ts` - SWIFT message handling
|
|
- `iso20022-adapter.ts` - ISO 20022 message handling
|
|
|