libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit d4b9a5e4a1f2ce8ce01e57349af3565455a91469
parent cf46a7bca60499922fbaaea0e8e05606a8c008d4
Author: Antoine A <>
Date:   Wed, 14 Aug 2024 15:49:06 +0200

nexus: parse bank transaction code

Diffstat:
MMakefile | 2+-
Mnexus/codegen.py | 89++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mnexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Anexus/src/main/kotlin/tech/libeufin/nexus/Iso20022BankTransactionCode.kt | 386+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rnexus/src/main/kotlin/tech/libeufin/nexus/Iso20022CodeSets.kt -> nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022ExternalCodeSets.kt | 0
Mtestbench/src/test/kotlin/Iso20022Test.kt | 77++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
6 files changed, 578 insertions(+), 51 deletions(-)

diff --git a/Makefile b/Makefile @@ -108,7 +108,7 @@ common-test: install-nobuild-files ./gradlew :common:test --tests $(test) -i .PHONY: testbench-test -integration-test: install-nobuild-files +testbench-test: install-nobuild-files ./gradlew :testbench:test --tests $(test) -i .PHONY: testbench diff --git a/nexus/codegen.py b/nexus/codegen.py @@ -8,7 +8,7 @@ import polars as pl import requests -def iso20022codegen(): +def iso20022codegenExternalCodeSet(): # Get XLSX zip file from server r = requests.get( "https://www.iso20022.org/sites/default/files/media/file/ExternalCodeSets_XLSX.zip" @@ -33,10 +33,7 @@ def iso20022codegen(): (value, isoCode, description) = ( row["Code Value"], row["Code Name"], - row["Code Definition"] - .split("\n", 1)[0] - .rstrip() - .replace("_x000D_", ""), + row["Code Definition"].split("\n", 1)[0].strip().replace("_x000D_", ""), ) out += f'\n\t{value}("{isoCode}", "{description}"),' @@ -76,8 +73,86 @@ package tech.libeufin.nexus {extractCodeSet("ExternalReturnReason1Code", "ExternalReturnReasonCode")} """ - with open("src/main/kotlin/tech/libeufin/nexus/Iso20022CodeSets.kt", "w") as file1: + with open( + "src/main/kotlin/tech/libeufin/nexus/Iso20022ExternalCodeSets.kt", "w" + ) as file1: file1.write(kt) -iso20022codegen() +def iso20022codegenBankTransactionCode(): + # Get XLSX zip file from server + r = requests.get( + "https://www.iso20022.org/sites/default/files/media/file/BTC_Codification_21March2024.xlsx" + ) + assert r.status_code == 200 + + # Get the XLSX file + bytes = r.content + + # Parse excel + df = pl.read_excel( + bytes, + sheet_name="BTC_Codification", + read_options={"header_row": 2}, + ).rename(lambda name: name.splitlines()[0]) + + def extractCodeSet(setName: str, className: str) -> str: + out = f"enum class {className}(val description: String) {{" + codeName = f"{setName} Code" + for row in ( + df.group_by(codeName) + .agg(pl.col(setName).unique().sort()) + .sort(codeName) + .rows(named=True) + ): + if len(row[setName]) > 1: + print(row) + + (code, description) = ( + row[codeName].strip().replace("\xa0", ""), + row[setName][0].strip(), + ) + out += f'\n\t{code}("{description}"),' + + out += "\n}" + return out + + # Write kotlin file + kt = f"""/* + * This file is part of LibEuFin. + * Copyright (C) 2024 Taler Systems S.A. + + * LibEuFin is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation; either version 3, or + * (at your option) any later version. + + * LibEuFin is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General + * Public License for more details. + + * You should have received a copy of the GNU Affero General Public + * License along with LibEuFin; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/> + */ + +// THIS FILE IS GENERATED, DO NOT EDIT + +package tech.libeufin.nexus + +{extractCodeSet("Domain", "ExternalBankTransactionDomainCode")} + +{extractCodeSet("Family", "ExternalBankTransactionFamilyCode")} + +{extractCodeSet("SubFamily", "ExternalBankTransactionSubFamilyCode")} + +""" + with open( + "src/main/kotlin/tech/libeufin/nexus/Iso20022BankTransactionCode.kt", "w" + ) as file1: + file1.write(kt) + + +iso20022codegenExternalCodeSet() +iso20022codegenBankTransactionCode() diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt @@ -369,23 +369,23 @@ fun parseTx( } } + /** Parse amount */ fun XmlDestructor.amount(acceptedCurrency: String) = one("Amt") { val currency = attr("Ccy") /** FIXME: test by sending non-CHF to PoFi and see which currency gets here. */ if (currency != acceptedCurrency) throw Exception("Currency $currency not supported") TalerAmount("$currency:${text()}") } - - /** Check if transaction code is reversal */ - fun XmlDestructor.isReversalCode(): Boolean { + + /** Parse bank transaction code */ + fun XmlDestructor.bankTransactionCode(): BankTransactionCode { return one("BkTxCd").one("Domn") { - // TODO automate enum generation for all those code - val domainCode = one("Cd").text() + val domain = one("Cd").enum<ExternalBankTransactionDomainCode>() one("Fmly") { - val familyCode = one("Cd").text() - val subFamilyCode = one("SubFmlyCd").text() + val family = one("Cd").enum<ExternalBankTransactionFamilyCode>() + val subFamily = one("SubFmlyCd").enum<ExternalBankTransactionSubFamilyCode>() - subFamilyCode == "RRTN" || subFamilyCode == "RPCR" + BankTransactionCode(domain, family, subFamily) } } } @@ -406,17 +406,19 @@ fun parseTx( val kind = one("CdtDbtInd").enum<Kind>() val amount = amount(acceptedCurrency) one("NtryDtls").one("TxDtls") { // TODO handle batches + val code = bankTransactionCode() + if (!code.isPayment()) return@one val txRef = opt("Refs")?.opt("AcctSvcrRef")?.text() - val reversal = isReversalCode() val nexusId = nexusId() - if (reversal) { + if (code.isReversal()) { if (kind == Kind.CRDT) { val reason = returnReason() txsInfo.add(TxInfo.CreditReversal( ref = nexusId ?: txRef ?: entryRef, bookDate = bookDate, nexusId = nexusId, - reason = reason + reason = reason, + code = code )) } } else { @@ -431,7 +433,8 @@ fun parseTx( bankId = bankId, amount = amount, subject = subject, - debtorPayto = debtorPayto + debtorPayto = debtorPayto, + code = code )) } Kind.DBIT -> { @@ -442,7 +445,8 @@ fun parseTx( nexusId = nexusId, amount = amount, subject = subject, - creditorPayto = creditorPayto + creditorPayto = creditorPayto, + code = code )) } } @@ -465,7 +469,8 @@ fun parseTx( } each("Ntry") { if (!isBooked()) return@each - if (isReversalCode()) return@each + val code = bankTransactionCode() + if (code.isReversal() || !code.isPayment()) return@each val entryRef = opt("AcctSvcrRef")?.text() val bookDate = executionDate() val kind = one("CdtDbtInd").enum<Kind>() @@ -483,7 +488,8 @@ fun parseTx( bankId = null, amount = amount, subject = subject, - debtorPayto = debtorPayto + debtorPayto = debtorPayto, + code = code )) } } @@ -503,8 +509,9 @@ fun parseTx( } each("Ntry") { if (!isBooked()) return@each + val code = bankTransactionCode() // Non reversal transaction are handled in camt.054 - if (!isReversalCode()) return@each + if (!code.isReversal() || !code.isPayment()) return@each val entryRef = opt("AcctSvcrRef")?.text() val bookDate = executionDate() @@ -518,7 +525,8 @@ fun parseTx( ref = nexusId ?: txRef ?: entryRef, bookDate = bookDate, nexusId = nexusId, - reason = reason + reason = reason, + code = code )) } } @@ -531,8 +539,9 @@ fun parseTx( } each("Ntry") { if (!isBooked()) return@each + val code = bankTransactionCode() // Reversal are handled from camt.053 - if (isReversalCode()) return@each + if (code.isReversal() || !code.isPayment()) return@each val entryRef = opt("AcctSvcrRef")?.text() val bookDate = executionDate() @@ -551,7 +560,8 @@ fun parseTx( bankId = bankId, amount = amount, subject = subject, - debtorPayto = debtorPayto + debtorPayto = debtorPayto, + code = code )) } Kind.DBIT -> { @@ -563,7 +573,8 @@ fun parseTx( nexusId = nexusId, amount = amount, subject = subject, - creditorPayto = creditorPayto + creditorPayto = creditorPayto, + code = code )) } } @@ -589,9 +600,12 @@ private sealed interface TxInfo { val ref: String? // When was this transaction booked val bookDate: Instant + // ISO20022 bank transaction code + val code: BankTransactionCode data class CreditReversal( override val ref: String?, override val bookDate: Instant, + override val code: BankTransactionCode, // Unique ID generated by libeufin-nexus val nexusId: String?, val reason: String? @@ -599,6 +613,7 @@ private sealed interface TxInfo { data class Credit( override val ref: String?, override val bookDate: Instant, + override val code: BankTransactionCode, // Unique ID generated by payment provider val bankId: String?, val amount: TalerAmount, @@ -608,6 +623,7 @@ private sealed interface TxInfo { data class Debit( override val ref: String?, override val bookDate: Instant, + override val code: BankTransactionCode, // Unique ID generated by libeufin-nexus val nexusId: String?, val amount: TalerAmount, @@ -654,4 +670,23 @@ private fun parseTxLogic(info: TxInfo): TxNotification { ) } } +} + +data class BankTransactionCode( + val domain: ExternalBankTransactionDomainCode, + val family: ExternalBankTransactionFamilyCode, + val subFamily: ExternalBankTransactionSubFamilyCode +) { + fun isReversal(): Boolean = REVERSAL_CODE.contains(subFamily) + fun isPayment(): Boolean = domain == ExternalBankTransactionDomainCode.PMNT + + override fun toString(): String = + "${domain.name} ${family.name} ${subFamily.name} - '${domain.description}' '${family.description}' '${subFamily.description}'" + + companion object { + private val REVERSAL_CODE = setOf( + ExternalBankTransactionSubFamilyCode.RPCR, + ExternalBankTransactionSubFamilyCode.RRTN, + ) + } } \ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022BankTransactionCode.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022BankTransactionCode.kt @@ -0,0 +1,386 @@ +/* + * This file is part of LibEuFin. + * Copyright (C) 2024 Taler Systems S.A. + + * LibEuFin is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation; either version 3, or + * (at your option) any later version. + + * LibEuFin is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General + * Public License for more details. + + * You should have received a copy of the GNU Affero General Public + * License along with LibEuFin; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/> + */ + +// THIS FILE IS GENERATED, DO NOT EDIT + +package tech.libeufin.nexus + +enum class ExternalBankTransactionDomainCode(val description: String) { + ACMT("Account Management"), + CAMT("Cash Management"), + CMDT("Commodities"), + DERV("Derivatives"), + FORX("Foreign Exchange"), + LDAS("Loans, Deposits & Syndications"), + PMET("Precious Metal"), + PMNT("Payments"), + SECU("Securities"), + TRAD("Trade Services"), + XTND("Extended Domain"), +} + +enum class ExternalBankTransactionFamilyCode(val description: String) { + ACCB("Account Balancing"), + ACOP("Additional Miscellaneous Credit Operations"), + ADOP("Additional Miscellaneous Debit Operations"), + BLOC("Blocked Transactions"), + CAPL("Cash Pooling"), + CASH("Miscellaneous Securities Operations"), + CCRD("Customer Card Transactions"), + CLNC("Clean Collection"), + CNTR("Counter Transactions"), + COLC("Custody Collection"), + COLL("Collateral Management"), + CORP("Corporate Action"), + CSLN("Consumer Loans"), + CUST("Custody"), + DCCT("Documentary Credit"), + DLVR("Delivery"), + DOCC("Documentary Collection"), + DRFT("Drafts"), + FTDP("Fixed Term Deposits"), + FTLN("Fixed Term Loans"), + FTUR("Futures"), + FWRD("Forwards"), + GUAR("Guarantees"), + ICCN("Issued Cash Concentration Transactions"), + ICDT("Issued Credit Transfers"), + ICHQ("Issued Cheques"), + IDDT("Issued Direct Debits"), + IRCT("Issued Real-Time Credit Transfers"), + LACK("Lack"), + LBOX("Lockbox Transactions"), + LFUT("Listed Derivatives - Futures"), + LOCT("Stand-By Letter Of Credit"), + LOPT("Listed Derivatives - Options"), + MCOP("Miscellaneous Credit Operations"), + MCRD("Merchant Card Transactions"), + MDOP("Miscellaneous Debit Operations"), + MGLN("Mortgage Loans"), + NDFX("Non Deliverable"), + NSET("Non Settled"), + NTAV("Not Available"), + NTDP("Notice Deposits"), + NTLN("Notice Loans"), + OBND("OTC Derivatives - Bonds"), + OCRD("OTC Derivatives - Credit"), + OEQT("OTC Derivatives - Equity"), + OIRT("OTC Derivatives - Interest Rates"), + OPCL("Opening & Closing"), + OPTN("Options"), + OSED("OTC Derivatives - Structured Exotic Derivatives"), + OSWP("OTC Derivatives – Swaps"), + OTHB("CSD Blocked transactions"), + OTHR("Other"), + RCCN("Received Cash Concentration Transactions"), + RCDT("Received Credit Transfers"), + RCHQ("Received Cheques"), + RDDT("Received Direct Debits"), + RRCT("Received Real-Time Credit Transfers"), + SETT("Trade, Clearing and Settlement"), + SPOT("Spots"), + SWAP("Swaps"), + SYDN("Syndications"), +} + +enum class ExternalBankTransactionSubFamilyCode(val description: String) { + ACCC("Account Closing"), + ACCO("Account Opening"), + ACCT("Account Transfer"), + ACDT("ACH Credit"), + ACOR("ACH Corporate Trade"), + ADBT("ACH Debit"), + ADJT("Adjustments (Generic)"), + APAC("ACH Pre-Authorised"), + ARET("ACH Return"), + AREV("ACH Reversal"), + ARPD("ARP Debit"), + ASET("ACH Settlement"), + ATXN("ACH Transaction"), + AUTT("Automatic Transfer"), + BBDD("SEPA B2B Direct Debit"), + BCDP("Branch Deposit"), + BCHQ("Bank Cheque"), + BCKV("Back Value"), + BCWD("Branch Withdrawl"), + BFWD("Bond Forward"), + BIDS("Repurchase Offer/Issuer Bid/Reverse Rights."), + BKFE("Bank Fees"), + BONU("Bonus Issue/Capitalisation Issue"), + BOOK("Internal Book Transfer"), + BPUT("Put Redemption"), + BROK("Brokerage Fee"), + BSBC("Sell Buy Back"), + BSBO("Buy Sell Back"), + CAJT("Credit Adjustments (Generic)"), + CAPG("Capital Gains Distribution"), + CASH("Cash Letter"), + CCCH("Certified Customer Cheque"), + CCHQ("Cheque"), + CCIR("Cross Currency IRS"), + CCPC("CCP Cleared Initial Margin"), + CCPM("CCP Cleared Variation Margin"), + CCSM("CCP Cleared Segregated Initial Margin"), + CDIS("Controlled Disbursement"), + CDPT("Cash Deposit"), + CHAR("Charge/Fees"), + CHKD("Check Deposit"), + CHRG("Charges (Generic)"), + CLAI("Compensation/Claims"), + CLCQ("Circular Cheque"), + CMBO("Corporate Mark Broker Owned"), + CMCO("Corporate Mark Client Owned"), + COME("Commission Excluding Taxes (Generic)"), + COMI("Commission Including Taxes (Generic)"), + COMM("Commission (Generic)"), + COMT("Non Taxable Commissions (Generic)"), + CONV("Conversion"), + COVE("Cover Transaction"), + CPEN("Cash Penalties"), + CPRB("Corporate Rebate"), + CQRV("Cheque Reversal"), + CRCQ("Crossed Cheque"), + CRDS("Credit DefaultSwap"), + CROS("Cross Trade"), + CRPR("Cross Product"), + CRSP("Credit Support"), + CRTL("Credit Line"), + CSHA("Cash Letter Adjustment"), + CSLI("Cash In Lieu"), + CWDL("Cash Withdrawal"), + DAJT("Debit Adjustments (Generic)"), + DDFT("Discounted Draft"), + DDWN("Drawdown"), + DECR("Decrease in Value"), + DMCG("Draft Maturity Change"), + DMCT("Domestic Credit Transfer"), + DPST("Deposit"), + DRAW("Drawing"), + DRIP("Dividend Reinvestment"), + DSBR("Controlled Disbursement"), + DTCH("Dutch Auction"), + DVCA("Cash Dividend"), + DVOP("Dividend Option"), + ENCT("Nordic Payment Council Credit Transfer"), + EQBO("Equity Mark Broker Owned"), + EQCO("Equity Mark Client Owned"), + EQPT("Equity Option"), + EQUS("Equity Swap"), + ERTA("Exchange Rate Adjustment"), + ERWA("Lending Income"), + ERWI("Borrowing Fee"), + ESCT("SEPA Credit Transfer"), + ESDD("SEPA Core Direct Debit"), + EXOF("Exchange"), + EXPT("Exotic Option"), + EXRI("Call On Intermediate Securities"), + EXTD("Exchange Traded Derivatives"), + EXWA("Warrant Exercise/Warrant Conversion"), + FCDP("Foreign Currencies Deposit"), + FCTA("Factor Update"), + FCWD("Foreign Currencies Withdrawal"), + FEES("Fees (Generic)"), + FICT("Financial Institution Credit Transfer"), + FIDD("Financial Institution Direct Debit Payment"), + FIOA("Financial Institution Own Account Transfer"), + FIXI("Fixed Income"), + FLTA("Float Adjustment"), + FRZF("Freeze Of Funds"), + FUCO("Futures Commission"), + FUTU("Future Variation Margin"), + FWBC("Forwards Broker Owned Collateral"), + FWCC("Forwards Client Owned Collateral"), + FWSB("MFA Segregated Broker Cash Collateral"), + FWSC("MFA Segregated Client Cash Collateral"), + GEN1("Withdrawal/Distribution"), + GEN2("Deposit/Contribution"), + IADD("Invoice Accepted with Differed Due Date"), + INFD("Fixed Deposit Interest Amount"), + INSP("Inspeci/Share Exchange"), + INTR("Interest Payment"), + ISSU("Depositary Receipt Issue"), + LBCA("Credit Adjustment"), + LBDP("Deposit"), + LIQU("Liquidation Dividend / Liquidation Payment"), + MARG("Margin Payments"), + MBSB("Mortgage Back Segregated Broker Cash Collateral"), + MBSC("Mortgage Back Segregated Client Cash Collateral"), + MCAL("Full Call / Early Redemption"), + MGCC("Margin Client Owned Cash Collateral"), + MGSC("Initial Futures Margin Segregated Client Cash Collateral"), + MIXD("Mixed Deposit"), + MNFE("Management Fees"), + MRGR("Merger"), + MSCD("Miscellaneous Deposit"), + NETT("Netting"), + NPCC("Non Presented Circular Cheques"), + NSYN("Non Syndicated"), + NTAV("Not Available"), + NWID("New issue distribution"), + OCCC("Client owned OCC pledged collateral"), + ODFT("Overdraft"), + ODLT("Odd Lot Sale/Purchase"), + OODD("One-Off Direct Debit"), + OPBC("Option Broker Owned Collateral"), + OPCC("Option Client Owned Collateral"), + OPCQ("Open Cheque"), + OPSB("OTC Option Segregated Broker Cash Collateral"), + OPSC("OTC Option Segregated Client Cash Collateral"), + OPTN("FX Option"), + ORCQ("Order Cheque"), + OTCC("OTC CCP"), + OTCD("OTC Derivatives"), + OTCG("OTC"), + OTCN("OTC Non-CCP"), + OTHR("Other"), + OVCH("Overdraft Charge"), + OWNE("External Account Transfer"), + OWNI("Internal Account Transfer"), + PADD("Pre-Authorised Direct Debit"), + PAIR("Pair-Off"), + PCAL("Partial Redemption With Reduction Of Nominal Value"), + PLAC("Placement"), + PMDD("Direct Debit"), + PORT("Portfolio Move"), + POSC("Credit Card Payment"), + POSD("Point-of-Sale (POS) Payment - Debit Card"), + PPAY("Principal Payment"), + PRCT("Priority Credit Transfer"), + PRDD("Reversal Due To Payment Reversal"), + PRED("Partial Redemption Without Reduction Of Nominal Value"), + PRII("Interest Payment with Principles"), + PRIN("Interest Payment with Principles"), + PRIO("Priority Issue"), + PRUD("Principal Pay-Down/Pay-Up"), + PSTE("Posting Error"), + RCDD("Reversal Due To Payment Cancellation Request"), + RCOV("Reversal due to a Cover Transaction Return"), + REAA("Redemption Asset Allocation"), + REDM("Final Maturity"), + REPU("Repo"), + RESI("Futures Residual Amount"), + RHTS("Rights Issue/Subscription Rights/Rights Offer"), + RIMB("Reimbursement (Generic)"), + RNEW("Renewal"), + RPBC("Bi-lateral repo broker owned collateral"), + RPCC("Repo client owned collateral"), + RPCR("Reversal Due To Payment Cancellation Request"), + RPMT("Repayment"), + RPSB("Bi-lateral Repo Segregated Broker Cash Collateral"), + RPSC("Bi-lateral Repo Segregated Client Cash Collateral"), + RRTN("Reversal Due To Payment Return"), + RVPO("Reverse Repo"), + RWPL("Redemption Withdrawing Plan"), + SABG("Settlement Against Bank Guarantee"), + SALA("Payroll/Salary Payment"), + SBSC("Securities Buy Sell Sell Buy Back"), + SCIE("Single Currency IRS Exotic"), + SCIR("Single Currency IRS"), + SCRP("Securities Cross Products"), + SDVA("Same Day Value Credit Transfer"), + SECB("Securities Borrowing"), + SECL("Securities Lending"), + SHBC("Broker owned collateral Short Sale"), + SHCC("Client owned collateral Short Sale"), + SHPR("Equity Premium Reserve"), + SHSL("Short Sell"), + SLBC("Lending Broker Owned Cash Collateral"), + SLCC("Lending Client Owned Cash Collateral"), + SLEB("Securities Lending And Borrowing"), + SLOA("SecuredLoan"), + SOSE("Settlement Of Sight Export Document"), + SOSI("Settlement Of Sight Import Document"), + SSPL("Subscription Savings Plan"), + STAC("Settlement After Collection"), + STAM("Settlement At Maturity"), + STDO("Standing Order"), + STLM("Settlement"), + STLR("Settlement Under Reserve"), + STOD("Bill of Exchange Settlement on Demand"), + SUAA("Subscription Asset Allocation"), + SUBS("Subscription"), + SWAP("Swap Payment"), + SWBC("Swap Broker Owned Collateral"), + SWCC("Client Owned Collateral"), + SWEP("Sweep"), + SWFP("Final Payment"), + SWIC("Switch"), + SWPP("Partial Payment"), + SWPT("Swaption"), + SWRS("Reset Payment"), + SWSB("ISDA/CSA Segregated Broker Cash Collateral"), + SWSC("ISDA/CSA Segregated Client Cash Collateral"), + SWUF("Upfront Payment"), + SYND("Syndicated"), + TAXE("Taxes (Generic)"), + TBAC("TBA Closing"), + TBAS("To Be Announced"), + TBBC("TBA Broker owned cash collateral"), + TBCC("TBA Client owned cash collateral"), + TCDP("Travellers Cheques Deposit"), + TCWD("Travellers Cheques Withdrawal"), + TEND("Tender"), + TOPG("Topping"), + TOUT("Transfer Out"), + TRAD("Trade"), + TRCP("Treasury Cross Product"), + TREC("Tax Reclaim"), + TRFE("Transaction Fees"), + TRIN("Transfer In"), + TRPO("Triparty Repo"), + TRVO("Triparty Reverse Repo"), + TTLS("Treasury Tax And Loan Service"), + TURN("Turnaround"), + UDFT("Dishonoured/Unpaid Draft"), + UNCO("Underwriting Commission"), + UPCQ("Unpaid Cheque"), + UPCT("Unpaid Card Transaction"), + UPDD("Reversal Due To Return/Unpaid Direct Debit"), + URCQ("Cheque Under Reserve"), + URDD("Direct Debit Under Reserve"), + VALD("Value Date"), + VCOM("Credit Transfer With Agreed Commercial Information"), + WITH("Withholding Tax"), + XBCP("Cross-Border Credit Card Payment"), + XBCQ("Foreign Cheque"), + XBCT("Cross-Border Credit Transfer"), + XBCW("Cross-Border Cash Withdrawal"), + XBRD("Cross-Border"), + XBSA("Cross-Border Payroll/Salary Payment"), + XBST("Cross-Border Standing Order"), + XCHC("Exchange Traded CCP"), + XCHG("Exchange Traded"), + XCHN("Exchange Traded Non-CCP"), + XICT("Cross-Border Intra Company Transfer"), + XPCQ("Unpaid Foreign Cheque"), + XRCQ("Foreign Cheque Under Reserve"), + XRTN("Cross Border Reversal Due to Payment Return"), + YTDA("YTD Adjustment"), + ZABA("Zero Balancing"), + ACON("ACH Concentration"), + BACT("Branch Account Transfer"), + COAT("Corporate Own Account Transfer"), + ICCT("Intra Company Transfer"), + LBDB("Debit"), + POSP("Point-of-Sale (POS) Payment"), + SMCD("Smart-Card Payment"), + SMRT("Smart-Card Payment"), + XBDD("Cross-Border Direct Debit"), +} + diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022CodeSets.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022ExternalCodeSets.kt diff --git a/testbench/src/test/kotlin/Iso20022Test.kt b/testbench/src/test/kotlin/Iso20022Test.kt @@ -18,12 +18,10 @@ */ import org.junit.Test -import tech.libeufin.nexus.ebics.Dialect -import tech.libeufin.nexus.nexusConfig -import tech.libeufin.nexus.parseCustomerAck -import tech.libeufin.nexus.parseCustomerPaymentStatusReport -import tech.libeufin.nexus.parseTx +import tech.libeufin.nexus.ebics.* +import tech.libeufin.nexus.* import java.nio.file.Files +import java.nio.file.Path import kotlin.io.path.Path import kotlin.io.path.exists import kotlin.io.path.isDirectory @@ -52,29 +50,62 @@ class Iso20022Test { if (!root.exists()) return for (platform in root.listDirectoryEntries()) { if (!platform.isDirectory()) continue + + // List logs + var logs = mutableListOf<Path>() for (file in platform.listDirectoryEntries()) { if (!file.isDirectory()) continue - val fetch = file.resolve("fetch") - if (!fetch.exists()) continue - val nexusCfg = nexusConfig(platform.resolve("ebics.conf")) - val cfg = nexusCfg.ebics - val currency = cfg.currency - val dialect = cfg.dialect - for (log in fetch.listDirectoryEntries()) { - val content = Files.newInputStream(log) - val name = log.toString() - println(name) - if (name.contains("HAC")) { - parseCustomerAck(content) - } else if (name.contains("pain.002")) { - parseCustomerPaymentStatusReport(content) - } else if ( - !name.contains("camt.052") && !name.contains("_C52_") && !name.contains("_Z01_") - ) { - parseTx(content, currency, dialect) + when (file.fileName.toString()) { + "fetch" -> for (transaction in file.listDirectoryEntries()) { + if (transaction.isDirectory()) { + logs.addAll(transaction.listDirectoryEntries()) + } + } + "submit" -> {} + else -> for (transaction in file.listDirectoryEntries()) { + when (transaction.fileName.toString()) { + "fetch" -> logs.addAll(transaction.listDirectoryEntries()) + "submit" -> {} + else -> { + var payload = transaction.resolve("payload") + if (payload.exists()) { + logs.addAll(payload.listDirectoryEntries()) + continue + } + payload = transaction.resolve("payload.xml") + if (payload.exists()) { + logs.add(payload) + } + } + } } } } + + // Load config + val nexusCfg = nexusConfig(platform.resolve("ebics.conf")) + val cfg = nexusCfg.ebics + val currency = cfg.currency + val dialect = cfg.dialect + // Parse logs + for (log in logs) { + val content = Files.newInputStream(log) + val name = log.toString() + println(name) + if (name.contains("wssparam")) { + // Skip + } else if (name.contains("HAC")) { + parseCustomerAck(content) + } else if (name.contains("HKD")) { + EbicsAdministrative.parseHKD(content) + } else if (name.contains("pain.002")) { + parseCustomerPaymentStatusReport(content) + } else if ( + !name.contains("camt.052") && !name.contains("_C52_") && !name.contains("_Z01_") + ) { + parseTx(content, currency, dialect) + } + } } } } \ No newline at end of file