libeufin

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

commit cf71b2da383a9fb86d074eeb15c0c9d5fefd1f05
parent 19b8afec701f83903e01ca03a6a431616edc5e3f
Author: Antoine A <>
Date:   Mon,  9 Sep 2024 14:57:51 +0200

nexus: pain001 test

Diffstat:
Anexus/sample/platform/gls_pain001.xml | 2++
Anexus/sample/platform/postfinance_pain001.xml | 2++
Mnexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsSubmit.kt | 31+++++++++++++++++--------------
Mnexus/src/test/kotlin/IngestionTest.kt | 42+++++++++++++++++++++++++++++++++++++-----
Mnexus/src/test/kotlin/Iso20022Test.kt | 49++++++++++++++++++++++++++++++++++++++++++++++++-
5 files changed, 106 insertions(+), 20 deletions(-)

diff --git a/nexus/sample/platform/gls_pain001.xml b/nexus/sample/platform/gls_pain001.xml @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.09" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:pain.001.001.09 pain.001.001.09.xsd"><CstmrCdtTrfInitn><GrpHdr><MsgId>MESSAGE_ID</MsgId><CreDtTm>2024-09-09T00:00:00Z</CreDtTm><NbOfTxs>3</NbOfTxs><CtrlSum>47.32</CtrlSum><InitgPty><Nm>myname</Nm></InitgPty></GrpHdr><PmtInf><PmtInfId>NOTPROVIDED</PmtInfId><PmtMtd>TRF</PmtMtd><BtchBookg>false</BtchBookg><NbOfTxs>3</NbOfTxs><CtrlSum>47.32</CtrlSum><PmtTpInf><SvcLvl><Cd>SEPA</Cd></SvcLvl></PmtTpInf><ReqdExctnDt><Dt>2024-09-09Z</Dt></ReqdExctnDt><Dbtr><Nm>myname</Nm></Dbtr><DbtrAcct><Id><IBAN>CH7789144474425692816</IBAN></Id></DbtrAcct><DbtrAgt><FinInstnId><BICFI>BIC</BICFI></FinInstnId></DbtrAgt><ChrgBr>SLEV</ChrgBr><CdtTrfTxInf><PmtId><InstrId>TX_FIRST</InstrId><EndToEndId>TX_FIRST</EndToEndId></PmtId><Amt><InstdAmt Ccy="EUR">42</InstdAmt></Amt><Cdtr><Nm>Test</Nm></Cdtr><CdtrAcct><Id><IBAN>CH4189144589712575493</IBAN></Id></CdtrAcct><RmtInf><Ustrd>Test 42</Ustrd></RmtInf></CdtTrfTxInf><CdtTrfTxInf><PmtId><InstrId>TX_SECOND</InstrId><EndToEndId>TX_SECOND</EndToEndId></PmtId><Amt><InstdAmt Ccy="EUR">5.11</InstdAmt></Amt><Cdtr><Nm>Test</Nm></Cdtr><CdtrAcct><Id><IBAN>CH4189144589712575493</IBAN></Id></CdtrAcct><RmtInf><Ustrd>Test 5.11</Ustrd></RmtInf></CdtTrfTxInf><CdtTrfTxInf><PmtId><InstrId>TX_THIRD</InstrId><EndToEndId>TX_THIRD</EndToEndId></PmtId><Amt><InstdAmt Ccy="EUR">0.21</InstdAmt></Amt><Cdtr><Nm>Test</Nm></Cdtr><CdtrAcct><Id><IBAN>CH4189144589712575493</IBAN></Id></CdtrAcct><RmtInf><Ustrd>Test 0.21</Ustrd></RmtInf></CdtTrfTxInf></PmtInf></CstmrCdtTrfInitn></Document> +\ No newline at end of file diff --git a/nexus/sample/platform/postfinance_pain001.xml b/nexus/sample/platform/postfinance_pain001.xml @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.09" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:pain.001.001.09 pain.001.001.09.xsd"><CstmrCdtTrfInitn><GrpHdr><MsgId>MESSAGE_ID</MsgId><CreDtTm>2024-09-09T00:00:00Z</CreDtTm><NbOfTxs>3</NbOfTxs><CtrlSum>47.32</CtrlSum><InitgPty><Nm>myname</Nm></InitgPty></GrpHdr><PmtInf><PmtInfId>NOTPROVIDED</PmtInfId><PmtMtd>TRF</PmtMtd><BtchBookg>false</BtchBookg><NbOfTxs>3</NbOfTxs><CtrlSum>47.32</CtrlSum><PmtTpInf><SvcLvl><Cd>SDVA</Cd></SvcLvl></PmtTpInf><ReqdExctnDt><Dt>2024-09-09Z</Dt></ReqdExctnDt><Dbtr><Nm>myname</Nm></Dbtr><DbtrAcct><Id><IBAN>CH7789144474425692816</IBAN></Id></DbtrAcct><DbtrAgt><FinInstnId><BICFI>BIC</BICFI></FinInstnId></DbtrAgt><ChrgBr>SLEV</ChrgBr><CdtTrfTxInf><PmtId><InstrId>TX_FIRST</InstrId><EndToEndId>TX_FIRST</EndToEndId></PmtId><Amt><InstdAmt Ccy="EUR">42</InstdAmt></Amt><Cdtr><Nm>Test</Nm></Cdtr><CdtrAcct><Id><IBAN>CH4189144589712575493</IBAN></Id></CdtrAcct><RmtInf><Ustrd>Test 42</Ustrd></RmtInf></CdtTrfTxInf><CdtTrfTxInf><PmtId><InstrId>TX_SECOND</InstrId><EndToEndId>TX_SECOND</EndToEndId></PmtId><Amt><InstdAmt Ccy="EUR">5.11</InstdAmt></Amt><Cdtr><Nm>Test</Nm></Cdtr><CdtrAcct><Id><IBAN>CH4189144589712575493</IBAN></Id></CdtrAcct><RmtInf><Ustrd>Test 5.11</Ustrd></RmtInf></CdtTrfTxInf><CdtTrfTxInf><PmtId><InstrId>TX_THIRD</InstrId><EndToEndId>TX_THIRD</EndToEndId></PmtId><Amt><InstdAmt Ccy="EUR">0.21</InstdAmt></Amt><Cdtr><Nm>Test</Nm></Cdtr><CdtrAcct><Id><IBAN>CH4189144589712575493</IBAN></Id></CdtrAcct><RmtInf><Ustrd>Test 0.21</Ustrd></RmtInf></CdtTrfTxInf></PmtInf></CstmrCdtTrfInitn></Document> +\ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsSubmit.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsSubmit.kt @@ -30,21 +30,11 @@ import tech.libeufin.nexus.iso20022.* import java.time.Instant import kotlin.time.toKotlinDuration -/** - * Submit an initiated payments [batch] using [client]. - * - * Parse creditor IBAN account metadata then perform an EBICS direct credit - * - * Returns the orderID - */ -private suspend fun submitBatch( - client: EbicsClient, - batch: PaymentBatch -): String { - val msg = Pain001Msg( +fun batchToPain001Msg(account: IbanAccountMetadata, batch: PaymentBatch): Pain001Msg { + return Pain001Msg( messageId = batch.messageId, timestamp = batch.creationDate, - debtor = client.cfg.account, + debtor = account, sum = batch.sum, txs = batch.payments.map { payment -> val payto = Payto.parse(payment.creditPaytoUri).expectIban() @@ -61,7 +51,20 @@ private suspend fun submitBatch( ) } ) - +} + +/** + * Submit an initiated payments [batch] using [client]. + * + * Parse creditor IBAN account metadata then perform an EBICS direct credit + * + * Returns the orderID + */ +private suspend fun submitBatch( + client: EbicsClient, + batch: PaymentBatch +): String { + val msg = batchToPain001Msg(client.cfg.account, batch) val xml = createPain001( msg = msg, dialect = client.cfg.dialect diff --git a/nexus/src/test/kotlin/IngestionTest.kt b/nexus/src/test/kotlin/IngestionTest.kt @@ -27,23 +27,25 @@ import tech.libeufin.nexus.ebics.* import tech.libeufin.nexus.iso20022.* import java.nio.file.Files import java.time.Instant -import kotlin.io.path.Path +import kotlin.io.path.* import kotlin.test.* /** End-to-end test for XML file ingestion */ class IngestionTest { /** Register batches of initiated payments for reconcciliation */ - suspend fun Database.batches(batches: Map<String, List<InitiatedPayment>>) { + suspend fun Database.batches(batches: Map<String, List<InitiatedPayment>>): List<PaymentBatch> { + val tmp = mutableListOf<PaymentBatch>() for ((name, txs) in batches) { for (tx in txs) { initiated.create(tx) } this.initiated.batch(Instant.now(), name) - val tmp = this.initiated.submittable("EUR") - assertEquals(1, tmp.size) - this.initiated.batchSubmissionSuccess(tmp[0].id, Instant.now(), name.replace("BATCH", "ORDER")) + val (batch) = this.initiated.submittable("EUR") + this.initiated.batchSubmissionSuccess(batch.id, Instant.now(), name.replace("BATCH", "ORDER")) + tmp.add(batch) } + return tmp } /** Ingest an XML sample into the database */ @@ -149,6 +151,36 @@ class IngestionTest { assertContentEquals(outgoing, outgoing_tx) } + @Test + fun pain001() = setup { db, cfg -> + val (batch) = db.batches(mapOf( + "MESSAGE_ID" to listOf( + genInitPay( + endToEndId = "TX_FIRST", + amount = "EUR:42", + subject = "Test 42", + ), + genInitPay( + endToEndId = "TX_SECOND", + amount = "EUR:5.11", + subject = "Test 5.11", + ), + genInitPay( + endToEndId = "TX_THIRD", + amount = "EUR:0.21", + subject = "Test 0.21", + ), + ), + )) + val msg = batchToPain001Msg(cfg.ebics.account, batch).copy(timestamp = dateToInstant("2024-09-09"),) + for (dialect in Dialect.entries) { + assertContentEquals( + Path("sample/platform/${dialect}_pain001.xml").readBytes(), + createPain001(msg, dialect) + ) + } + } + /** HAC order id test */ @Test fun hac() = setup { db, cfg -> diff --git a/nexus/src/test/kotlin/Iso20022Test.kt b/nexus/src/test/kotlin/Iso20022Test.kt @@ -19,13 +19,60 @@ import org.junit.Test import tech.libeufin.common.* -import tech.libeufin.nexus.ebics.Dialect +import tech.libeufin.nexus.* +import tech.libeufin.nexus.ebics.* import tech.libeufin.nexus.iso20022.* import kotlin.io.path.* import kotlin.test.* +import java.time.Instant class Iso20022Test { @Test + fun pain001() { + val creditor = IbanAccountMetadata( + iban = "CH4189144589712575493", + bic = null, + name = "Test" + ) + val msg = Pain001Msg( + messageId = "MESSAGE_ID", + timestamp = dateToInstant("2024-09-09"), + debtor = IbanAccountMetadata( + iban = "CH7789144474425692816", + bic = "BIC", + name = "myname" + ), + sum = TalerAmount("EUR:47.32"), + txs = listOf( + Pain001Tx( + creditor = creditor, + amount = TalerAmount("EUR:42"), + subject = "Test 42", + endToEndId = "TX_FIRST" + ), + Pain001Tx( + creditor = creditor, + amount = TalerAmount("EUR:5.11"), + subject = "Test 5.11", + endToEndId = "TX_SECOND" + ), + Pain001Tx( + creditor = creditor, + amount = TalerAmount("EUR:0.21"), + subject = "Test 0.21", + endToEndId = "TX_THIRD" + ) + ) + ) + for (dialect in Dialect.entries) { + assertContentEquals( + Path("sample/platform/${dialect}_pain001.xml").readBytes(), + createPain001(msg, dialect) + ) + } + } + + @Test fun hac() { assertContentEquals( parseCustomerAck(Path("sample/platform/hac.xml").inputStream()),