libeufin

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

commit 9adbdbe59b7a865fa61881ba4a3c60f847bfa413
parent 9076c7a1b22c87e27feb2e1a99a22d6566ad7034
Author: MS <ms@taler.net>
Date:   Tue, 30 May 2023 10:49:24 +0200

PostFinance connection.

Testing the ingestion (including deduplication) of
outgoing payments.

Diffstat:
Mnexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt | 5++++-
Mnexus/src/test/kotlin/Iso20022Test.kt | 49+++++++++++++++++++++++++++++++++++++++++++++----
Mnexus/src/test/kotlin/MakeEnv.kt | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mnexus/src/test/kotlin/PostFinance.kt | 12++----------
4 files changed, 182 insertions(+), 17 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/Iso20022.kt @@ -972,6 +972,9 @@ fun ingestCamtMessageIntoAccount( // https://bugs.gnunet.org/view.php?id=6381 continue@txloop } + /* Checking for the unstructured remittance information before + storing the payment in the database. */ + val paymentSubject = entry.getSingletonSubject() // throws if not found. val rawEntity = NexusBankTransactionEntity.new { bankAccount = acct accountTransactionId = paymentUid @@ -983,7 +986,7 @@ fun ingestCamtMessageIntoAccount( } rawEntity.flush() newTransactions++ - newPaymentsLog += "\n- ${entry.getSingletonSubject()}" + newPaymentsLog += "\n- $paymentSubject" // This block tries to acknowledge a former outgoing payment as booked. if (singletonBatchedTransaction.creditDebitIndicator == CreditDebitIndicator.DBIT) { diff --git a/nexus/src/test/kotlin/Iso20022Test.kt b/nexus/src/test/kotlin/Iso20022Test.kt @@ -9,7 +9,8 @@ import org.junit.Ignore import org.junit.Test import org.w3c.dom.Document import poFiCamt052 -import poFiCamt054_2019 +import poFiCamt054_2019_incoming +import poFiCamt054_2019_outgoing import prepNexusDb import tech.libeufin.nexus.bankaccount.getBankAccount import tech.libeufin.nexus.iso20022.* @@ -119,23 +120,63 @@ class Iso20022Test { @Test fun parsePoFiCamt054() { - val doc = XMLUtil.parseStringIntoDom(poFiCamt054_2019) + val doc = XMLUtil.parseStringIntoDom(poFiCamt054_2019_incoming) parseCamtMessage(doc, dialect = "pf") } - // TODO: test deduplication here. + /** + * Testing how outgoing payments get ingested and how their + * deduplication logic reacts, given that sometimes camt.054 + * was seen without the AcctSvcrRef. + */ + @Test + fun ingestPoFiCamt054_outgoing() { + val doc = XMLUtil.parseStringIntoDom(poFiCamt054_2019_outgoing) + withTestDatabase { + prepNexusDb() + transaction { assert(NexusBankTransactionEntity.all().count() == 0L) } + ingestCamtMessageIntoAccount( + "foo", + doc, + FetchLevel.NOTIFICATION, + dialect = "pf" + ) + transaction { assert(NexusBankTransactionEntity.all().count() == 1L) } + // Checking that the payment doesn't get stored twice. + ingestCamtMessageIntoAccount( + "foo", + doc, + FetchLevel.NOTIFICATION, + dialect = "pf" + ) + transaction { assert(NexusBankTransactionEntity.all().count() == 1L) } + } + } @Test fun ingestPoFiCamt054() { - val doc = XMLUtil.parseStringIntoDom(poFiCamt054_2019) + val doc = XMLUtil.parseStringIntoDom(poFiCamt054_2019_incoming) withTestDatabase { prepNexusDb() + // Checking that no transactions exist already in the database. + transaction { assert(NexusBankTransactionEntity.all().count() == 0L) } + ingestCamtMessageIntoAccount( + "foo", + doc, + FetchLevel.NOTIFICATION, + dialect = "pf" + ) + // Checking that now ONE transaction exist in the database. + transaction { assert(NexusBankTransactionEntity.all().count() == 1L) } + // Checking now that the same payment doesn't get ingested twice. ingestCamtMessageIntoAccount( "foo", doc, FetchLevel.NOTIFICATION, dialect = "pf" ) + // The count should have stayed the same. + transaction { assert(NexusBankTransactionEntity.all().count() == 1L) } } } // Checks that the 2019 pain.001 version validates. diff --git a/nexus/src/test/kotlin/MakeEnv.kt b/nexus/src/test/kotlin/MakeEnv.kt @@ -494,10 +494,140 @@ fun genNexusIncomingCamt( ) ) +val poFiCamt054_2019_outgoing: String = """ + <?xml version="1.0" encoding="UTF-8"?> + <Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.054.001.08" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:camt.054.001.08 file:///C:/Users/burkhalterl/Documents/Musterfiles%20ISOV19/Schemen/camt.054.001.08.xsd"> + <BkToCstmrDbtCdtNtfctn> + <GrpHdr> + <MsgId>20200618375204295372463</MsgId> + <CreDtTm>2022-03-10T23:40:14</CreDtTm> + <MsgPgntn> + <PgNb>1</PgNb> + <LastPgInd>true</LastPgInd> + </MsgPgntn> + <AddtlInf>SPS/2.0/PROD</AddtlInf> + </GrpHdr> + <Ntfctn> + <Id>20200618375204295372465</Id> + <CreDtTm>2022-03-10T23:40:14</CreDtTm> + <FrToDt> + <FrDtTm>2022-03-10T00:00:00</FrDtTm> + <ToDtTm>2022-03-10T23:59:59</ToDtTm> + </FrToDt> + <Acct> + <Id> + <IBAN>${FOO_USER_IBAN}</IBAN> + </Id> + <Ccy>CHF</Ccy> + <Ownr> + <Nm>Robert Schneider SA Grands magasins Biel/Bienne</Nm> + </Ownr> + </Acct> + <Ntry> + <NtryRef>CH2909000000250094239</NtryRef> + <Amt Ccy="CHF">522.10</Amt> + <CdtDbtInd>DBIT</CdtDbtInd> + <RvslInd>false</RvslInd> + <Sts> + <Cd>BOOK</Cd> + </Sts> + <BookgDt> + <Dt>2022-03-10</Dt> + </BookgDt> + <ValDt> + <Dt>2022-03-10</Dt> + </ValDt> + <AcctSvcrRef>1000000000000000</AcctSvcrRef> + <BkTxCd> + <Domn> + <Cd>PMNT</Cd> + <Fmly> + <Cd>RCDT</Cd> + <SubFmlyCd>ATXN</SubFmlyCd> + </Fmly> + </Domn> + </BkTxCd> + <NtryDtls> + <Btch> + <NbOfTxs>1</NbOfTxs> + </Btch> + <TxDtls> + <Refs> + <InstrId>1006265-25bbb3b1a</InstrId> + <EndToEndId>client-generated</EndToEndId> + <UETR>b009c997-97b3-4a9c-803c-d645a7276bf0</UETR> + <Prtry> + <Tp>00</Tp> + <Ref>00000000000000000000020</Ref> + </Prtry> + </Refs> + <Amt Ccy="CHF">522.10</Amt> + <CdtDbtInd>DBIT</CdtDbtInd> + <BkTxCd> + <Domn> + <Cd>PMNT</Cd> + <Fmly> + <Cd>RCDT</Cd> + <SubFmlyCd>ATXN</SubFmlyCd> + </Fmly> + </Domn> + </BkTxCd> + <RltdPties> + <Dbtr> + <Pty> + <Nm>Bernasconi Maria</Nm> + <PstlAdr> + <AdrLine>Place de la Gare 12</AdrLine> + <AdrLine>2502 Biel/Bienne</AdrLine> + </PstlAdr> + </Pty> + </Dbtr> + <DbtrAcct> + <Id> + <IBAN>CH5109000000250092291</IBAN> + </Id> + </DbtrAcct> + <CdtrAcct> + <Id> + <IBAN>CH2909000000250094239</IBAN> + </Id> + </CdtrAcct> + </RltdPties> + <RltdAgts> + <DbtrAgt> + <FinInstnId> + <BICFI>POFICHBEXXX</BICFI> + <Nm>POSTFINANCE AG</Nm> + <PstlAdr> + <AdrLine>MINGERSTRASSE 20</AdrLine> + <AdrLine>3030 BERNE</AdrLine> + </PstlAdr> + </FinInstnId> + </DbtrAgt> + </RltdAgts> + <RmtInf> + <Strd> + <AddtlRmtInf>?REJECT?0</AddtlRmtInf> + <AddtlRmtInf>?ERROR?000</AddtlRmtInf> + </Strd> + <Ustrd>Reserve pub.</Ustrd> + </RmtInf> + <RltdDts> + <AccptncDtTm>2022-03-10T20:00:00</AccptncDtTm> + </RltdDts> + </TxDtls> + </NtryDtls> + <AddtlNtryInf>GUTSCHRIFT AUFTRAGGEBER: Bernasconi Maria Place de la Gare 12 2502 Biel/Bienne REFERENZEN: NOTPROVIDED 1006265-25bbb3b1a 2000000000000000</AddtlNtryInf> + </Ntry> + </Ntfctn> + </BkToCstmrDbtCdtNtfctn> + </Document> +""".trimIndent() + // Comes from a "mit Sammelbuchung" sample. // "mit Einzelbuchung" sample didn't have the "Ustrd" // See: https://www.postfinance.ch/de/support/services/dokumente/musterfiles-fuer-geschaeftskunden.html -val poFiCamt054_2019: String = """ +val poFiCamt054_2019_incoming: String = """ <?xml version="1.0" encoding="UTF-8"?> <Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.054.001.08" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:camt.054.001.08 file:///C:/Users/burkhalterl/Documents/Musterfiles%20ISOV19/Schemen/camt.054.001.08.xsd"> <BkToCstmrDbtCdtNtfctn> @@ -627,7 +757,6 @@ val poFiCamt054_2019: String = """ </Ntfctn> </BkToCstmrDbtCdtNtfctn> </Document> - """.trimIndent() val poFiCamt054_2013: String = """ diff --git a/nexus/src/test/kotlin/PostFinance.kt b/nexus/src/test/kotlin/PostFinance.kt @@ -103,14 +103,6 @@ fun main() { fooBankAccount.iban = "CH9789144829733648596" } } - // uploadPain001Payment() // XE2 - // downloadPayment() // Z54. - /*runBlocking { - (ebicsConn as EbicsBankConnectionProtocol).fetchPaymentReceipt( - FetchSpecLatestJson(FetchLevel.RECEIPT, null), - HttpClient(), - "postfinance", - "foo" - ) - }*/ + // uploadQrrPayment() + // downloadPayment() } \ No newline at end of file