feat: OMNL Bank online production — 128 chains, Central Bank UI, settlement stack
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m22s
CI/CD Pipeline / Security Scanning (push) Successful in 2m30s
CI/CD Pipeline / Lint and Format (push) Failing after 44s
CI/CD Pipeline / Terraform Validation (push) Failing after 27s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 31s
Deploy ChainID 138 / Deploy ChainID 138 (push) Failing after 56s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 29s
Validation / validate-genesis (push) Successful in 34s
Validation / validate-terraform (push) Failing after 39s
Validation / validate-kubernetes (push) Failing after 14s
Validation / validate-smart-contracts (push) Failing after 20s
Validation / validate-security (push) Failing after 1m19s
Validation / validate-documentation (push) Failing after 27s
Verify Deployment / Verify Deployment (push) Failing after 1m13s
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m22s
CI/CD Pipeline / Security Scanning (push) Successful in 2m30s
CI/CD Pipeline / Lint and Format (push) Failing after 44s
CI/CD Pipeline / Terraform Validation (push) Failing after 27s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 31s
Deploy ChainID 138 / Deploy ChainID 138 (push) Failing after 56s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 29s
Validation / validate-genesis (push) Successful in 34s
Validation / validate-terraform (push) Failing after 39s
Validation / validate-kubernetes (push) Failing after 14s
Validation / validate-smart-contracts (push) Failing after 20s
Validation / validate-security (push) Failing after 1m19s
Validation / validate-documentation (push) Failing after 27s
Verify Deployment / Verify Deployment (push) Failing after 1m13s
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
53
services/settlement-middleware/src/adapters/fineract.ts
Normal file
53
services/settlement-middleware/src/adapters/fineract.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import axios from 'axios';
|
||||
|
||||
function authHeaders(): Record<string, string> {
|
||||
const base = (process.env.OMNL_FINERACT_BASE_URL || '').replace(/\/$/, '');
|
||||
const tenant = process.env.OMNL_FINERACT_TENANT || 'omnl';
|
||||
const user =
|
||||
process.env.OMNL_FINERACT_USER?.trim() ||
|
||||
process.env.OMNL_FINERACT_USERNAME?.trim() ||
|
||||
'ali_hospitallers_tenant';
|
||||
const pass = process.env.OMNL_FINERACT_PASSWORD || '';
|
||||
if (!base || !pass) throw new Error('Fineract credentials required');
|
||||
const auth = Buffer.from(`${user}:${pass}`).toString('base64');
|
||||
return {
|
||||
Authorization: `Basic ${auth}`,
|
||||
'Fineract-Platform-TenantId': tenant,
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
}
|
||||
|
||||
export async function postJournalEntry(entry: {
|
||||
officeId: number;
|
||||
transactionDate: string;
|
||||
referenceNumber: string;
|
||||
comments: string;
|
||||
debits: { glAccountId: number; amount: number }[];
|
||||
credits: { glAccountId: number; amount: number }[];
|
||||
}): Promise<{ resourceId: number }> {
|
||||
const base = (process.env.OMNL_FINERACT_BASE_URL || '').replace(/\/$/, '');
|
||||
const { data } = await axios.post(`${base}/journalentries`, entry, {
|
||||
headers: authHeaders(),
|
||||
timeout: 60000,
|
||||
});
|
||||
return { resourceId: data.resourceId ?? data.entityId ?? 0 };
|
||||
}
|
||||
|
||||
export async function fetchGlBalances(officeId: number): Promise<Record<string, string>> {
|
||||
const base = (process.env.OMNL_FINERACT_BASE_URL || '').replace(/\/$/, '');
|
||||
try {
|
||||
const { data } = await axios.get(`${base}/runreports/General Ledger Report`, {
|
||||
headers: authHeaders(),
|
||||
params: { R_officeId: officeId, genericResultSet: false },
|
||||
timeout: 60000,
|
||||
});
|
||||
const out: Record<string, string> = {};
|
||||
const rows = Array.isArray(data) ? data : (data as { data?: unknown[] })?.data ?? [];
|
||||
for (const row of rows as { glCode?: string; balance?: number }[]) {
|
||||
if (row.glCode) out[row.glCode] = String(row.balance ?? 0);
|
||||
}
|
||||
return out;
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import axios from 'axios';
|
||||
import { loadHybxConfig } from '@dbis/integration-foundation';
|
||||
|
||||
/** Production HYBX rail client for real settlement dispatch */
|
||||
export class HybxProductionRail {
|
||||
private readonly baseUrl: string;
|
||||
private readonly apiKey: string;
|
||||
private readonly clientSecret: string;
|
||||
|
||||
constructor() {
|
||||
const cfg = loadHybxConfig({ testMode: false });
|
||||
if (cfg.environment === 'production' && process.env.SETTLEMENT_ALLOW_HYBX_PRODUCTION !== '1') {
|
||||
throw new Error('SETTLEMENT_ALLOW_HYBX_PRODUCTION=1 required for live HYBX rails');
|
||||
}
|
||||
this.baseUrl = cfg.baseUrl.replace(/\/$/, '');
|
||||
this.apiKey = cfg.apiKey;
|
||||
this.clientSecret = cfg.clientSecret;
|
||||
}
|
||||
|
||||
async dispatchPayment(payload: {
|
||||
idempotencyKey: string;
|
||||
amount: string;
|
||||
currency: string;
|
||||
creditorIban: string;
|
||||
creditorBic?: string;
|
||||
beneficiaryName: string;
|
||||
remittanceInfo: string;
|
||||
rail: string;
|
||||
}): Promise<{ paymentId: string; status: string }> {
|
||||
const sidecar = process.env.HYBX_MIFOS_FINERACT_SIDECAR_URL?.replace(/\/$/, '');
|
||||
const url = sidecar ? `${sidecar}/v1/rtgs/payments` : `${this.baseUrl}/v1/payments`;
|
||||
const { data } = await axios.post(
|
||||
url,
|
||||
{
|
||||
externalId: payload.idempotencyKey,
|
||||
amount: payload.amount,
|
||||
currency: payload.currency,
|
||||
beneficiary: {
|
||||
iban: payload.creditorIban,
|
||||
bic: payload.creditorBic,
|
||||
name: payload.beneficiaryName,
|
||||
},
|
||||
remittanceInformation: payload.remittanceInfo,
|
||||
rail: payload.rail,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': this.apiKey,
|
||||
Authorization: `Bearer ${this.clientSecret}`,
|
||||
},
|
||||
timeout: 120000,
|
||||
},
|
||||
);
|
||||
return {
|
||||
paymentId: String(data.paymentId ?? data.id ?? payload.idempotencyKey),
|
||||
status: String(data.status ?? 'DISPATCHED'),
|
||||
};
|
||||
}
|
||||
}
|
||||
59
services/settlement-middleware/src/adapters/omnl.ts
Normal file
59
services/settlement-middleware/src/adapters/omnl.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import axios from 'axios';
|
||||
|
||||
export async function archiveIso20022(payload: {
|
||||
messageType: string;
|
||||
direction: 'INBOUND' | 'OUTBOUND';
|
||||
raw: string;
|
||||
settlementRef: string;
|
||||
}): Promise<{ messageId: string }> {
|
||||
const base = (process.env.TOKEN_AGGREGATION_URL || 'http://localhost:3000').replace(/\/$/, '');
|
||||
const key = process.env.OMNL_API_KEY || '';
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
||||
if (key) headers.Authorization = `Bearer ${key}`;
|
||||
|
||||
const { data } = await axios.post(
|
||||
`${base}/api/v1/omnl/iso20022/messages`,
|
||||
{
|
||||
messageType: payload.messageType,
|
||||
direction: payload.direction,
|
||||
payload: payload.raw,
|
||||
metadata: { settlementRef: payload.settlementRef },
|
||||
},
|
||||
{ headers, timeout: 60000 },
|
||||
);
|
||||
return { messageId: String(data.messageId ?? data.id ?? payload.settlementRef) };
|
||||
}
|
||||
|
||||
export async function requestTokenMint(payload: {
|
||||
lineId: string;
|
||||
amount: string;
|
||||
recipient: string;
|
||||
settlementRef: string;
|
||||
dryRun: boolean;
|
||||
symbol?: string;
|
||||
tokenAddress?: string;
|
||||
}): Promise<{ txHash?: string; status: string }> {
|
||||
const base = (process.env.TOKEN_AGGREGATION_URL || 'http://localhost:3000').replace(/\/$/, '');
|
||||
const key = process.env.OMNL_API_KEY || '';
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
||||
if (key) headers.Authorization = `Bearer ${key}`;
|
||||
|
||||
try {
|
||||
const { data } = await axios.post(
|
||||
`${base}/api/v1/omnl/settlement/token-load`,
|
||||
payload,
|
||||
{ headers, timeout: 120000 },
|
||||
);
|
||||
return {
|
||||
txHash: data.txHash,
|
||||
status: String(data.status ?? (payload.dryRun ? 'DRY_RUN' : 'SUBMITTED')),
|
||||
};
|
||||
} catch (err) {
|
||||
if (axios.isAxiosError(err) && err.response?.status === 404) {
|
||||
return {
|
||||
status: payload.dryRun ? 'DRY_RUN' : 'PENDING_CHAIN_ENDPOINT',
|
||||
};
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
56
services/settlement-middleware/src/adapters/swift.ts
Normal file
56
services/settlement-middleware/src/adapters/swift.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import axios from 'axios';
|
||||
|
||||
export async function forwardSwiftToListener(raw: string): Promise<{ accepted: boolean }> {
|
||||
const url = (process.env.SWIFT_LISTENER_URL || 'http://192.168.11.114:8788').replace(/\/$/, '');
|
||||
const token = process.env.SWIFT_LISTENER_WEBHOOK_TOKEN || '';
|
||||
const headers: Record<string, string> = { 'Content-Type': 'text/plain' };
|
||||
if (token) headers.Authorization = `Bearer ${token}`;
|
||||
|
||||
await axios.post(`${url}/inbound`, raw, { headers, timeout: 60000 });
|
||||
return { accepted: true };
|
||||
}
|
||||
|
||||
export function buildMt103Stub(fields: {
|
||||
senderRef: string;
|
||||
currency: string;
|
||||
amount: string;
|
||||
valueDate: string;
|
||||
orderingCustomer: string;
|
||||
beneficiary: string;
|
||||
iban: string;
|
||||
bic?: string;
|
||||
remittance: string;
|
||||
}): string {
|
||||
const bicLine = fields.bic ? `:57A:${fields.bic}\n` : '';
|
||||
return `{1:F01OMNLUS33XXXX0000000000}{2:I103BANKUS33XXXXN}{4:
|
||||
:20:${fields.senderRef}
|
||||
:23B:CRED
|
||||
:32A:${fields.valueDate.replace(/-/g, '').slice(2)}${fields.currency}${fields.amount.replace('.', ',')}
|
||||
:50K:${fields.orderingCustomer}
|
||||
:59:/${fields.iban}
|
||||
${fields.beneficiary}
|
||||
${bicLine}:70:${fields.remittance}
|
||||
:71A:OUR
|
||||
-}`;
|
||||
}
|
||||
|
||||
export function buildMt102Stub(fields: {
|
||||
senderRef: string;
|
||||
currency: string;
|
||||
amount: string;
|
||||
valueDate: string;
|
||||
orderingInstitution: string;
|
||||
beneficiary: string;
|
||||
iban: string;
|
||||
remittance: string;
|
||||
}): string {
|
||||
return `{1:F01OMNLUS33XXXX0000000000}{2:I102BANKUS33XXXXN}{4:
|
||||
:20:${fields.senderRef}
|
||||
:21:NONREF
|
||||
:32B:${fields.currency}${fields.amount.replace('.', ',')}
|
||||
:50A:${fields.orderingInstitution}
|
||||
:59:/${fields.iban}
|
||||
${fields.beneficiary}
|
||||
:70:${fields.remittance}
|
||||
-}`;
|
||||
}
|
||||
Reference in New Issue
Block a user