6.8 KiB
6.8 KiB
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:
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:
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
// 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
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
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
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
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
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:
try {
// Your code
} catch (error) {
return this.handleError(error, 'methodName');
}
2. Idempotency
Support idempotency keys in your internal system:
mapTransferToInternal(transfer: NostroVostroTransfer): unknown {
return {
transactionId: transfer.transferId,
idempotencyKey: transfer.idempotencyKey, // Pass through
// ... other fields
};
}
3. Validation
Validate data before mapping:
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:
async postTransfer(transfer: NostroVostroTransfer): Promise<...> {
console.log(`[${this.name}] Posting transfer ${transfer.transferId}`);
// ... implementation
}
Testing
Unit Tests
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 integrationflexcube-adapter.ts- Oracle Flexcube integrationswift-adapter.ts- SWIFT message handlingiso20022-adapter.ts- ISO 20022 message handling