"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.processExternalTransfer = processExternalTransfer; exports.getMoneySupply = getMoneySupply; exports.getSettlementStatus = getSettlementStatus; const uuid_1 = require("uuid"); const settlement_core_1 = require("@dbis/settlement-core"); const config_1 = require("../config"); const fineract_1 = require("../adapters/fineract"); const hybx_production_1 = require("../adapters/hybx-production"); const omnl_1 = require("../adapters/omnl"); const swift_1 = require("../adapters/swift"); const settlement_store_1 = require("../store/settlement-store"); async function processExternalTransfer(input) { const cfg = (0, config_1.loadConfig)(); const profile = (0, config_1.loadOfficeProfile)(); const officeId = (0, config_1.settlementOfficeId)(input.officeId); const req = { ...input, officeId }; const existing = settlement_store_1.settlementStore.getByIdempotencyKey(req.idempotencyKey); if (existing?.phase === 'SETTLED') return existing; const now = new Date().toISOString(); let record = existing ?? { settlementId: (0, uuid_1.v4)(), idempotencyKey: req.idempotencyKey, phase: 'RECEIVED', request: req, errors: [], createdAt: now, updatedAt: now, }; const advance = (phase, patch = {}) => { record = { ...record, phase, updatedAt: new Date().toISOString(), ...patch }; settlement_store_1.settlementStore.save(record); }; try { if (!(0, settlement_core_1.isIbanValid)(req.creditorIban)) { throw new Error(`Invalid creditor IBAN: ${req.creditorIban}`); } advance('VALIDATED'); const verbiage = (0, settlement_core_1.rollExternalTransferVerbiage)(req); advance('VERBIAGE_ROLLED', { verbiageDocument: verbiage }); const amount = parseFloat(req.amount) || 0; const tf = req.tradeFinance; const gl = tf ? (0, settlement_core_1.resolveInstrumentGl)(tf.instrument) : null; const debitGl = gl?.debitGl ?? profile.settlement.glSettlement ?? cfg.moneySupply.glSettlement; const creditGl = gl?.creditGl ?? profile.settlement.glM1 ?? cfg.moneySupply.glM1; if (amount > 0) { const [debitGlId, creditGlId] = await Promise.all([ (0, fineract_1.resolveGlAccountId)(debitGl), (0, fineract_1.resolveGlAccountId)(creditGl), ]); const je = await (0, fineract_1.postJournalEntry)({ officeId: req.officeId, transactionDate: req.valueDate, referenceNumber: req.idempotencyKey, comments: verbiage.slice(0, 500), debits: [{ glAccountId: debitGlId, amount }], credits: [{ glAccountId: creditGlId, amount }], }); advance('FINERACT_POSTED', { fineractJournalRef: String(je.resourceId) }); } else { advance('FINERACT_POSTED'); } if (cfg.rails.hybx.enabled && req.rail !== 'INTERNAL') { const hybx = new hybx_production_1.HybxProductionRail(); const pay = await hybx.dispatchPayment({ idempotencyKey: req.idempotencyKey, amount: req.amount, currency: req.currency, creditorIban: req.creditorIban, creditorBic: req.creditorBic, beneficiaryName: req.beneficiaryName, remittanceInfo: req.remittanceInfo, rail: req.rail, }); advance('HYBX_RAIL_DISPATCHED', { hybxPaymentId: pay.paymentId }); } else { advance('HYBX_RAIL_DISPATCHED'); } const swiftKind = (0, settlement_core_1.swiftKindForRequest)(req); const swiftRaw = swiftKind === 'MT102' ? (0, swift_1.buildMt102Stub)({ senderRef: req.idempotencyKey, currency: req.currency, amount: req.amount, valueDate: req.valueDate, orderingInstitution: req.orderingName ?? cfg.omnlLegalName, beneficiary: req.beneficiaryName, iban: req.creditorIban, remittance: req.remittanceInfo, }) : (0, swift_1.buildMt103Stub)({ senderRef: req.idempotencyKey, currency: req.currency, amount: req.amount, valueDate: req.valueDate, orderingCustomer: req.orderingName ?? cfg.omnlLegalName, beneficiary: req.beneficiaryName, iban: req.creditorIban, bic: req.creditorBic, remittance: req.remittanceInfo, }); if (cfg.rails.swift.enabled) { await (0, swift_1.forwardSwiftToListener)(swiftRaw); } const iso = await (0, omnl_1.archiveIso20022)({ messageType: swiftKind === 'MT102' ? 'pacs.008' : 'pacs.008', direction: 'OUTBOUND', raw: swiftRaw, settlementRef: record.settlementId, }); advance('ISO20022_ARCHIVED', { iso20022MessageId: iso.messageId }); if (req.convertToCrypto?.enabled) { const balances = await (0, fineract_1.fetchGlBalances)(req.officeId); const snap = (0, settlement_core_1.buildMoneySupplySnapshot)(req.officeId, balances); const { loadAmount, fullyBacked } = (0, settlement_core_1.m2FiatToTokenLoadAmount)(req.amount, snap.M2.broadMoney); if (!fullyBacked) { record.errors.push(`M2 backing insufficient for token load (${loadAmount}/${req.amount})`); } const mint = await (0, omnl_1.requestTokenMint)({ lineId: req.convertToCrypto.tokenLineId, amount: loadAmount, recipient: req.convertToCrypto.recipientAddress, settlementRef: record.settlementId, dryRun: !cfg.production.allowChainMintExecute, }); advance('CHAIN_MINT_REQUESTED', { chainTxHash: mint.txHash }); } else { advance('CHAIN_MINT_REQUESTED'); } advance('SETTLED'); return record; } catch (err) { const msg = err instanceof Error ? err.message : String(err); record.errors.push(msg); advance('FAILED'); return record; } } async function getMoneySupply(officeId) { const balances = await (0, fineract_1.fetchGlBalances)(officeId); return (0, settlement_core_1.buildMoneySupplySnapshot)(officeId, balances); } function getSettlementStatus(id) { return settlement_store_1.settlementStore.getById(id); }