Some checks failed
CI / Frontend Lint (pull_request) Failing after 5s
CI / Frontend Type Check (pull_request) Failing after 7s
CI / Frontend Build (pull_request) Failing after 6s
CI / Frontend E2E Tests (pull_request) Failing after 8s
CI / Orchestrator Build (pull_request) Failing after 6s
CI / Contracts Compile (pull_request) Failing after 7s
CI / Contracts Test (pull_request) Failing after 6s
Code Quality / SonarQube Analysis (pull_request) Failing after 19s
Code Quality / Code Quality Checks (pull_request) Failing after 5s
Security Scan / Dependency Vulnerability Scan (pull_request) Failing after 5s
Security Scan / OWASP ZAP Scan (pull_request) Failing after 4s
Outbound generators: - swift/mt760.ts: SBLC issuance (FIN Cat-7). 12-tag message built from InstrumentTerms with deterministic messageHash() for planHash anchoring. URDG 758 / UCP 600 aware. - swift/pacs009.ts: FI-to-FI credit transfer (ISO 20022 XML, pacs.009.001.08). Fixes the pacs.008 mis-routing flagged in the gap-analysis (pacs.008 is customer-to-bank; pacs.009 is bank-to-bank). BIC validation on all four agents. - swift/mt202.ts: FIN equivalent of pacs.009 for non-migrated corridors. 32A amount formatted with SWIFT decimal comma. Inbound parsers: - swift/camt.ts: parseCamt025 (receipt / status), parseCamt054 (credit/debit notification), reconcileCamt054 (diffs amount, ccy, direction, endToEndId so VALIDATING can feed mismatches into Data.valueMismatch()), parseCamt dispatcher on xmlns marker. Public surface in swift/index.ts documents channel selection: pacs.008 stays on the PSP customer leg; pacs.009/MT202 is the interbank leg; COMMIT requires camt.025 ACSC or camt.054 CRDT (arch §9.2 accepted !== settled). Tests: swift.test.ts — 14 cases covering the happy path, validation errors (bad BIC, malformed date, negative amount, missing pay step), determinism of messageHash, camt parser + reconciliation. tsc clean. 74 tests pass across 6 suites.
79 lines
2.6 KiB
TypeScript
79 lines
2.6 KiB
TypeScript
/**
|
|
* MT202 COV — General Financial Institution Transfer (cover method).
|
|
*
|
|
* Arch §4.3. FIN equivalent of pacs.009 used on SWIFT networks that
|
|
* have not yet migrated to ISO 20022. Generated alongside pacs.009
|
|
* during transitional period — settlement confirmation can arrive on
|
|
* either channel.
|
|
*/
|
|
|
|
import type { Plan, PlanStep } from "../../types/plan";
|
|
|
|
export interface Mt202Options {
|
|
transactionReference: string;
|
|
relatedReference?: string;
|
|
valueDate: string; // YYYY-MM-DD
|
|
sendingInstitution: string; // BIC
|
|
receivingInstitution: string;// BIC
|
|
beneficiaryInstitution: string; // BIC
|
|
orderingInstitution?: string;// BIC
|
|
}
|
|
|
|
export interface Mt202Message {
|
|
sender: string;
|
|
receiver: string;
|
|
fin: string;
|
|
fields: Record<string, string>;
|
|
}
|
|
|
|
function yyMMdd(iso: string): string {
|
|
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso);
|
|
if (!m) throw new Error(`MT202: valueDate must be YYYY-MM-DD, got '${iso}'`);
|
|
return `${m[1].slice(2)}${m[2]}${m[3]}`;
|
|
}
|
|
|
|
function bicCheck(bic: string, field: string): void {
|
|
if (!/^[A-Z0-9]{8}([A-Z0-9]{3})?$/.test(bic)) {
|
|
throw new Error(`MT202: ${field} must be a valid BIC, got '${bic}'`);
|
|
}
|
|
}
|
|
|
|
function findPayStep(plan: Plan): PlanStep {
|
|
const step = plan.steps.find((s) => s.type === "pay");
|
|
if (!step) throw new Error("MT202: plan must contain a 'pay' step");
|
|
return step;
|
|
}
|
|
|
|
export function generateMt202(plan: Plan, opts: Mt202Options): Mt202Message {
|
|
bicCheck(opts.sendingInstitution, "sendingInstitution");
|
|
bicCheck(opts.receivingInstitution, "receivingInstitution");
|
|
bicCheck(opts.beneficiaryInstitution, "beneficiaryInstitution");
|
|
if (opts.orderingInstitution) bicCheck(opts.orderingInstitution, "orderingInstitution");
|
|
|
|
const payStep = findPayStep(plan);
|
|
const ccy = (payStep.asset ?? "USD").toUpperCase();
|
|
const amount = payStep.amount.toFixed(2).replace(".", ",");
|
|
const field32A = `${yyMMdd(opts.valueDate)}${ccy}${amount}`;
|
|
|
|
const fields: Record<string, string> = {
|
|
"20": opts.transactionReference,
|
|
"21": opts.relatedReference ?? opts.transactionReference,
|
|
"32A": field32A,
|
|
"52A": opts.orderingInstitution ?? opts.sendingInstitution,
|
|
"57A": opts.receivingInstitution,
|
|
"58A": opts.beneficiaryInstitution,
|
|
};
|
|
|
|
const block1 = `{1:F01${opts.sendingInstitution.padEnd(12, "X")}0000000000}`;
|
|
const block2 = `{2:I202${opts.receivingInstitution.padEnd(12, "X")}N}`;
|
|
const block4 = Object.entries(fields).map(([t, v]) => `:${t}:${v}`).join("\n");
|
|
const block4Wrapped = `{4:\n${block4}\n-}`;
|
|
|
|
return {
|
|
sender: opts.sendingInstitution,
|
|
receiver: opts.receivingInstitution,
|
|
fin: `${block1}${block2}${block4Wrapped}`,
|
|
fields,
|
|
};
|
|
}
|