Files
dbis_core/docs/nostro-vostro/plugin-development-guide.md
defiQUG 849e6a8357
Some checks failed
CI / test (push) Has been cancelled
CI / security (push) Has been cancelled
CI / build (push) Has been cancelled
Initial commit
2025-12-12 15:02:56 -08:00

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 integration
  • flexcube-adapter.ts - Oracle Flexcube integration
  • swift-adapter.ts - SWIFT message handling
  • iso20022-adapter.ts - ISO 20022 message handling