[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[libeufin] branch master updated: nexus submit
From: |
gnunet |
Subject: |
[libeufin] branch master updated: nexus submit |
Date: |
Fri, 27 Oct 2023 16:04:08 +0200 |
This is an automated email from the git hooks/post-receive script.
ms pushed a commit to branch master
in repository libeufin.
The following commit(s) were added to refs/heads/master by this push:
new eb2f11f5 nexus submit
eb2f11f5 is described below
commit eb2f11f533bdde5b0c97335b025fd69847412bb5
Author: MS <ms@taler.net>
AuthorDate: Fri Oct 27 16:01:57 2023 +0200
nexus submit
Fetching the unsubmitted payment initiations from the
database and submit them, in the loop or only once, according
to the configuration.
---
contrib/libeufin-nexus.conf | 2 +-
.../src/main/kotlin/tech/libeufin/nexus/DbInit.kt | 7 +
.../main/kotlin/tech/libeufin/nexus/EbicsSetup.kt | 14 +-
.../main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt | 142 +++++++++++++++++++--
nexus/src/test/kotlin/ConfigLoading.kt | 26 ++++
5 files changed, 170 insertions(+), 21 deletions(-)
diff --git a/contrib/libeufin-nexus.conf b/contrib/libeufin-nexus.conf
index 5db6b00b..e83a2f62 100644
--- a/contrib/libeufin-nexus.conf
+++ b/contrib/libeufin-nexus.conf
@@ -21,7 +21,7 @@ PARTNER_ID = myorg
SYSTEM_ID = banksys
# Name given by the bank to the bank account driven by Nexus.
-ACCOUNT_NUMBER = DE1234567890
+ACCOUNT_NUMBER = payto://iban/BIC/DE1234567890?receiver-name=Nexus-User
# File that holds the bank EBICS keys.
BANK_PUBLIC_KEYS_FILE = enc-auth-keys.json
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt
index b973cdf2..d170eee0 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt
@@ -7,6 +7,13 @@ import tech.libeufin.util.initializeDatabaseTables
import tech.libeufin.util.resetDatabaseTables
import kotlin.system.exitProcess
+/**
+ * Runs the argument and fails the process, if that throws
+ * an exception.
+ *
+ * @param getLambda function that might return a value.
+ * @return the value from getLambda.
+ */
fun <T>doOrFail(getLambda: () -> T): T =
try {
getLambda()
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
index 85b47df3..f79fa7ab 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt
@@ -264,7 +264,7 @@ suspend fun doKeysRequestAndUpdateState(
* @param configFile location of the configuration entry point.
* @return internal representation of the configuration.
*/
-private fun extractEbicsConfig(configFile: String?): EbicsSetupConfig {
+fun extractEbicsConfig(configFile: String?): EbicsSetupConfig {
val config = loadConfigOrFail(configFile)
// Checking the config.
val cfg = try {
@@ -327,16 +327,16 @@ class EbicsSetup: CliktCommand("Set up the EBICS
subscriber") {
* This function collects the main steps of setting up an EBICS access.
*/
override fun run() {
- val cfg = extractEbicsConfig(this.configFile)
+ val cfg = doOrFail { extractEbicsConfig(this.configFile) }
if (checkFullConfig) {
doOrFail {
- cfg.config.requireNumber("nexus-ebics-submit",
"frequency").apply {
- if (this < 0) throw Exception("section
'nexus-ebics-submit' has negative frequency")
+ cfg.config.requireString("nexus-ebics-submit",
"frequency").apply {
+ checkFrequency(this)
}
- cfg.config.requireNumber("nexus-ebics-fetch",
"frequency").apply {
- if (this < 0) throw Exception("section 'nexus-ebics-fetch'
has negative frequency")
+ cfg.config.requireString("nexus-ebics-fetch",
"frequency").apply {
+ checkFrequency(this)
}
- cfg.config.requirePath("nexus-ebics-fetch",
"statement-log-directory")
+ cfg.config.requirePath("nexus-ebics-fetch",
"statement_log_directory")
cfg.config.requireNumber("nexus-httpd", "port")
cfg.config.requirePath("nexus-httpd", "unixpath")
cfg.config.requireString("nexus-httpd", "serve")
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt
b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt
index 3eb1e6a4..c25622d1 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt
@@ -22,9 +22,13 @@ package tech.libeufin.nexus
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.options.option
import io.ktor.client.*
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.time.delay
import tech.libeufin.nexus.ebics.submitPayment
-import tech.libeufin.util.IbanPayto
+import tech.libeufin.util.getDatabaseName
import tech.libeufin.util.parsePayto
+import java.util.*
+import kotlin.concurrent.fixedRateTimer
import kotlin.system.exitProcess
/**
@@ -46,14 +50,14 @@ private suspend fun submitInitiatedPayment(
cfg: EbicsSetupConfig,
clientPrivateKeysFile: ClientPrivateKeysFile,
bankPublicKeysFile: BankPublicKeysFile,
- initiatedPayment: InitiatedPayment,
- debtor: IbanPayto
+ initiatedPayment: InitiatedPayment
): Boolean {
val creditor = parsePayto(initiatedPayment.creditPaytoUri)
if (creditor?.receiverName == null) {
logger.error("Won't create pain.001 without the receiver name")
return false
}
+ val debtor = cfg.accountNumber
if (debtor.bic == null || debtor.receiverName == null) {
logger.error("Won't create pain.001 without the debtor BIC and name")
return false
@@ -74,6 +78,98 @@ private suspend fun submitInitiatedPayment(
return true
}
+/**
+ * Converts human-readable duration in how many seconds. Supports
+ * the suffixes 's' (seconds), 'm' (minute), 'h' (hours). A valid
+ * duration is therefore, for example, Nm, where N is the number of
+ * minutes.
+ *
+ * @param trimmed duration
+ * @return how many seconds is the duration input, or null if the input
+ * is not valid.
+ */
+fun getFrequencyInSeconds(humanFormat: String): Int? {
+ val trimmed = humanFormat.trim()
+ if (trimmed.isEmpty()) {
+ logger.error("Input was empty")
+ return null
+ }
+ val lastChar = trimmed.last()
+ val howManySeconds: Int = when (lastChar) {
+ 's' -> {1}
+ 'm' -> {60}
+ 'h' -> {60 * 60}
+ else -> {
+ logger.error("Duration symbol not one of s, m, h. '$lastChar' was
found instead")
+ return null
+ }
+ }
+ val maybeNumber = trimmed.dropLast(1)
+ val howMany = try {
+ maybeNumber.toInt()
+ } catch (e: Exception) {
+ logger.error("Prefix was not a valid input: '$maybeNumber'")
+ return null
+ }
+ if (howMany == 0) return 0
+ val ret = howMany * howManySeconds
+ if (howMany != ret / howManySeconds) {
+ logger.error("Result overflew")
+ return null
+ }
+ return ret
+}
+
+/**
+ * Sanity-checks the frequency found in the configuration and
+ * either returns it or fails the process. Note: the returned
+ * value is also guaranteed to be non-negative.
+ *
+ * @param foundInConfig frequency value as found in the configuration.
+ * @return the duration in seconds of the value found in the configuration.
+ */
+fun checkFrequency(foundInConfig: String): Int {
+ val frequencySeconds = getFrequencyInSeconds(foundInConfig)
+ if (frequencySeconds == null) {
+ throw Exception("Invalid frequency value")
+ }
+ if (frequencySeconds < 0) {
+ throw Exception("Configuration error: cannot operate with a negative
submit frequency ($foundInConfig)")
+ }
+ return frequencySeconds
+}
+
+private fun submitBatch(
+ cfg: EbicsSetupConfig,
+ db: Database,
+ httpClient: HttpClient,
+ clientKeys: ClientPrivateKeysFile,
+ bankKeys: BankPublicKeysFile
+) {
+ runBlocking {
+ db.initiatedPaymentsUnsubmittedGet(cfg.currency).forEach {
+ val submitted = submitInitiatedPayment(
+ httpClient,
+ cfg,
+ clientKeys,
+ bankKeys,
+ it.value
+ )
+ /**
+ * The following block tries to flag the initiated payment as
submitted,
+ * but it does NOT fail the process if the flagging fails. This
way, we
+ * do NOT block other payments to be submitted.
+ */
+ if (submitted) {
+ val flagged = db.initiatedPaymentSetSubmitted(it.key)
+ if (!flagged) {
+ logger.warn("Initiated payment with row ID ${it.key} could
not be flagged as submitted")
+ }
+ }
+ }
+ }
+}
+
class EbicsSubmit : CliktCommand("Submits any initiated payment found in the
database") {
private val configFile by option(
"--config", "-c",
@@ -83,23 +179,43 @@ class EbicsSubmit : CliktCommand("Submits any initiated
payment found in the dat
/**
* Submits any initiated payment that was not submitted
* so far and -- according to the configuration -- returns
- * or long-polls for new payments.
+ * or long-polls (currently not implemented) for new payments.
*/
override fun run() {
- val cfg = loadConfigOrFail(configFile)
+ val cfg: EbicsSetupConfig = doOrFail { extractEbicsConfig(configFile) }
val frequency: Int = doOrFail {
- cfg.requireNumber("nexus-ebics-submit", "frequency")
+ val configValue = cfg.config.requireString("nexus-ebics-submit",
"frequency")
+ return@doOrFail checkFrequency(configValue)
+
}
- if (frequency < 0) {
- logger.error("Configuration error: cannot operate with a negative
submit frequency ($frequency)")
+ val dbCfg = cfg.config.extractDbConfigOrFail()
+ val db = Database(dbCfg.dbConnStr)
+ val httpClient = HttpClient()
+ val bankKeys = loadBankKeys(cfg.bankPublicKeysFilename)
+ if (bankKeys == null) {
+ logger.error("Could not find the bank keys at:
${cfg.bankPublicKeysFilename}")
exitProcess(1)
}
- if (frequency == 0) {
- logger.error("Long-polling not implemented, set frequency > 0")
+ if (!bankKeys.accepted) {
+ logger.error("Bank keys are not accepted, yet. Won't submit any
payment.")
exitProcess(1)
}
- val dbCfg = cfg.extractDbConfigOrFail()
- val db = Database(dbCfg.dbConnStr)
- throw NotImplementedError("to be done")
+ val clientKeys = loadPrivateKeysFromDisk(cfg.clientPrivateKeysFilename)
+ if (clientKeys == null) {
+ logger.error("Client private keys not found at:
${cfg.clientPrivateKeysFilename}")
+ exitProcess(1)
+ }
+ if (frequency == 0) {
+ logger.warn("Long-polling not implemented, submitting what is
found and exit")
+ submitBatch(cfg, db, httpClient, clientKeys, bankKeys)
+ return
+ }
+ fixedRateTimer(
+ name = "ebics submit period",
+ period = (frequency * 1000).toLong(),
+ action = {
+ submitBatch(cfg, db, httpClient, clientKeys, bankKeys)
+ }
+ )
}
}
\ No newline at end of file
diff --git a/nexus/src/test/kotlin/ConfigLoading.kt
b/nexus/src/test/kotlin/ConfigLoading.kt
index 1216a13a..e281399a 100644
--- a/nexus/src/test/kotlin/ConfigLoading.kt
+++ b/nexus/src/test/kotlin/ConfigLoading.kt
@@ -2,6 +2,9 @@ import org.junit.Test
import org.junit.jupiter.api.assertThrows
import tech.libeufin.nexus.EbicsSetupConfig
import tech.libeufin.nexus.NEXUS_CONFIG_SOURCE
+import tech.libeufin.nexus.getFrequencyInSeconds
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
class ConfigLoading {
/**
@@ -16,6 +19,15 @@ class ConfigLoading {
cfg._dump()
}
+ @Test
+ fun loadPath() {
+ val handle = TalerConfig(NEXUS_CONFIG_SOURCE)
+ handle.load()
+ val cfg = EbicsSetupConfig(handle)
+ cfg.config.requirePath("nexus-ebics-fetch", "statement_log_directory")
+ }
+
+
/**
* Tests that if the configuration lacks at least one option, then
* the config loader throws exception.
@@ -32,4 +44,18 @@ class ConfigLoading {
EbicsSetupConfig(handle)
}
}
+
+ // Checks converting human-readable durations to seconds.
+ @Test
+ fun timeParsing() {
+ assertEquals(1, getFrequencyInSeconds("1s"))
+ assertEquals(10*60, getFrequencyInSeconds("10m"))
+ assertEquals(24*60*60, getFrequencyInSeconds("24h"))
+ assertEquals(60*60, getFrequencyInSeconds(" 1h "))
+ assertEquals(60*60, getFrequencyInSeconds("01h"))
+ assertNull(getFrequencyInSeconds("1.1s"))
+ assertNull(getFrequencyInSeconds(" "))
+ assertNull(getFrequencyInSeconds("m"))
+ assertNull(getFrequencyInSeconds(""))
+ }
}
\ No newline at end of file
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [libeufin] branch master updated: nexus submit,
gnunet <=