libeufin

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

commit c928ab9e189020c2d150d42ecb51ceb618412c19
parent 4725b87198c315c57a2d1f0f4a917715a69b4928
Author: Antoine A <>
Date:   Tue, 30 Apr 2024 11:53:15 +0900

Parse camt052 files for GLS bank

Diffstat:
Anexus/sample/platform/gls_camt052.xml | 315+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rnexus/sample/platform/gls.xml -> nexus/sample/platform/gls_camt053.xml | 0
Mnexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt | 15++++++++-------
Mnexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt | 8+++++++-
Mnexus/src/test/kotlin/Iso20022Test.kt | 41+++++++++++++++++++++++++++++++++++++++--
Mtestbench/src/main/kotlin/Main.kt | 3++-
6 files changed, 371 insertions(+), 11 deletions(-)

diff --git a/nexus/sample/platform/gls_camt052.xml b/nexus/sample/platform/gls_camt052.xml @@ -0,0 +1,314 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.052.001.02" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:camt.052.001.02 camt.052.001.02.xsd"> + <BkToCstmrAcctRpt> + <Rpt> + <Ntry> + <Amt Ccy="EUR">2.00</Amt> + <CdtDbtInd>DBIT</CdtDbtInd> + <Sts>BOOK</Sts> + <BookgDt> + <Dt>2024-04-18</Dt> + </BookgDt> + <AcctSvcrRef>2024041801514102000</AcctSvcrRef> + <BkTxCd> + <Domn> + <Cd>PMNT</Cd> + <Fmly> + <Cd>ICDT</Cd> + <SubFmlyCd>ESCT</SubFmlyCd> + </Fmly> + </Domn> + </BkTxCd> + <NtryDtls> + <TxDtls> + <Refs> + <MsgId>G059N0SR5V0WZ0XSFY1H92QBZ0</MsgId> + <PmtInfId>NOTPROVIDED</PmtInfId> + <EndToEndId>NOTPROVIDED</EndToEndId> + <TxId>2024041785403105090200000010000001</TxId> + </Refs> + <AmtDtls> + <TxAmt> + <Amt Ccy="EUR">2.00</Amt> + </TxAmt> + </AmtDtls> + <BkTxCd> + <Domn> + <Cd>PMNT</Cd> + <Fmly> + <Cd>ICDT</Cd> + <SubFmlyCd>ESCT</SubFmlyCd> + </Fmly> + </Domn> + <Prtry> + <Cd>NTRF+177+08381</Cd> + <Issr>DK</Issr> + </Prtry> + </BkTxCd> + <RltdPties> + <Dbtr> + <Nm>Mr Test</Nm> + </Dbtr> + <DbtrAcct> + <Id> + <IBAN>DE84500105177118117964</IBAN> + </Id> + </DbtrAcct> + <Cdtr> + <Nm>John Smith</Nm> + </Cdtr> + <CdtrAcct> + <Id> + <IBAN>DE20500105172419259181</IBAN> + </Id> + </CdtrAcct> + </RltdPties> + <RltdAgts> + <CdtrAgt> + <FinInstnId> + <BIC>BYLADEM1WOR</BIC> + </FinInstnId> + </CdtrAgt> + </RltdAgts> + <RmtInf> + <Ustrd>TestABC123</Ustrd> + </RmtInf> + </TxDtls> + </NtryDtls> + <AddtlNtryInf>Überweisungsauftrag</AddtlNtryInf> + </Ntry> + <Ntry> + <Amt Ccy="EUR">1.10</Amt> + <CdtDbtInd>DBIT</CdtDbtInd> + <Sts>BOOK</Sts> + <BookgDt> + <Dt>2024-04-18</Dt> + </BookgDt> + <ValDt> + <Dt>2024-04-18</Dt> + </ValDt> + <AcctSvcrRef>2024041810552821000</AcctSvcrRef> + <BkTxCd> + <Domn> + <Cd>PMNT</Cd> + <Fmly> + <Cd>ICDT</Cd> + <SubFmlyCd>ESCT</SubFmlyCd> + </Fmly> + </Domn> + <Prtry> + <Cd>NTRF+177+08381</Cd> + <Issr>DK</Issr> + </Prtry> + </BkTxCd> + <NtryDtls> + <TxDtls> + <Refs> + <MsgId>YF5QBARGQ0MNY0VK59S477VDG4</MsgId> + <PmtInfId>NOTPROVIDED</PmtInfId> + <EndToEndId>NOTPROVIDED</EndToEndId> + <TxId>2024041885917775090200000010000001</TxId> + </Refs> + <AmtDtls> + <TxAmt> + <Amt Ccy="EUR">1.10</Amt> + </TxAmt> + </AmtDtls> + <BkTxCd> + <Domn> + <Cd>PMNT</Cd> + <Fmly> + <Cd>ICDT</Cd> + <SubFmlyCd>ESCT</SubFmlyCd> + </Fmly> + </Domn> + <Prtry> + <Cd>NTRF+177+08381</Cd> + <Issr>DK</Issr> + </Prtry> + </BkTxCd> + <RltdPties> + <Dbtr> + <Nm>Mr Test</Nm> + </Dbtr> + <DbtrAcct> + <Id> + <IBAN>DE84500105177118117964</IBAN> + </Id> + </DbtrAcct> + <Cdtr> + <Nm>John Smith</Nm> + </Cdtr> + <CdtrAcct> + <Id> + <IBAN>DE20500105172419259181</IBAN> + </Id> + </CdtrAcct> + </RltdPties> + <RltdAgts> + <CdtrAgt> + <FinInstnId> + <BIC>INGDDEFFXXX</BIC> + </FinInstnId> + </CdtrAgt> + </RltdAgts> + <RmtInf> + <Ustrd>This should fail because dummy</Ustrd> + </RmtInf> + </TxDtls> + </NtryDtls> + <AddtlNtryInf>Überweisungsauftrag</AddtlNtryInf> + </Ntry> + <Ntry> + <Amt Ccy="EUR">3.00</Amt> + <CdtDbtInd>CRDT</CdtDbtInd> + <Sts>BOOK</Sts> + <BookgDt> + <Dt>2024-04-12</Dt> + </BookgDt> + <AcctSvcrRef>2024041210041357000</AcctSvcrRef> + <BkTxCd> + <Domn> + <Cd>PMNT</Cd> + <Fmly> + <Cd>RCDT</Cd> + <SubFmlyCd>ESCT</SubFmlyCd> + </Fmly> + </Domn> + </BkTxCd> + <NtryDtls> + <TxDtls> + <Refs> + <EndToEndId>NOTPROVIDED</EndToEndId> + <TxId>BYLADEM1WOR-G2910276709458A2</TxId> + </Refs> + <AmtDtls> + <TxAmt> + <Amt Ccy="EUR">3.00</Amt> + </TxAmt> + </AmtDtls> + <BkTxCd> + <Domn> + <Cd>PMNT</Cd> + <Fmly> + <Cd>RCDT</Cd> + <SubFmlyCd>ESCT</SubFmlyCd> + </Fmly> + </Domn> + </BkTxCd> + <RltdPties> + <Dbtr> + <Nm>John Smith</Nm> + </Dbtr> + <DbtrAcct> + <Id> + <IBAN>DE84500105177118117964</IBAN> + </Id> + </DbtrAcct> + <Cdtr> + <Nm>Mr Test</Nm> + </Cdtr> + <CdtrAcct> + <Id> + <IBAN>DE20500105172419259181</IBAN> + </Id> + </CdtrAcct> + </RltdPties> + <RltdAgts> + <DbtrAgt> + <FinInstnId> + <BIC>BYLADEM1WOR</BIC> + </FinInstnId> + </DbtrAgt> + </RltdAgts> + <RmtInf> + <Ustrd>Taler FJDQ7W6G7NWX4H9M1MKA12090FRC9K7DA6N0FANDZZFXTR6QHX5G Test.,-</Ustrd> + </RmtInf> + </TxDtls> + </NtryDtls> + <AddtlNtryInf>Überweisungsgutschr.</AddtlNtryInf> + </Ntry> + <Ntry> + <Amt Ccy="EUR">1.10</Amt> + <CdtDbtInd>CRDT</CdtDbtInd> + <Sts>BOOK</Sts> + <BookgDt> + <Dt>2024-04-12</Dt> + </BookgDt> + <AcctSvcrRef>2024041210041357000</AcctSvcrRef> + <BkTxCd> + <Domn> + <Cd>PMNT</Cd> + <Fmly> + <Cd>ICDT</Cd> + <SubFmlyCd>RRTN</SubFmlyCd> + </Fmly> + </Domn> + </BkTxCd> + <NtryDtls> + <TxDtls> + <Refs> + <EndToEndId>G27KNKZAR5DV7HRB085YMA9GB4</EndToEndId> + <TxId>2024042288942205090200000010000001</TxId> + </Refs> + <AmtDtls> + <TxAmt> + <Amt Ccy="EUR">1.10</Amt> + </TxAmt> + </AmtDtls> + <BkTxCd> + <Domn> + <Cd>PMNT</Cd> + <Fmly> + <Cd>ICDT</Cd> + <SubFmlyCd>RRTN</SubFmlyCd> + </Fmly> + </Domn> + </BkTxCd> + <RltdPties> + <Dbtr> + <Nm>John Smith</Nm> + </Dbtr> + <DbtrAcct> + <Id> + <IBAN>DE84500105177118117964</IBAN> + </Id> + </DbtrAcct> + <Cdtr> + <Nm>Mr Test</Nm> + </Cdtr> + <CdtrAcct> + <Id> + <IBAN>DE20500105172419259181</IBAN> + </Id> + </CdtrAcct> + </RltdPties> + <RtrInf> + <OrgnlBkTxCd> + <Prtry> + <Cd>116</Cd> + <Issr>DK</Issr> + </Prtry> + </OrgnlBkTxCd> + <Orgtr> + <Id> + <OrgId> + <BICOrBEI>GENODEM1GLS</BICOrBEI> + </OrgId> + </Id> + </Orgtr> + <Rsn> + <Cd>AC01</Cd> + </Rsn> + <AddtlInf>IBAN ...</AddtlInf> + </RtrInf> + </TxDtls> + </NtryDtls> + <AddtlNtryInf>Überweisungsgutschr.</AddtlNtryInf> + </Ntry> + </Rpt> + </BkToCstmrAcctRpt> +</Document> + +\ No newline at end of file diff --git a/nexus/sample/platform/gls.xml b/nexus/sample/platform/gls_camt053.xml diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt @@ -156,7 +156,7 @@ private suspend fun ingestDocument( whichDocument: SupportedDocument ) { when (whichDocument) { - SupportedDocument.CAMT_053, SupportedDocument.CAMT_054 -> { + SupportedDocument.CAMT_052, SupportedDocument.CAMT_053, SupportedDocument.CAMT_054 -> { try { parseTx(xml, cfg.currency, cfg.dialect).forEach { if (cfg.fetch.ignoreBefore != null && it.executionTime < cfg.fetch.ignoreBefore) { @@ -212,10 +212,6 @@ private suspend fun ingestDocument( db.initiated.bankMessage(status.msgId, msg) } } - SupportedDocument.CAMT_052 -> { - // TODO parsing - // TODO ingesting - } } } @@ -307,15 +303,18 @@ enum class EbicsDocument { acknowledgement, /// Payment status - CustomerPaymentStatusReport pain.002 status, - /// Debit & credit notifications - BankToCustomerDebitCreditNotification camt.054 - notification, + /// Account intraday reports - BankToCustomerAccountReport camt.052 + report, /// Account statements - BankToCustomerStatement camt.053 statement, + /// Debit & credit notifications - BankToCustomerDebitCreditNotification camt.054 + notification, ; fun shortDescription(): String = when (this) { acknowledgement -> "EBICS acknowledgement" status -> "Payment status" + report -> "Account intraday reports" statement -> "Account statements" notification -> "Debit & credit notifications" } @@ -323,6 +322,7 @@ enum class EbicsDocument { fun fullDescription(): String = when (this) { acknowledgement -> "EBICS acknowledgement - CustomerAcknowledgement HAC pain.002" status -> "Payment status - CustomerPaymentStatusReport pain.002" + report -> "Account intraday reports - BankToCustomerAccountReport camt.052" statement -> "Account statements - BankToCustomerStatement camt.053" notification -> "Debit & credit notifications - BankToCustomerDebitCreditNotification camt.054" } @@ -330,6 +330,7 @@ enum class EbicsDocument { fun doc(): SupportedDocument = when (this) { acknowledgement -> SupportedDocument.PAIN_002_LOGS status -> SupportedDocument.PAIN_002 + report -> SupportedDocument.CAMT_052 statement -> SupportedDocument.CAMT_053 notification -> SupportedDocument.CAMT_054 } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Iso20022.kt @@ -385,7 +385,7 @@ fun parseTx( XmlDestructor.fromStream(notifXml, "Document") { when (dialect) { Dialect.gls -> { - opt("BkToCstmrStmt")?.each("Stmt") { // Camt.053 + fun XmlDestructor.parseGlsInner() { opt("Acct") { // Sanity check on currency and IBAN ? } @@ -440,6 +440,12 @@ fun parseTx( } } } + opt("BkToCstmrAcctRpt")?.each("Rpt") { // Camt.052 + parseGlsInner() + } + opt("BkToCstmrStmt")?.each("Stmt") { // Camt.053 + parseGlsInner() + } } Dialect.postfinance -> { opt("BkToCstmrStmt")?.each("Stmt") { // Camt.053 diff --git a/nexus/src/test/kotlin/Iso20022Test.kt b/nexus/src/test/kotlin/Iso20022Test.kt @@ -91,8 +91,45 @@ class Iso20022Test { } @Test - fun gls() { - val content = Files.newInputStream(Path("sample/platform/gls.xml")) + fun gls_camt052() { + val content = Files.newInputStream(Path("sample/platform/gls_camt052.xml")) + val txs = parseTx(content, "EUR", Dialect.gls) + assertEquals( + listOf( + OutgoingPayment( + messageId = "G059N0SR5V0WZ0XSFY1H92QBZ0", + amount = TalerAmount("EUR:2"), + wireTransferSubject = "TestABC123", + executionTime = instant("2024-04-18"), + creditPaytoUri = "payto://iban/DE20500105172419259181?receiver-name=John+Smith" + ), + OutgoingPayment( + messageId = "YF5QBARGQ0MNY0VK59S477VDG4", + amount = TalerAmount("EUR:1.1"), + wireTransferSubject = "This should fail because dummy", + executionTime = instant("2024-04-18"), + creditPaytoUri = "payto://iban/DE20500105172419259181?receiver-name=John+Smith" + ), + IncomingPayment( + bankId = "BYLADEM1WOR-G2910276709458A2", + amount = TalerAmount("EUR:3"), + wireTransferSubject = "Taler FJDQ7W6G7NWX4H9M1MKA12090FRC9K7DA6N0FANDZZFXTR6QHX5G Test.,-", + executionTime = instant("2024-04-12"), + debitPaytoUri = "payto://iban/DE84500105177118117964?receiver-name=John+Smith" + ), + Reversal( + msgId = "G27KNKZAR5DV7HRB085YMA9GB4", + reason = "IncorrectAccountNumber 'Format of the account number specified is not correct' - 'IBAN ...'", + executionTime = instant("2024-04-12") + ) + ), + txs + ) + } + + @Test + fun gls_camt053() { + val content = Files.newInputStream(Path("sample/platform/gls_camt053.xml")) val txs = parseTx(content, "EUR", Dialect.gls) assertEquals( listOf( diff --git a/testbench/src/main/kotlin/Main.kt b/testbench/src/main/kotlin/Main.kt @@ -135,7 +135,7 @@ class Cli : CliktCommand("Run integration tests on banks provider") { val payto = benchCfg.payto[currency] ?: dummyPayto ?: throw Exception("Missing test payto for $currency") - val recoverDoc = "notification statement" + val recoverDoc = "report statement notification" runBlocking { step("Init ${kind.name}") @@ -160,6 +160,7 @@ class Cli : CliktCommand("Run integration tests on banks provider") { put("fetch", "Fetch all documents", "ebics-fetch $ebicsFlags") put("ack", "Fetch CustomerAcknowledgement", "ebics-fetch $ebicsFlags acknowledgement") put("status", "Fetch CustomerPaymentStatusReport", "ebics-fetch $ebicsFlags status") + put("report", "Fetch BankToCustomerAccountReport", "ebics-fetch $ebicsFlags report") put("notification", "Fetch BankToCustomerDebitCreditNotification", "ebics-fetch $ebicsFlags notification") put("statement", "Fetch BankToCustomerStatement", "ebics-fetch $ebicsFlags statement") put("submit", "Submit pending transactions", "ebics-submit $ebicsFlags")