commit 0e716df72a0e424754dd1f33aceff81e799f68b5 parent b26b024f9fadcf5b74afed8daaa681ca593fa19c Author: Antoine A <> Date: Wed, 2 Jul 2025 13:10:09 +0200 nexus: fix pain.001 format for instant transactions Diffstat:
6 files changed, 22 insertions(+), 11 deletions(-)
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsSubmit.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsSubmit.kt @@ -1,6 +1,6 @@ /* * This file is part of LibEuFin. - * Copyright (C) 2023-2024 Taler Systems S.A. + * Copyright (C) 2023-2025 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 @@ -63,13 +63,15 @@ fun batchToPain001Msg(account: IbanAccountMetadata, batch: PaymentBatch): Pain00 private suspend fun submitBatch( client: EbicsClient, order: EbicsOrder, - batch: PaymentBatch + batch: PaymentBatch, + instant: Boolean ): String { val ebicsCfg = client.cfg.ebics val msg = batchToPain001Msg(ebicsCfg.account, batch) val xml = createPain001( msg = msg, - dialect = ebicsCfg.dialect + dialect = ebicsCfg.dialect, + instant = instant ) return client.upload(order, xml) } @@ -88,14 +90,14 @@ private suspend fun submitAll(client: EbicsClient) { runCatching { if (instantDebitOrder != null) { try { - return@runCatching submitBatch(client, instantDebitOrder!!, batch) + return@runCatching submitBatch(client, instantDebitOrder!!, batch, true) } catch (e: EbicsError.Code) { // No longer try to submit using the instant method for now logger.debug("Failed to submit using instant credit order ${e.fmt()}") instantDebitOrder = null } } - submitBatch(client, debitOrder, batch) + submitBatch(client, debitOrder, batch, false) }.fold( onSuccess = { orderId -> client.db.initiated.batchSubmissionSuccess(batch.id, Instant.now(), orderId) diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/Manual.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/Manual.kt @@ -64,7 +64,8 @@ class ExportCmt: CliktCommand("export") { val xml = createPain001( msg = msg, - dialect = ebicsCfg.dialect + dialect = ebicsCfg.dialect, + instant = false ) zip.write(xml) append("\nbatch ${batch.messageId}:") diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsOrder.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/ebics/EbicsOrder.kt @@ -201,7 +201,7 @@ enum class Dialect { fun instantDirectDebit(): EbicsOrder? { return when (this) { postfinance -> null - gls -> EbicsOrder.V3("BTU", "SCI", null, "pain.001") + gls -> EbicsOrder.V3("BTU", "SCI", "DE", "pain.001") maerki_baumann -> throw IllegalArgumentException("Maerki Baumann does not have EBICS access") } } diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/pain001.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/pain001.kt @@ -54,7 +54,8 @@ data class Pain001Msg( /** Create a pain.001 XML document [msg] valid for [dialect] */ fun createPain001( msg: Pain001Msg, - dialect: Dialect + dialect: Dialect, + instant: Boolean ): ByteArray { val version = "09" val suffix = when (dialect) { @@ -82,6 +83,8 @@ fun createPain001( el("Prvdr", "Taler Systems SA") el("Vrsn", VERSION) }*/ + + // LocalInstrument } el("PmtInf") { el("PmtInfId", "NOTPROVIDED") @@ -90,7 +93,12 @@ fun createPain001( el("NbOfTxs", msg.txs.size.toString()) el("CtrlSum", totalAmount) if (dialect == Dialect.gls) { - el("PmtTpInf/SvcLvl/Cd", "SEPA") + el("PmtTpInf") { + el("SvcLvl/Cd", "SEPA") + if (instant) { + el("LclInstrm/Cd", "INST") + } + } } el("ReqdExctnDt/Dt", DateTimeFormatter.ISO_DATE.format(zonedTimestamp)) el("Dbtr/Nm", msg.debtor.name) diff --git a/nexus/src/test/kotlin/Iso20022Test.kt b/nexus/src/test/kotlin/Iso20022Test.kt @@ -67,7 +67,7 @@ class Iso20022Test { for (dialect in Dialect.entries) { assertEquals( Path("sample/platform/${dialect}_pain001.xml").readText().replace("VERSION", VERSION), - createPain001(msg, dialect).asUtf8() + createPain001(msg, dialect, false).asUtf8() ) } } diff --git a/nexus/src/test/kotlin/RegistrationTest.kt b/nexus/src/test/kotlin/RegistrationTest.kt @@ -215,7 +215,7 @@ class RegistrationTest { println(dialect) assertEquals( Path("sample/platform/${dialect}_pain001.xml").readText().replace("VERSION", VERSION), - createPain001(msg, dialect).asUtf8() + createPain001(msg, dialect, false).asUtf8() ) } }