libeufin

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

commit 4495f3d05129fcd5ab0a81b459e8303565365825
parent 3d9dffab37a4117a147748fdde402ee0a4622669
Author: Antoine A <>
Date:   Thu, 12 Dec 2024 15:01:26 +0100

nexus: add manual management cmd

Diffstat:
Mnexus/src/main/kotlin/tech/libeufin/nexus/cli/LibeufinNexus.kt | 2+-
Anexus/src/main/kotlin/tech/libeufin/nexus/cli/Manual.kt | 132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mnexus/src/main/kotlin/tech/libeufin/nexus/cli/Testing.kt | 79++++++++-----------------------------------------------------------------------
Mnexus/src/main/kotlin/tech/libeufin/nexus/db/InitiatedDAO.kt | 2+-
Mtestbench/src/main/kotlin/Main.kt | 2+-
5 files changed, 143 insertions(+), 74 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/LibeufinNexus.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/LibeufinNexus.kt @@ -44,7 +44,7 @@ fun CliktCommand.transientOption() = option( class LibeufinNexus : CliktCommand() { init { versionOption(VERSION) - subcommands(DbInit(), EbicsSetup(), EbicsSubmit(), EbicsFetch(), Serve(), InitiatePayment(), CliConfigCmd(NEXUS_CONFIG_SOURCE), TestingCmd()) + subcommands(DbInit(), EbicsSetup(), EbicsSubmit(), EbicsFetch(), Serve(), InitiatePayment(), ManualCmd(), CliConfigCmd(NEXUS_CONFIG_SOURCE), TestingCmd()) } override fun run() = Unit } \ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/Manual.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/Manual.kt @@ -0,0 +1,131 @@ +/* + * This file is part of LibEuFin. + * Copyright (C) 2024 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 + * published by the Free Software Foundation; either version 3, or + * (at your option) any later version. + + * LibEuFin is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General + * Public License for more details. + + * You should have received a copy of the GNU Affero General Public + * License along with LibEuFin; see the file COPYING. If not, see + * <http://www.gnu.org/licenses/> + */ + +package tech.libeufin.nexus.cli + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.Context +import com.github.ajalt.clikt.core.subcommands +import com.github.ajalt.clikt.parameters.arguments.* +import com.github.ajalt.clikt.parameters.groups.provideDelegate +import com.github.ajalt.clikt.parameters.options.convert +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.flag +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.types.* +import com.github.ajalt.mordant.terminal.* +import tech.libeufin.common.* +import tech.libeufin.nexus.* +import tech.libeufin.nexus.db.* +import tech.libeufin.nexus.ebics.* +import tech.libeufin.nexus.iso20022.* +import java.util.zip.* +import java.time.Instant +import java.io.* + +class ExportCmt: CliktCommand("export") { + override fun help(context: Context) = "Export pending batches as pain001 messages" + + private val common by CommonOption() + private val out by argument().file() + + override fun run() = cliCmd(logger, common.log) { + nexusConfig(common.config).withDb { db, cfg -> + // Create and get pending batches + db.initiated.batch(Instant.now(), randEbicsId()) + val batches = db.initiated.submittable(cfg.currency) + + var nbTx: Int = 0 + ZipOutputStream(BufferedOutputStream(FileOutputStream(out))).use { zip -> + val ebicsCfg = cfg.ebics + for (batch in batches) { + nbTx = batch.payments.size + val entry = ZipEntry("${batch.creationDate.toDateTimeFilePath()}-${batch.messageId}.xml") + zip.putNextEntry(entry) + val msg = batchToPain001Msg(ebicsCfg.account, batch) + + val xml = createPain001( + msg = msg, + dialect = ebicsCfg.dialect + ) + zip.write(xml) + println("batch ${batch.messageId}:") + for (tx in batch.payments) { + println("tx ${tx.endToEndId} ${tx.amount} ${tx.creditor.iban} '${tx.creditor.receiverName}'") + } + println("") + } + } + println("Exported ${batches.size} pain.001 files in $out") + } + } +} + +class ImportCmt: CliktCommand("import") { + override fun help(context: Context) = "Import EBICS camt files" + + private val common by CommonOption() + private val sources by argument().file().multiple(required = true) + + override fun run() = cliCmd(logger, common.log) { + nexusConfig(common.config).withDb { db, cfg -> + for (source in sources) { + var nbTx: Int = 0 + source.inputStream().use { xml -> + nbTx += registerTxs(db, cfg, xml) + } + println("Imported $nbTx transactions from $source") + } + } + } +} + +class StatusCmd: CliktCommand("status") { + override fun help(context: Context) = "Change batches or transactions status" + + enum class Kind { + batch, + tx + } + + private val common by CommonOption() + private val kind by argument().enum<Kind>() + private val id by argument() + private val status by argument().enum<StatusUpdate>() + private val msg by argument().optional() + + override fun run() = cliCmd(logger, common.log) { + nexusConfig(common.config).withDb { db, cfg -> + when (kind) { + Kind.batch -> db.initiated.batchStatusUpdate(id, status, msg) + Kind.tx -> db.initiated.txStatusUpdate(id, null, status, msg) + } + } + } +} + +class ManualCmd : CliktCommand("manual") { + init { + subcommands(ExportCmt(), ImportCmt(), StatusCmd()) + } + + override fun help(context: Context) = "Manual management commands" + + override fun run() = Unit +} +\ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/Testing.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/Testing.kt @@ -72,7 +72,11 @@ class FakeIncoming: CliktCommand() { private val common by CommonOption() private val amount by option( "--amount", - help = "The amount to transfer, payto 'amount' parameter takes the precedence" + help = "The payment amount, payto 'amount' parameter takes the precedence" + ).convert { TalerAmount(it) } + private val creditFee by option( + "--credit-fee", + help = "The payment credit fee" ).convert { TalerAmount(it) } private val subject by option( "--subject", @@ -96,6 +100,7 @@ class FakeIncoming: CliktCommand() { amount = amount, debtorPayto = payto, subject = subject, + creditFee = creditFee, executionTime = Instant.now(), bankId = randEbicsId() ) @@ -276,80 +281,12 @@ class ListCmd: CliktCommand("list") { } printTable(columnNames, rows) } - } -} - -class ExportCmt: CliktCommand("export") { - override fun help(context: Context) = "Export pending batches as pain001 messages" - - private val common by CommonOption() - private val out by argument().file() - - override fun run() = cliCmd(logger, common.log) { - nexusConfig(common.config).withDb { db, cfg -> - // Create and get pending batches - db.initiated.batch(Instant.now(), randEbicsId()) - val batches = db.initiated.submittable(cfg.currency) - - var nbTx: Int = 0 - ZipOutputStream(BufferedOutputStream(FileOutputStream(out))).use { zip -> - val ebicsCfg = cfg.ebics - for (batch in batches) { - nbTx = batch.payments.size - val entry = ZipEntry("${batch.creationDate.toDateTimeFilePath()}-${batch.messageId}.xml") - zip.putNextEntry(entry) - val msg = batchToPain001Msg(ebicsCfg.account, batch) - val xml = createPain001( - msg = msg, - dialect = ebicsCfg.dialect - ) - zip.write(xml) - } - } - println("Exported $nbTx transactions in $out") - - // Update batches status - val now = Instant.now() - val t = Terminal() - for (batch in batches) { - val filename = "${batch.creationDate.toDateTimeFilePath()}-${batch.messageId}.xml" - val success = YesNoPrompt("Was $filename upload successful?", t).ask() - if (success == true) { - db.initiated.batchSubmissionSuccess(batch.id, now, null) - } else if (success == false) { - val permanent = YesNoPrompt("Is the error permanent?", t).ask() - val msg = StringPrompt("Error msg", t).ask() - if (permanent != null) { - db.initiated.batchSubmissionFailure(batch.id, now, msg, permanent) - } - } - } - } - } -} - -class ImportCmt: CliktCommand("import") { - override fun help(context: Context) = "Import EBICS camt files" - - private val common by CommonOption() - private val sources by argument().file().multiple(required = true) - - override fun run() = cliCmd(logger, common.log) { - nexusConfig(common.config).withDb { db, cfg -> - for (source in sources) { - var nbTx: Int = 0 - source.inputStream().use { xml -> - nbTx += registerTxs(db, cfg, xml) - } - println("Imported $nbTx transactions from $source") - } - } - } + } } class TestingCmd : CliktCommand("testing") { init { - subcommands(FakeIncoming(), ListCmd(), EbicsDownload(), TxCheck(), Wss(), ExportCmt(), ImportCmt()) + subcommands(FakeIncoming(), ListCmd(), EbicsDownload(), TxCheck(), Wss()) } override fun help(context: Context) = "Testing helper commands" diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/db/InitiatedDAO.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/db/InitiatedDAO.kt @@ -261,7 +261,7 @@ class InitiatedDAO(private val db: Database) { } /** Register payment status [state] with [msg] for transaction [endToEndId] in batch [msgId] */ - suspend fun txStatusUpdate(endToEndId: String, msgId: String?, state: StatusUpdate, msg: String) = db.serializable( + suspend fun txStatusUpdate(endToEndId: String, msgId: String?, state: StatusUpdate, msg: String?) = db.serializable( "SELECT FROM tx_status_update(?,?,?::submission_state,?)" ) { setString(1, endToEndId) diff --git a/testbench/src/main/kotlin/Main.kt b/testbench/src/main/kotlin/Main.kt @@ -176,7 +176,7 @@ class Cli : CliktCommand() { put("list-initiated", "List initiated payments", "testing list $flags initiated") put("wss", "Listen to notification over websocket", "testing wss $debugFlags") put("submit", "Submit pending transactions", "ebics-submit $ebicsFlags") - put("export", "Export pending batches as pain001 messages", "testing export $flags payments.zip") + put("export", "Export pending batches as pain001 messages", "manual export $flags payments.zip") put("setup", "Setup", "ebics-setup $debugFlags") put("reset-keys", suspend { if (kind.test) {