libeufin

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

commit 5315d262be9ac369d375943d4b3c7f3e5c897a04
parent b771f6976c8f541f23dc5b8f50725174837d63f1
Author: Antoine A <>
Date:   Mon,  2 Dec 2024 13:21:19 +0100

nexus: parse maerki_baumann debit confirmation

Diffstat:
Mnexus/sample/platform/maerki_baumann_camt053.xml | 255+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mnexus/src/main/kotlin/tech/libeufin/nexus/iso20022/camt.kt | 43++++++++++++++++++++++++++++++-------------
Mnexus/src/test/kotlin/Iso20022Test.kt | 32++++++++++++++++++++++++++++++++
Mnexus/src/test/kotlin/RegistrationTest.kt | 49+++++++++++++++++++++++++++++++++++++++++++++++--
4 files changed, 364 insertions(+), 15 deletions(-)

diff --git a/nexus/sample/platform/maerki_baumann_camt053.xml b/nexus/sample/platform/maerki_baumann_camt053.xml @@ -196,6 +196,261 @@ </NtryDtls> <AddtlNtryInf>Bank clearing payment Grothoff Hans</AddtlNtryInf> </Ntry> + <Ntry> + <Amt Ccy="CHF">.1</Amt> + <CdtDbtInd>DBIT</CdtDbtInd> + <RvslInd>false</RvslInd> + <Sts> + <Cd>BOOK</Cd> + </Sts> + <BookgDt> + <Dt>2024-11-27</Dt> + </BookgDt> + <ValDt> + <Dt>2024-11-27</Dt> + </ValDt> + <BkTxCd> + <Domn> + <Cd>PMNT</Cd> + <Fmly> + <Cd>ICDT</Cd> + <SubFmlyCd>OTHR</SubFmlyCd> + </Fmly> + </Domn> + </BkTxCd> + <NtryDtls> + <TxDtls> + <Refs> + <MsgId>BATCH_SINGLE_REPORTING</MsgId> + <AcctSvcrRef>ZV20241121/773541/1</AcctSvcrRef> + <PmtInfId>NOTPROVIDED</PmtInfId> + <InstrId>5IBJZOWESQGPCSOXSNNBBY49ZURI5W7Q4H</InstrId> + <EndToEndId>5IBJZOWESQGPCSOXSNNBBY49ZURI5W7Q4H</EndToEndId> + <UETR>30c030ad-01cc-4cbf-b2d4-428cccaa1a85</UETR> + </Refs> + <Amt Ccy="CHF">.1</Amt> + <CdtDbtInd>DBIT</CdtDbtInd> + <RltdPties> + <Cdtr> + <Pty> + <Nm>Grothoff Hans</Nm> + </Pty> + </Cdtr> + <CdtrAcct> + <Id> + <IBAN>CH7389144832588726658</IBAN> + </Id> + </CdtrAcct> + </RltdPties> + <RmtInf> + <Ustrd>multi 0 2024-11-21T15:21:59.8859234 63Z</Ustrd> + </RmtInf> + <AddtlTxInf>PAIN-Auftrag Grothoff Hans</AddtlTxInf> + </TxDtls> + </NtryDtls> + <AddtlNtryInf>PAIN-Auftrag Grothoff Hans</AddtlNtryInf> + </Ntry> + <Ntry> + <Amt Ccy="CHF">.13</Amt> + <CdtDbtInd>DBIT</CdtDbtInd> + <RvslInd>false</RvslInd> + <Sts> + <Cd>BOOK</Cd> + </Sts> + <BookgDt> + <Dt>2024-11-27</Dt> + </BookgDt> + <ValDt> + <Dt>2024-11-27</Dt> + </ValDt> + <BkTxCd> + <Domn> + <Cd>PMNT</Cd> + <Fmly> + <Cd>ICDT</Cd> + <SubFmlyCd>OTHR</SubFmlyCd> + </Fmly> + </Domn> + </BkTxCd> + <NtryDtls> + <Btch> + <NbOfTxs>1</NbOfTxs> + <TtlAmt Ccy="CHF">.13</TtlAmt> + <CdtDbtInd>DBIT</CdtDbtInd> + </Btch> + <TxDtls> + <Refs> + <MsgId>BATCH_SINGLE_REPORTING</MsgId> + <AcctSvcrRef>ZV20241121/773541/4</AcctSvcrRef> + <PmtInfId>NOTPROVIDED</PmtInfId> + <InstrId>XZ15UR0XU52QWI7Q4XB88EDS44PLH7DYXH</InstrId> + <EndToEndId>XZ15UR0XU52QWI7Q4XB88EDS44PLH7DYXH</EndToEndId> + <UETR>b14d4554-49a0-4c99-a49e-5ebb4fa177d1</UETR> + </Refs> + <Amt Ccy="CHF">.13</Amt> + <CdtDbtInd>DBIT</CdtDbtInd> + <AmtDtls> + <InstdAmt> + <Amt Ccy="CHF">.13</Amt> + </InstdAmt> + <TxAmt> + <Amt Ccy="CHF">.13</Amt> + </TxAmt> + </AmtDtls> + <RltdPties> + <Cdtr> + <Pty> + <Nm>Grothoff Hans</Nm> + </Pty> + </Cdtr> + <CdtrAcct> + <Id> + <IBAN>CH7389144832588726658</IBAN> + </Id> + </CdtrAcct> + </RltdPties> + <RmtInf> + <Ustrd>multi 3 2024-11-21T15:21:59.8859234 63Z</Ustrd> + </RmtInf> + <AddtlTxInf>PAIN-Auftrag Grothoff Hans</AddtlTxInf> + </TxDtls> + </NtryDtls> + <AddtlNtryInf>PAIN-Auftrag Grothoff Hans</AddtlNtryInf> + </Ntry> + <Ntry> + <Amt Ccy="CHF">.12</Amt> + <CdtDbtInd>DBIT</CdtDbtInd> + <RvslInd>false</RvslInd> + <Sts> + <Cd>BOOK</Cd> + </Sts> + <BookgDt> + <Dt>2024-11-27</Dt> + </BookgDt> + <ValDt> + <Dt>2024-11-27</Dt> + </ValDt> + <BkTxCd> + <Domn> + <Cd>PMNT</Cd> + <Fmly> + <Cd>ICDT</Cd> + <SubFmlyCd>OTHR</SubFmlyCd> + </Fmly> + </Domn> + </BkTxCd> + <NtryDtls> + <Btch> + <NbOfTxs>1</NbOfTxs> + <TtlAmt Ccy="CHF">.12</TtlAmt> + <CdtDbtInd>DBIT</CdtDbtInd> + </Btch> + <TxDtls> + <Refs> + <MsgId>BATCH_SINGLE_REPORTING</MsgId> + <AcctSvcrRef>ZV20241121/773541/3</AcctSvcrRef> + <PmtInfId>NOTPROVIDED</PmtInfId> + <InstrId>A09R35EW0359SZ51464E7TC37A0P2CBK04</InstrId> + <EndToEndId>A09R35EW0359SZ51464E7TC37A0P2CBK04</EndToEndId> + <UETR>fa3869de-e29b-4163-9ffa-b9c6d6b70d25</UETR> + </Refs> + <Amt Ccy="CHF">.12</Amt> + <CdtDbtInd>DBIT</CdtDbtInd> + <AmtDtls> + <InstdAmt> + <Amt Ccy="CHF">.12</Amt> + </InstdAmt> + <TxAmt> + <Amt Ccy="CHF">.12</Amt> + </TxAmt> + </AmtDtls> + <RltdPties> + <Cdtr> + <Pty> + <Nm>Grothoff Hans</Nm> + </Pty> + </Cdtr> + <CdtrAcct> + <Id> + <IBAN>CH7389144832588726658</IBAN> + </Id> + </CdtrAcct> + </RltdPties> + <RmtInf> + <Ustrd>multi 2 2024-11-21T15:21:59.8859234 63Z</Ustrd> + </RmtInf> + <AddtlTxInf>PAIN-Auftrag Grothoff Hans</AddtlTxInf> + </TxDtls> + </NtryDtls> + <AddtlNtryInf>PAIN-Auftrag Grothoff Hans</AddtlNtryInf> + </Ntry> + <Ntry> + <Amt Ccy="CHF">.11</Amt> + <CdtDbtInd>DBIT</CdtDbtInd> + <RvslInd>false</RvslInd> + <Sts> + <Cd>BOOK</Cd> + </Sts> + <BookgDt> + <Dt>2024-11-27</Dt> + </BookgDt> + <ValDt> + <Dt>2024-11-27</Dt> + </ValDt> + <BkTxCd> + <Domn> + <Cd>PMNT</Cd> + <Fmly> + <Cd>ICDT</Cd> + <SubFmlyCd>OTHR</SubFmlyCd> + </Fmly> + </Domn> + </BkTxCd> + <NtryDtls> + <Btch> + <NbOfTxs>1</NbOfTxs> + <TtlAmt Ccy="CHF">.11</TtlAmt> + <CdtDbtInd>DBIT</CdtDbtInd> + </Btch> + <TxDtls> + <Refs> + <MsgId>BATCH_SINGLE_REPORTING</MsgId> + <AcctSvcrRef>ZV20241121/773541/2</AcctSvcrRef> + <PmtInfId>NOTPROVIDED</PmtInfId> + <InstrId>UYXZ78LE9KAIMBY6UNXFYT1K8KNY8VLZLT</InstrId> + <EndToEndId>UYXZ78LE9KAIMBY6UNXFYT1K8KNY8VLZLT</EndToEndId> + <UETR>93b01b18-36f6-4e42-a3bf-e4341b7e64cf</UETR> + </Refs> + <Amt Ccy="CHF">.11</Amt> + <CdtDbtInd>DBIT</CdtDbtInd> + <AmtDtls> + <InstdAmt> + <Amt Ccy="CHF">.11</Amt> + </InstdAmt> + <TxAmt> + <Amt Ccy="CHF">.11</Amt> + </TxAmt> + </AmtDtls> + <RltdPties> + <Cdtr> + <Pty> + <Nm>Grothoff Hans</Nm> + </Pty> + </Cdtr> + <CdtrAcct> + <Id> + <IBAN>CH7389144832588726658</IBAN> + </Id> + </CdtrAcct> + </RltdPties> + <RmtInf> + <Ustrd>multi 1 2024-11-21T15:21:59.8859234 63Z</Ustrd> + </RmtInf> + <AddtlTxInf>PAIN-Auftrag Grothoff Hans</AddtlTxInf> + </TxDtls> + </NtryDtls> + <AddtlNtryInf>PAIN-Auftrag Grothoff Hans</AddtlNtryInf> + </Ntry> </Stmt> </BkToCstmrStmt> </Document> \ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/camt.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/camt.kt @@ -501,19 +501,36 @@ fun parseTx(notifXml: InputStream, dialect: Dialect): List<AccountTransactions> val code = optBankTransactionCode() ?: code val (amount, fee) = amountAndFee() val subject = wireTransferSubject() - if (!code.isReversal() && kind == Kind.CRDT) { - val bankId = opt("Refs")?.opt("UETR")?.text() - val debtorPayto = payto("Dbtr") - txsInfo.add(TxInfo.Credit( - ref = bankId ?: txRef ?: entryRef, - bookDate = bookDate, - bankId = bankId, - amount = amount, - subject = subject, - debtorPayto = debtorPayto, - code = code, - creditFee = fee - )) + if (!code.isReversal()) { + when (kind) { + Kind.CRDT -> { + val bankId = opt("Refs")?.opt("UETR")?.text() + val debtorPayto = payto("Dbtr") + txsInfo.add(TxInfo.Credit( + ref = bankId ?: txRef ?: entryRef, + bookDate = bookDate, + bankId = bankId, + amount = amount, + subject = subject, + debtorPayto = debtorPayto, + code = code, + creditFee = fee + )) + } + Kind.DBIT -> { + val outgoingId = outgoingId() + val creditorPayto = payto("Cdtr") + txsInfo.add(TxInfo.Debit( + ref = outgoingId.ref() ?: txRef ?: entryRef, + bookDate = bookDate, + id = outgoingId, + amount = amount, + subject = subject, + creditorPayto = creditorPayto, + code = code + )) + } + } } } } diff --git a/nexus/src/test/kotlin/Iso20022Test.kt b/nexus/src/test/kotlin/Iso20022Test.kt @@ -373,6 +373,38 @@ class Iso20022Test { subject = "Random subject", executionTime = dateToInstant("2024-11-04"), debtorPayto = ibanPayto("CH7389144832588726658", "Mr Test") + ), + OutgoingPayment( + endToEndId = "5IBJZOWESQGPCSOXSNNBBY49ZURI5W7Q4H", + msgId = "BATCH_SINGLE_REPORTING", + amount = TalerAmount("CHF:0.1"), + subject = "multi 0 2024-11-21T15:21:59.8859234 63Z", + executionTime = dateToInstant("2024-11-27"), + creditorPayto = ibanPayto("CH7389144832588726658", "Grothoff Hans") + ), + OutgoingPayment( + endToEndId = "XZ15UR0XU52QWI7Q4XB88EDS44PLH7DYXH", + msgId = "BATCH_SINGLE_REPORTING", + amount = TalerAmount("CHF:0.13"), + subject = "multi 3 2024-11-21T15:21:59.8859234 63Z", + executionTime = dateToInstant("2024-11-27"), + creditorPayto = ibanPayto("CH7389144832588726658", "Grothoff Hans") + ), + OutgoingPayment( + endToEndId = "A09R35EW0359SZ51464E7TC37A0P2CBK04", + msgId = "BATCH_SINGLE_REPORTING", + amount = TalerAmount("CHF:0.12"), + subject = "multi 2 2024-11-21T15:21:59.8859234 63Z", + executionTime = dateToInstant("2024-11-27"), + creditorPayto = ibanPayto("CH7389144832588726658", "Grothoff Hans") + ), + OutgoingPayment( + endToEndId = "UYXZ78LE9KAIMBY6UNXFYT1K8KNY8VLZLT", + msgId = "BATCH_SINGLE_REPORTING", + amount = TalerAmount("CHF:0.11"), + subject = "multi 1 2024-11-21T15:21:59.8859234 63Z", + executionTime = dateToInstant("2024-11-27"), + creditorPayto = ibanPayto("CH7389144832588726658", "Grothoff Hans") ) ) )) diff --git a/nexus/src/test/kotlin/RegistrationTest.kt b/nexus/src/test/kotlin/RegistrationTest.kt @@ -405,12 +405,28 @@ class RegistrationTest { /** Maerki Baumann dialect test */ @Test fun maerki_baumann() = setup("maerki_baumann.conf") { db, cfg -> + db.batches(mapOf( + "BATCH_SINGLE_REPORTING" to listOf( + genInitPay("5IBJZOWESQGPCSOXSNNBBY49ZURI5W7Q4H"), + genInitPay("XZ15UR0XU52QWI7Q4XB88EDS44PLH7DYXH"), + genInitPay("A09R35EW0359SZ51464E7TC37A0P2CBK04"), + genInitPay("UYXZ78LE9KAIMBY6UNXFYT1K8KNY8VLZLT"), + ), + )) + // Register camt files db.register(cfg, "sample/platform/maerki_baumann_camt053.xml", OrderDoc.statement) // Check state db.check( - status = emptyMap(), + status = mapOf( + "BATCH_SINGLE_REPORTING" to Pair(SubmissionState.success, mapOf( + "5IBJZOWESQGPCSOXSNNBBY49ZURI5W7Q4H" to SubmissionState.success, + "XZ15UR0XU52QWI7Q4XB88EDS44PLH7DYXH" to SubmissionState.success, + "A09R35EW0359SZ51464E7TC37A0P2CBK04" to SubmissionState.success, + "UYXZ78LE9KAIMBY6UNXFYT1K8KNY8VLZLT" to SubmissionState.success, + )), + ), incoming = listOf( IncomingPayment( bankId = "adbe4a5a-6cea-4263-b259-8ab964561a32", @@ -429,7 +445,36 @@ class RegistrationTest { debtorPayto = ibanPayto("CH7389144832588726658", "Mr Test") ) ), - outgoing = emptyList() + outgoing = listOf( + OutgoingPayment( + endToEndId = "5IBJZOWESQGPCSOXSNNBBY49ZURI5W7Q4H", + amount = TalerAmount("CHF:0.1"), + subject = "multi 0 2024-11-21T15:21:59.8859234 63Z", + executionTime = dateToInstant("2024-11-27"), + creditorPayto = ibanPayto("CH7389144832588726658", "Grothoff Hans") + ), + OutgoingPayment( + endToEndId = "XZ15UR0XU52QWI7Q4XB88EDS44PLH7DYXH", + amount = TalerAmount("CHF:0.13"), + subject = "multi 3 2024-11-21T15:21:59.8859234 63Z", + executionTime = dateToInstant("2024-11-27"), + creditorPayto = ibanPayto("CH7389144832588726658", "Grothoff Hans") + ), + OutgoingPayment( + endToEndId = "A09R35EW0359SZ51464E7TC37A0P2CBK04", + amount = TalerAmount("CHF:0.12"), + subject = "multi 2 2024-11-21T15:21:59.8859234 63Z", + executionTime = dateToInstant("2024-11-27"), + creditorPayto = ibanPayto("CH7389144832588726658", "Grothoff Hans") + ), + OutgoingPayment( + endToEndId = "UYXZ78LE9KAIMBY6UNXFYT1K8KNY8VLZLT", + amount = TalerAmount("CHF:0.11"), + subject = "multi 1 2024-11-21T15:21:59.8859234 63Z", + executionTime = dateToInstant("2024-11-27"), + creditorPayto = ibanPayto("CH7389144832588726658", "Grothoff Hans") + ) + ) ) } } \ No newline at end of file