commit bc5c9cfae023ee45a586bd1f74ab9c4615d25b7e
parent a41440e41dc6649670beb9c40e6e5d214df3e803
Author: Antoine A <>
Date: Mon, 5 Feb 2024 18:44:59 +0100
Add nexus commands to trigger outgoing payments and generate fake incoming payments
Diffstat:
5 files changed, 130 insertions(+), 96 deletions(-)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -1,6 +1,6 @@
/*
* This file is part of LibEuFin.
- * Copyright (C) 2023 Taler Systems S.A.
+ * Copyright (C) 2023-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
@@ -432,8 +432,8 @@ class CreateAccountOption: OptionGroup() {
help = "Payto URI of this account"
).convert { Payto.parse(it) }
val debit_threshold: TalerAmount? by option(
- help = "Max debit allowed for this account")
- .convert { TalerAmount(it) }
+ help = "Max debit allowed for this account"
+ ).convert { TalerAmount(it) }
}
class CreateAccount : CliktCommand(
diff --git a/contrib/payment_initiation_debug.sh b/contrib/payment_initiation_debug.sh
@@ -1,51 +0,0 @@
-#!/bin/bash
-
-# This script injects an initiated payment into its
-# table, in order to test the ebics-submit subcommand.
-
-usage() {
- echo "Usage: ./payment_initiation.sh CONFIG_FILE IBAN_CREDITOR PAYMENT_SUBJECT"
- echo
- echo "Pays a fixed amount of 1 CHF to the IBAN_CREDITOR with the PAYMENT_SUBJECT."
- echo "It requires the EBICS keying to be already carried out, see ebics-setup"
- echo "subcommand at libeufin-nexus(1)"
-}
-
-# Detecting the help case.
-if test "$1" = "--help" -o "$1" = "-h" -o -z ${1:-};
- then usage
- exit
-fi
-
-set -eu
-
-CONFIG_FILE=$1
-IBAN_CREDITOR=$2
-PAYMENT_SUBJECT=$3
-
-# Getting the database connection.
-DB_NAME=$(taler-config -c $1 -s nexus-postgres -o config)
-echo database: $DB_NAME
-
-# Optionally reading the user-provided request UID.
-if test -n "${LIBEUFIN_SUBMIT_REQUEST_UID:-}"
- then SUBMIT_REQUEST_UID="$LIBEUFIN_SUBMIT_REQUEST_UID"
- else SUBMIT_REQUEST_UID=$(uuidgen | cut -c -30)
-fi
-
-# Finally inserting the initiated payment into the database.
-INSERT_COMMAND="
-INSERT INTO libeufin_nexus.initiated_outgoing_transactions
- (amount,
- wire_transfer_subject,
- initiation_time,
- credit_payto_uri,
- request_uid)
- VALUES ((1,0),
- '${PAYMENT_SUBJECT}',
- $(($(date +%s) * 1000000)),
- 'payto://iban/POFICHBE/${IBAN_CREDITOR}?receiver-name=Merchant',
- '${SUBMIT_REQUEST_UID}')"
-
-# Only logging errors.
-psql $DB_NAME -c "$INSERT_COMMAND" > /dev/null
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt
@@ -1,6 +1,7 @@
/*
* This file is part of LibEuFin.
* Copyright (C) 2023 Stanisci and Dold.
+ * 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
@@ -26,7 +27,10 @@ package tech.libeufin.nexus
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.subcommands
-import com.github.ajalt.clikt.parameters.options.versionOption
+import com.github.ajalt.clikt.parameters.options.*
+import com.github.ajalt.clikt.parameters.groups.*
+import com.github.ajalt.clikt.parameters.arguments.*
+import kotlinx.coroutines.*
import io.ktor.client.*
import io.ktor.util.*
import org.slf4j.Logger
@@ -34,6 +38,7 @@ import org.slf4j.LoggerFactory
import tech.libeufin.nexus.ebics.*
import tech.libeufin.common.*
import java.nio.file.Path
+import java.time.Instant
val NEXUS_CONFIG_SOURCE = ConfigSource("libeufin", "libeufin-nexus", "libeufin-nexus")
val logger: Logger = LoggerFactory.getLogger("libeufin-nexus")
@@ -190,6 +195,7 @@ class EbicsSetupConfig(val config: TalerConfig) {
}
}
+
/**
* Abstracts the config loading
*
@@ -207,13 +213,123 @@ fun TalerConfig.dbConfig(): DatabaseConfig =
sqlDir = requirePath("libeufin-nexusdb-postgres", "sql_dir")
)
+class InitiatePayment: CliktCommand("Initiate an outgoing payment") {
+ private val common by CommonOption()
+ private val amount by option(
+ "--amount",
+ help = "The amount to transfer, payto 'amount' parameter takes the precedence"
+ ).convert { TalerAmount(it) }
+ private val subject by option(
+ "--subject",
+ help = "The payment subject, payto 'message' parameter takes the precedence"
+ )
+ private val requestUid by option(
+ "--request-uid",
+ help = "The payment request UID"
+ )
+ private val payto by argument(
+ help = "The credited account IBAN payto URI"
+ ).convert { Payto.parse(it).expectIban() }
+
+ override fun run() = cliCmd(logger, common.log) {
+ val cfg = loadConfig(common.config)
+ val dbCfg = cfg.dbConfig()
+ val currency = cfg.requireString("nexus-ebics", "currency")
+
+ val subject = payto.message ?: subject ?: throw Exception("Missing subject")
+ val amount = payto.amount ?: amount ?: throw Exception("Missing amount")
+
+ if (payto.receiverName == null)
+ throw Exception("Missing receiver name in creditor payto")
+
+ if (amount.currency != currency)
+ throw Exception("Wrong currency: expected $currency got ${amount.currency}")
+
+ val requestUid = requestUid ?: run {
+ val bytes = ByteArray(16)
+ kotlin.random.Random.nextBytes(bytes)
+ Base32Crockford.encode(bytes)
+ }
+
+ Database(dbCfg.dbConnStr).use { db ->
+ runBlocking {
+ db.initiatedPaymentCreate(
+ InitiatedPayment(
+ id = -1,
+ amount = amount,
+ wireTransferSubject = subject,
+ creditPaytoUri = payto.toString(),
+ initiationTime = Instant.now(),
+ requestUid = requestUid
+ )
+ )
+ }
+ }
+ }
+}
+
+class FakeIncoming: CliktCommand("Genere a fake incoming payment") {
+ private val common by CommonOption()
+ private val amount by option(
+ "--amount",
+ help = "The amount to transfer, payto 'amount' parameter takes the precedence"
+ ).convert { TalerAmount(it) }
+ private val subject by option(
+ "--subject",
+ help = "The payment subject, payto 'message' parameter takes the precedence"
+ )
+ private val payto by argument(
+ help = "The debited account IBAN payto URI"
+ ).convert { Payto.parse(it).expectIban() }
+
+ override fun run() = cliCmd(logger, common.log) {
+ val cfg = loadConfig(common.config)
+ val dbCfg = cfg.dbConfig()
+ val currency = cfg.requireString("nexus-ebics", "currency")
+
+ val subject = payto.message ?: subject ?: throw Exception("Missing subject")
+ val amount = payto.amount ?: amount ?: throw Exception("Missing amount")
+
+ if (amount.currency != currency)
+ throw Exception("Wrong currency: expected $currency got ${amount.currency}")
+
+ val bankId = run {
+ val bytes = ByteArray(16)
+ kotlin.random.Random.nextBytes(bytes)
+ Base32Crockford.encode(bytes)
+ }
+
+ Database(dbCfg.dbConnStr).use { db ->
+ runBlocking {
+ ingestIncomingPayment(db,
+ IncomingPayment(
+ amount = amount,
+ debitPaytoUri = payto.toString(),
+ wireTransferSubject = subject,
+ executionTime = Instant.now(),
+ bankId = bankId
+ )
+ )
+ }
+ }
+ }
+}
+
+class TestingCmd : CliktCommand("Testing helper commands", name = "testing") {
+ init {
+ subcommands(FakeIncoming())
+ }
+
+ override fun run() = Unit
+}
+
/**
* Main CLI class that collects all the subcommands.
*/
class LibeufinNexusCommand : CliktCommand() {
init {
versionOption(getVersion())
- subcommands(EbicsSetup(), DbInit(), EbicsSubmit(), EbicsFetch(), CliConfigCmd(NEXUS_CONFIG_SOURCE))
+ subcommands(EbicsSetup(), DbInit(), EbicsSubmit(), EbicsFetch(), InitiatePayment(), CliConfigCmd(NEXUS_CONFIG_SOURCE), TestingCmd())
}
override fun run() = Unit
}
diff --git a/testbench/src/main/kotlin/Main.kt b/testbench/src/main/kotlin/Main.kt
@@ -1,6 +1,6 @@
/*
* This file is part of LibEuFin.
- * Copyright (C) 2023 Taler Systems S.A.
+ * Copyright (C) 2023-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
@@ -19,7 +19,6 @@
package tech.libeufin.testbench
-import tech.libeufin.nexus.Database as NexusDb
import tech.libeufin.nexus.*
import tech.libeufin.bank.*
import tech.libeufin.common.*
@@ -120,11 +119,10 @@ class Cli : CliktCommand("Run integration tests on banks provider") {
val payto = "payto://iban/CH6208704048981247126?receiver-name=Grothoff%20Hans"
runBlocking {
- step("Test init ${kind.name}")
+ step("Init ${kind.name}")
if (ask("Reset DB ? y/n>") == "y") nexusCmd.test("dbinit -r $flags").assertOk()
else nexusCmd.test("dbinit $flags").assertOk()
- val nexusDb = NexusDb("postgresql:///libeufincheck")
val cmds = buildMap<String, suspend () -> Unit> {
put("reset-db", suspend {
@@ -157,28 +155,14 @@ class Cli : CliktCommand("Run integration tests on banks provider") {
Unit
})
put("tx", suspend {
- step("Test submit one transaction")
- nexusDb.initiatedPaymentCreate(InitiatedPayment(
- id = -1,
- amount = TalerAmount("CFH:42"),
- creditPaytoUri = payto,
- wireTransferSubject = "single transaction test",
- initiationTime = Instant.now(),
- requestUid = Base32Crockford.encode(randBytes(16))
- ))
+ step("Submit one transaction")
+ nexusCmd.test("initiate-payment $flags \"$payto&amount=CHF:42&message=single%20transaction%20test\"").assertOk()
nexusCmd.test("ebics-submit $ebicsFlags").assertOk()
})
put("txs", suspend {
- step("Test submit many transaction")
+ step("Submit many transaction")
repeat(4) {
- nexusDb.initiatedPaymentCreate(InitiatedPayment(
- id = -1,
- amount = TalerAmount("CFH:${100L+it}"),
- creditPaytoUri = payto,
- wireTransferSubject = "multi transaction test $it",
- initiationTime = Instant.now(),
- requestUid = Base32Crockford.encode(randBytes(16))
- ))
+ nexusCmd.test("initiate-payment $flags --amount=CHF:${100L+it} --subject \"multi transaction test $it\" \"$payto\"").assertOk()
}
nexusCmd.test("ebics-submit $ebicsFlags").assertOk()
})
@@ -186,14 +170,7 @@ class Cli : CliktCommand("Run integration tests on banks provider") {
put("tx", suspend {
step("Submit new transaction")
// TODO interactive payment editor
- nexusDb.initiatedPaymentCreate(InitiatedPayment(
- id = -1,
- amount = TalerAmount("CFH:1.1"),
- creditPaytoUri = payto,
- wireTransferSubject = "single transaction test",
- initiationTime = Instant.now(),
- requestUid = Base32Crockford.encode(randBytes(16))
- ))
+ nexusCmd.test("initiate-payment $flags \"$payto&amount=CHF:1.1&message=single%20transaction%20test\"").assertOk()
nexusCmd.test("ebics-submit $ebicsFlags").assertOk()
})
}
diff --git a/testbench/src/test/kotlin/IntegrationTest.kt b/testbench/src/test/kotlin/IntegrationTest.kt
@@ -1,6 +1,6 @@
/*
* This file is part of LibEuFin.
- * Copyright (C) 2023 Taler Systems S.A.
+ * Copyright (C) 2023-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
@@ -281,15 +281,7 @@ class IntegrationTest {
val reservePub = randBytes(32);
val amount = TalerAmount("EUR:${20+i}")
val subject = "cashin test $i: ${Base32Crockford.encode(reservePub)}"
- ingestIncomingPayment(db,
- IncomingPayment(
- amount = amount,
- debitPaytoUri = userPayTo.toString(),
- wireTransferSubject = subject,
- executionTime = Instant.now(),
- bankId = Base32Crockford.encode(reservePub)
- )
- )
+ nexusCmd.run("testing fake-incoming $flags --subject \"$subject\" --amount $amount $userPayTo")
val converted = client.get("http://0.0.0.0:8080/conversion-info/cashin-rate?amount_debit=EUR:${20 + i}")
.assertOkJson<ConversionResponse>().amount_credit
client.get("http://0.0.0.0:8080/accounts/exchange/transactions") {