libeufin

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

commit 8ad947fd464260822cec3dc9b0f6492f903f81d1
parent 65e2e51fa87b6222fb1b3818deea8ed200d3b72e
Author: Antoine A <>
Date:   Mon, 16 Sep 2024 00:59:56 +0200

nexus: use iban payto type

Diffstat:
Mcommon/src/main/kotlin/TalerCommon.kt | 6++++++
Mcommon/src/main/kotlin/db/types.kt | 4++++
Mcommon/src/main/kotlin/helpers.kt | 6++++++
Mnexus/src/main/kotlin/tech/libeufin/nexus/api/WireGatewayApi.kt | 4++--
Mnexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsSubmit.kt | 3+--
Mnexus/src/main/kotlin/tech/libeufin/nexus/cli/InitiatePayment.kt | 2+-
Mnexus/src/main/kotlin/tech/libeufin/nexus/cli/Testing.kt | 2+-
Mnexus/src/main/kotlin/tech/libeufin/nexus/db/Database.kt | 3++-
Mnexus/src/main/kotlin/tech/libeufin/nexus/db/InitiatedDAO.kt | 4++--
Mnexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt | 8++++----
Mnexus/src/main/kotlin/tech/libeufin/nexus/iso20022/camt.kt | 22+++++++++++-----------
Mnexus/src/test/kotlin/IngestionTest.kt | 14+++++++-------
Mnexus/src/test/kotlin/Iso20022Test.kt | 24++++++++++++------------
Mnexus/src/test/kotlin/helpers.kt | 8++++----
Mtestbench/src/test/kotlin/IntegrationTest.kt | 2+-
15 files changed, 64 insertions(+), 48 deletions(-)

diff --git a/common/src/main/kotlin/TalerCommon.kt b/common/src/main/kotlin/TalerCommon.kt @@ -332,6 +332,12 @@ sealed class Payto { } } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Payto) return false + return this.parsed == other.parsed + } + internal object Serializer : KSerializer<Payto> { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Payto", PrimitiveKind.STRING) diff --git a/common/src/main/kotlin/db/types.kt b/common/src/main/kotlin/db/types.kt @@ -57,4 +57,8 @@ fun ResultSet.getTalerTimestamp(name: String): TalerProtocolTimestamp{ fun ResultSet.getBankPayto(payto: String, name: String, ctx: BankPaytoCtx): String { return Payto.parse(getString(payto)).bank(getString(name), ctx) +} + +fun ResultSet.getIbanPayto(payto: String): IbanPayto { + return Payto.parse(getString(payto)).expectIban() } \ No newline at end of file diff --git a/common/src/main/kotlin/helpers.kt b/common/src/main/kotlin/helpers.kt @@ -169,4 +169,10 @@ fun ApplicationCall.longPath(name: String): Long { } catch (e: Exception) { throw badRequest("Long uri component malformed: ${e.message}", TalerErrorCode.GENERIC_PARAMETER_MALFORMED) // TODO better error ? } +} + +/* ----- Payto ----- */ + +fun ibanPayto(iban: String, name: String? = null): IbanPayto { + return Payto.parse(IbanPayto.build(iban, null, name)).expectIban() } \ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/api/WireGatewayApi.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/api/WireGatewayApi.kt @@ -109,12 +109,12 @@ fun Routing.wireGatewayApi(db: Database, cfg: NexusConfig) = authApi(cfg.wireGat metadata: TalerIncomingMetadata ) { cfg.checkCurrency(amount) - debitAccount.expectRequestIban() + val debitAccount = debitAccount.expectRequestIban() val timestamp = Instant.now() val bankId = randEbicsId() val res = db.payment.registerTalerableIncoming(IncomingPayment( amount = amount, - debitPaytoUri = debitAccount.toString(), + debtorPayto = debitAccount, subject = subject, executionTime = timestamp, bankId = bankId diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsSubmit.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsSubmit.kt @@ -37,8 +37,7 @@ fun batchToPain001Msg(account: IbanAccountMetadata, batch: PaymentBatch): Pain00 debtor = account, sum = batch.sum, txs = batch.payments.map { payment -> - val payto = Payto.parse(payment.creditPaytoUri).expectIban() - // TODO handle payto error + val payto = payment.creditor Pain001Tx( creditor = IbanAccountMetadata( iban = payto.iban.value, diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/InitiatePayment.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/InitiatePayment.kt @@ -66,7 +66,7 @@ class InitiatePayment: CliktCommand("Initiate an outgoing payment") { id = -1, amount = amount, subject = subject, - creditPaytoUri = payto.toString(), + creditor = payto, initiationTime = Instant.now(), endToEndId = endToEndId ?: randEbicsId() ) diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/Testing.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/Testing.kt @@ -89,7 +89,7 @@ class FakeIncoming: CliktCommand("Genere a fake incoming payment") { ingestIncomingPayment(db, IncomingPayment( amount = amount, - debitPaytoUri = payto.toString(), + debtorPayto = payto, subject = subject, executionTime = Instant.now(), bankId = randEbicsId() diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/db/Database.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/db/Database.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import org.slf4j.LoggerFactory import tech.libeufin.common.TalerAmount +import tech.libeufin.common.IbanPayto import tech.libeufin.common.db.DatabaseConfig import tech.libeufin.common.db.DbPool import tech.libeufin.common.db.watchNotifications @@ -41,7 +42,7 @@ data class InitiatedPayment( val id: Long, val amount: TalerAmount, val subject: String, - val creditPaytoUri: String, + val creditor: IbanPayto, val initiationTime: Instant, val endToEndId: String ) diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/db/InitiatedDAO.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/db/InitiatedDAO.kt @@ -51,7 +51,7 @@ class InitiatedDAO(private val db: Database) { setLong(1, paymentData.amount.value) setInt(2, paymentData.amount.frac) setString(3, paymentData.subject) - setString(4, paymentData.creditPaytoUri) + setString(4, paymentData.creditor.toString()) setLong(5, paymentData.initiationTime.micros()) setString(6, paymentData.endToEndId) oneUniqueViolation(PaymentInitiationResult.RequestUidReuse) { @@ -375,7 +375,7 @@ class InitiatedDAO(private val db: Database) { val payment = InitiatedPayment( id = it.getLong("initiated_outgoing_transaction_id"), amount = it.getAmount("amount", currency), - creditPaytoUri = it.getString("credit_payto"), + creditor = it.getIbanPayto("credit_payto"), subject = it.getString("subject"), initiationTime = it.getLong("initiation_time").asInstant(), endToEndId = it.getString("end_to_end_id") diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/db/PaymentDAO.kt @@ -52,7 +52,7 @@ class PaymentDAO(private val db: Database) { setInt(2, paymentData.amount.frac) setString(3, paymentData.subject) setLong(4, executionTime) - setString(5, paymentData.creditPaytoUri) + setString(5, paymentData.creditorPayto.toString()) setString(6, paymentData.endToEndId) setBytes(7, wtid?.raw) setString(8, baseUrl?.url) @@ -103,7 +103,7 @@ class PaymentDAO(private val db: Database) { setInt(2, paymentData.amount.frac) setString(3, paymentData.subject) setLong(4, paymentData.executionTime.micros()) - setString(5, paymentData.debitPaytoUri) + setString(5, paymentData.debtorPayto.toString()) setString(6, paymentData.bankId) setLong(7, bounceAmount.value) setInt(8, bounceAmount.frac) @@ -138,7 +138,7 @@ class PaymentDAO(private val db: Database) { setInt(2, paymentData.amount.frac) setString(3, paymentData.subject) setLong(4, executionTime) - setString(5, paymentData.debitPaytoUri) + setString(5, paymentData.debtorPayto.toString()) setString(6, paymentData.bankId) setString(7, metadata.type.name) when (metadata.type) { @@ -177,7 +177,7 @@ class PaymentDAO(private val db: Database) { setInt(2, paymentData.amount.frac) setString(3, paymentData.subject) setLong(4, executionTime) - setString(5, paymentData.debitPaytoUri) + setString(5, paymentData.debtorPayto.toString()) setString(6, paymentData.bankId) one { IncomingRegistrationResult.Success( diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/camt.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/iso20022/camt.kt @@ -36,10 +36,10 @@ data class IncomingPayment( val amount: TalerAmount, val subject: String, override val executionTime: Instant, - val debitPaytoUri: String + val debtorPayto: IbanPayto ): TxNotification { override fun toString(): String { - return "IN ${executionTime.fmtDate()} $amount $bankId debitor=$debitPaytoUri subject=\"$subject\"" + return "IN ${executionTime.fmtDate()} $amount $bankId debitor=$debtorPayto subject=\"$subject\"" } } @@ -52,11 +52,11 @@ data class OutgoingPayment( val amount: TalerAmount, val subject: String? = null, // Some implementation does not provide this for recovery override val executionTime: Instant, - val creditPaytoUri: String? = null // Some implementation does not provide this for recovery + val creditorPayto: IbanPayto? = null // Some implementation does not provide this for recovery ): TxNotification { override fun toString(): String { val msgIdFmt = if (msgId == null) "" else "$msgId." - return "OUT ${executionTime.fmtDate()} $amount $msgIdFmt$endToEndId creditor=$creditPaytoUri subject=\"$subject\"" + return "OUT ${executionTime.fmtDate()} $amount $msgIdFmt$endToEndId creditor=$creditorPayto subject=\"$subject\"" } } @@ -104,13 +104,13 @@ private data class OutgoingId( } /** Parse a payto */ -private fun XmlDestructor.payto(prefix: String): String? { +private fun XmlDestructor.payto(prefix: String): IbanPayto? { return opt("RltdPties") { val iban = opt("${prefix}Acct")?.one("Id")?.one("IBAN")?.text() if (iban != null) { val name = opt(prefix) { opt("Nm")?.text() ?: opt("Pty")?.one("Nm")?.text() } - // Parse bic ? - IbanPayto.build(iban, null, name) + // TODO more performant option + ibanPayto(iban, name) } else { null } @@ -467,7 +467,7 @@ private sealed interface TxInfo { val bankId: String?, val amount: TalerAmount, val subject: String?, - val debtorPayto: String? + val debtorPayto: IbanPayto? ): TxInfo data class Debit( override val ref: String?, @@ -477,7 +477,7 @@ private sealed interface TxInfo { val id: OutgoingId, val amount: TalerAmount, val subject: String?, - val creditorPayto: String? + val creditorPayto: IbanPayto? ): TxInfo } @@ -503,7 +503,7 @@ private fun parseTxLogic(info: TxInfo): TxNotification { IncomingPayment( amount = info.amount, bankId = info.bankId, - debitPaytoUri = info.debtorPayto, + debtorPayto = info.debtorPayto, executionTime = info.bookDate, subject = info.subject ) @@ -517,7 +517,7 @@ private fun parseTxLogic(info: TxInfo): TxNotification { endToEndId = info.id.endToEndId, msgId = info.id.msgId, executionTime = info.bookDate, - creditPaytoUri = info.creditorPayto, + creditorPayto = info.creditorPayto, subject = info.subject ) } else { diff --git a/nexus/src/test/kotlin/IngestionTest.kt b/nexus/src/test/kotlin/IngestionTest.kt @@ -119,7 +119,7 @@ class IngestionTest { amount = it.getAmount("amount", this@check.bankCurrency), subject = it.getString("subject"), executionTime = it.getLong("execution_time").asInstant(), - debitPaytoUri = it.getString("debit_payto"), + debtorPayto = it.getIbanPayto("debit_payto"), ) } } @@ -144,7 +144,7 @@ class IngestionTest { amount = it.getAmount("amount", this@check.bankCurrency), subject = it.getString("subject"), executionTime = it.getLong("execution_time").asInstant(), - creditPaytoUri = it.getString("credit_payto"), + creditorPayto = it.getIbanPayto("credit_payto"), ) } } @@ -323,7 +323,7 @@ class IngestionTest { amount = TalerAmount("EUR:3"), subject = "Taler FJDQ7W6G7NWX4H9M1MKA12090FRC9K7DA6N0FANDZZFXTR6QHX5G Test.,-", executionTime = dateToInstant("2024-04-12"), - debitPaytoUri = "payto://iban/DE84500105177118117964?receiver-name=John%20Smith" + debtorPayto = ibanPayto("DE84500105177118117964", "John Smith") ), ), outgoing = listOf( @@ -332,28 +332,28 @@ class IngestionTest { amount = TalerAmount("EUR:2"), subject = "TestABC123", executionTime = dateToInstant("2024-04-18"), - creditPaytoUri = "payto://iban/DE20500105172419259181?receiver-name=John%20Smith" + creditorPayto = ibanPayto("DE20500105172419259181", "John Smith") ), OutgoingPayment( endToEndId = "FD622SMXKT5QWSAHDY0H8NYG3G", amount = TalerAmount("EUR:1.1"), subject = "single 2024-09-02T14:29:52.875253314Z", executionTime = dateToInstant("2024-09-02"), - creditPaytoUri = "payto://iban/DE89500105173198527518?receiver-name=Grothoff%20Hans" + creditorPayto = ibanPayto("DE89500105173198527518", "Grothoff Hans") ), OutgoingPayment( endToEndId = "YF5QBARGQ0MNY0VK59S477VDG4", amount = TalerAmount("EUR:1.1"), subject = "Simple tx", executionTime = dateToInstant("2024-04-18"), - creditPaytoUri = "payto://iban/DE20500105172419259181?receiver-name=John%20Smith" + creditorPayto = ibanPayto("DE20500105172419259181", "John Smith") ), OutgoingPayment( endToEndId = "27SK3166EG36SJ7VP7VFYP0MW8", amount = TalerAmount("EUR:44"), subject = "init payment", executionTime = dateToInstant("2024-09-04"), - creditPaytoUri = "payto://iban/CH4189144589712575493?receiver-name=Test" + creditorPayto = ibanPayto("CH4189144589712575493", "Test") ), ) ) diff --git a/nexus/src/test/kotlin/Iso20022Test.kt b/nexus/src/test/kotlin/Iso20022Test.kt @@ -148,21 +148,21 @@ class Iso20022Test { amount = TalerAmount("CHF:3.00"), subject = null, executionTime = dateToInstant("2024-01-15"), - creditPaytoUri = null + creditorPayto = null ), IncomingPayment( bankId = "62e2b511-7313-4ccd-8d40-c9d8e612cd71", amount = TalerAmount("CHF:10"), subject = "G1XTY6HGWGMVRM7E6XQ4JHJK561ETFDFTJZ7JVGV543XZCB27YBG", executionTime = dateToInstant("2023-12-19"), - debitPaytoUri = "payto://iban/CH7389144832588726658?receiver-name=Mr%20Test" + debtorPayto = ibanPayto("CH7389144832588726658", "Mr Test") ), IncomingPayment( bankId = "62e2b511-7313-4ccd-8d40-c9d8e612cd71", amount = TalerAmount("CHF:2.53"), subject = "G1XTY6HGWGMVRM7E6XQ4JHJK561ETFDFTJZ7JVGV543XZCB27YB", executionTime = dateToInstant("2023-12-19"), - debitPaytoUri = "payto://iban/CH7389144832588726658?receiver-name=Mr%20Test" + debtorPayto = ibanPayto("CH7389144832588726658", "Mr Test") ), OutgoingBatch( msgId = "ZS1PGNTSV0ZNDFAJBBWWB8015G", @@ -204,7 +204,7 @@ class Iso20022Test { amount = TalerAmount("EUR:2"), subject = "TestABC123", executionTime = dateToInstant("2024-04-18"), - creditPaytoUri = "payto://iban/DE20500105172419259181?receiver-name=John%20Smith" + creditorPayto = ibanPayto("DE20500105172419259181", "John Smith") ), OutgoingReversal( endToEndId = "8XK8Z7RAX224FGWK832FD40GYC", @@ -216,7 +216,7 @@ class Iso20022Test { amount = TalerAmount("EUR:3"), subject = "Taler FJDQ7W6G7NWX4H9M1MKA12090FRC9K7DA6N0FANDZZFXTR6QHX5G Test.,-", executionTime = dateToInstant("2024-04-12"), - debitPaytoUri = "payto://iban/DE84500105177118117964?receiver-name=John%20Smith" + debtorPayto = ibanPayto("DE84500105177118117964", "John Smith") ), OutgoingReversal( endToEndId = "COMPAT_FAILURE", @@ -229,7 +229,7 @@ class Iso20022Test { amount = TalerAmount("EUR:1.1"), subject = "single 2024-09-02T14:29:52.875253314Z", executionTime = dateToInstant("2024-09-02"), - creditPaytoUri = "payto://iban/DE89500105173198527518?receiver-name=Grothoff%20Hans" + creditorPayto = ibanPayto("DE89500105173198527518", "Grothoff Hans") ), OutgoingPayment( endToEndId = "YF5QBARGQ0MNY0VK59S477VDG4", @@ -237,7 +237,7 @@ class Iso20022Test { amount = TalerAmount("EUR:1.1"), subject = "Simple tx", executionTime = dateToInstant("2024-04-18"), - creditPaytoUri = "payto://iban/DE20500105172419259181?receiver-name=John%20Smith" + creditorPayto = ibanPayto("DE20500105172419259181", "John Smith") ), ) ) @@ -254,7 +254,7 @@ class Iso20022Test { amount = TalerAmount("EUR:2"), subject = "TestABC123", executionTime = dateToInstant("2024-04-18"), - creditPaytoUri = "payto://iban/DE20500105172419259181?receiver-name=John%20Smith" + creditorPayto = ibanPayto("DE20500105172419259181", "John Smith") ), OutgoingReversal( endToEndId = "KGTDBASWTJ6JM89WXD3Q5KFQC4", @@ -270,7 +270,7 @@ class Iso20022Test { amount = TalerAmount("EUR:3"), subject = "Taler FJDQ7W6G7NWX4H9M1MKA12090FRC9K7DA6N0FANDZZFXTR6QHX5G Test.,-", executionTime = dateToInstant("2024-04-12"), - debitPaytoUri = "payto://iban/DE84500105177118117964?receiver-name=John%20Smith" + debtorPayto = ibanPayto("DE84500105177118117964", "John Smith") ), OutgoingReversal( endToEndId = "COMPAT_FAILURE", @@ -283,7 +283,7 @@ class Iso20022Test { amount = TalerAmount("EUR:1.1"), subject = "single 2024-09-02T14:29:52.875253314Z", executionTime = dateToInstant("2024-09-02"), - creditPaytoUri = "payto://iban/DE89500105173198527518?receiver-name=Grothoff%20Hans" + creditorPayto = ibanPayto("DE89500105173198527518", "Grothoff Hans") ), OutgoingPayment( endToEndId = "YF5QBARGQ0MNY0VK59S477VDG4", @@ -291,7 +291,7 @@ class Iso20022Test { amount = TalerAmount("EUR:1.1"), subject = "Simple tx", executionTime = dateToInstant("2024-04-18"), - creditPaytoUri = "payto://iban/DE20500105172419259181?receiver-name=John%20Smith" + creditorPayto = ibanPayto("DE20500105172419259181", "John Smith") ), ) ) @@ -307,7 +307,7 @@ class Iso20022Test { amount = TalerAmount("EUR:2.5"), subject = "Test ICT", executionTime = dateToInstant("2024-05-05"), - debitPaytoUri = "payto://iban/DE84500105177118117964?receiver-name=Mr%20Test" + debtorPayto = ibanPayto("DE84500105177118117964", "Mr Test") ) ) ) diff --git a/nexus/src/test/kotlin/helpers.kt b/nexus/src/test/kotlin/helpers.kt @@ -86,11 +86,11 @@ fun genInitPay( endToEndId: String, subject: String = "init payment", amount: String = "KUDOS:44", - creditPaytoUri: String = "payto://iban/CH4189144589712575493?receiver-name=Test" + creditorPayto: IbanPayto = ibanPayto("CH4189144589712575493", "Test") ) = InitiatedPayment( id = -1, amount = TalerAmount(amount), - creditPaytoUri = creditPaytoUri, + creditor = creditorPayto, subject = subject, initiationTime = Instant.now(), endToEndId = endToEndId @@ -101,7 +101,7 @@ fun genInPay(subject: String, amount: String = "KUDOS:44"): IncomingPayment { val bankId = randEbicsId() return IncomingPayment( amount = TalerAmount(amount), - debitPaytoUri = "payto://iban/not-used", + debtorPayto = ibanPayto("DE84500105177118117964"), subject = subject, executionTime = Instant.now(), bankId = bankId @@ -113,7 +113,7 @@ fun genOutPay(subject: String, endToEndId: String? = null): OutgoingPayment { val id = endToEndId ?: randEbicsId() return OutgoingPayment( amount = TalerAmount(44, 0, "KUDOS"), - creditPaytoUri = "payto://iban/CH4189144589712575493?receiver-name=Test", + creditorPayto = ibanPayto("CH4189144589712575493", "Test"), subject = subject, executionTime = Instant.now(), endToEndId = id diff --git a/testbench/src/test/kotlin/IntegrationTest.kt b/testbench/src/test/kotlin/IntegrationTest.kt @@ -162,7 +162,7 @@ class IntegrationTest { val reservePub = EddsaPublicKey.rand() val reservePayment = IncomingPayment( amount = TalerAmount("EUR:10"), - debitPaytoUri = userPayTo.toString(), + debtorPayto = userPayTo, subject = "Error test $reservePub", executionTime = Instant.now(), bankId = "reserve_error"