Files
brazil-swift-ops/packages/utils/src/banks.ts
defiQUG 12427713ff Complete bank selector dropdown and SWIFT/BIC registry
- Added Bank type and BankRegistry interface
- Created banks service with CRUD operations and validation
- Added bank dropdown to Transactions page with ESTRBRRJ as default
- Extended Transaction type with bankSwiftCode field
- Added unit tests for bank registry
- Bank information stored in TypeScript module (can be migrated to DB/XML)
2026-01-23 17:03:31 -08:00

159 lines
3.7 KiB
TypeScript

/**
* Bank Registry Service
* Manages SWIFT/BIC code information for financial institutions
*/
import type { Bank, BankRegistry } from '@brazil-swift-ops/types';
// Bank data - in production, this would be loaded from a database or external service
const BANKS_DATA: Bank[] = [
{
swiftCode: 'ESTRBRRJ',
institutionName: 'strategy INVESTIMENTOS S/A CVC',
city: 'Rio de Janeiro',
country: 'BR',
active: true,
},
// Add more banks as needed
];
let bankRegistry: BankRegistry = {
banks: BANKS_DATA,
lastUpdated: new Date(),
version: '1.0.0',
};
/**
* Get all banks
*/
export function getAllBanks(): Bank[] {
return bankRegistry.banks.filter((bank) => bank.active);
}
/**
* Get bank by SWIFT code
*/
export function getBankBySwiftCode(swiftCode: string): Bank | null {
const normalizedCode = swiftCode.toUpperCase().trim();
return (
bankRegistry.banks.find(
(bank) => bank.swiftCode.toUpperCase() === normalizedCode && bank.active
) || null
);
}
/**
* Search banks by name or city
*/
export function searchBanks(query: string): Bank[] {
const normalizedQuery = query.toLowerCase().trim();
if (!normalizedQuery) {
return getAllBanks();
}
return bankRegistry.banks.filter(
(bank) =>
bank.active &&
(bank.institutionName.toLowerCase().includes(normalizedQuery) ||
bank.city.toLowerCase().includes(normalizedQuery) ||
bank.swiftCode.toLowerCase().includes(normalizedQuery))
);
}
/**
* Add a new bank to the registry
*/
export function addBank(bank: Omit<Bank, 'active'> & { active?: boolean }): Bank {
// Check if bank already exists
const existing = getBankBySwiftCode(bank.swiftCode);
if (existing) {
throw new Error(`Bank with SWIFT code ${bank.swiftCode} already exists`);
}
const newBank: Bank = {
...bank,
active: bank.active !== undefined ? bank.active : true,
};
bankRegistry.banks.push(newBank);
bankRegistry.lastUpdated = new Date();
return newBank;
}
/**
* Update an existing bank
*/
export function updateBank(swiftCode: string, updates: Partial<Bank>): Bank {
const bank = getBankBySwiftCode(swiftCode);
if (!bank) {
throw new Error(`Bank with SWIFT code ${swiftCode} not found`);
}
Object.assign(bank, updates);
bankRegistry.lastUpdated = new Date();
return bank;
}
/**
* Deactivate a bank (soft delete)
*/
export function deactivateBank(swiftCode: string): boolean {
const bank = getBankBySwiftCode(swiftCode);
if (!bank) {
return false;
}
bank.active = false;
bankRegistry.lastUpdated = new Date();
return true;
}
/**
* Get bank registry metadata
*/
export function getBankRegistry(): BankRegistry {
return { ...bankRegistry };
}
/**
* Load banks from external source (e.g., JSON file, API)
* In production, this would fetch from a database or external service
*/
export function loadBanksFromSource(banks: Bank[]): void {
bankRegistry.banks = banks;
bankRegistry.lastUpdated = new Date();
}
/**
* Validate SWIFT code format
* SWIFT codes are 8 or 11 characters: 4 letters (bank), 2 letters (country), 2 characters (location), optional 3 characters (branch)
*/
export function validateSwiftCode(swiftCode: string): {
valid: boolean;
errors: string[];
} {
const errors: string[] = [];
const normalized = swiftCode.toUpperCase().trim();
if (!normalized) {
errors.push('SWIFT code is required');
return { valid: false, errors };
}
if (normalized.length !== 8 && normalized.length !== 11) {
errors.push('SWIFT code must be 8 or 11 characters');
}
if (!/^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$/.test(normalized)) {
errors.push('SWIFT code format is invalid (expected: AAAA BB CC DDD)');
}
return {
valid: errors.length === 0,
errors,
};
}