libeufin

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

commit d1dcd5d2fccefc7ccffae3a86918c52517bff654
parent 8e54274d33d7af9d0db849b66a9487e78691a238
Author: MS <ms@taler.net>
Date:   Thu, 26 Oct 2023 19:43:00 +0200

nexus: drafting the ebics-submit subcommand.

Diffstat:
Mnexus/src/main/kotlin/tech/libeufin/nexus/Database.kt | 2+-
Mnexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt | 5++---
Mnexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt | 16++++++++++------
Anexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mnexus/src/main/kotlin/tech/libeufin/nexus/Main.kt | 2+-
Mnexus/src/test/kotlin/PostFinance.kt | 7+++++--
6 files changed, 125 insertions(+), 13 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt @@ -340,7 +340,7 @@ class Database(dbConfig: String): java.io.Closeable { * bank yet. * * @param currency in which currency should the payment be submitted to the bank. - * @return potentially empty list of initiated payments. + * @return [Map] of the initiated payment row ID and [InitiatedPayment] */ suspend fun initiatedPaymentsUnsubmittedGet(currency: String): Map<Long, InitiatedPayment> = runConn { conn -> val stmt = conn.prepareStatement(""" diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/DbInit.kt @@ -7,14 +7,13 @@ import tech.libeufin.util.initializeDatabaseTables import tech.libeufin.util.resetDatabaseTables import kotlin.system.exitProcess -fun doOrFail(doLambda: () -> Unit) { +fun <T>doOrFail(getLambda: () -> T): T = try { - doLambda() + getLambda() } catch (e: Exception) { logger.error(e.message) exitProcess(1) } -} /** * This subcommand tries to load the SQL files that define diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSetup.kt @@ -27,7 +27,6 @@ import kotlinx.coroutines.runBlocking import tech.libeufin.util.ebics_h004.EbicsTypes import java.io.File import kotlin.system.exitProcess -import TalerConfig import TalerConfigError import kotlinx.serialization.encodeToString import tech.libeufin.nexus.ebics.* @@ -429,11 +428,15 @@ class EbicsSetup: CliktCommand("Set up the EBICS subscriber") { val cfg = extractEbicsConfig(this.configFile) if (checkFullConfig) { doOrFail { - cfg.config.requireString("nexus-ebics-submit", "frequency") - cfg.config.requireString("nexus-ebics-fetch", "frequency") - cfg.config.requireString("nexus-ebics-fetch", "statement-log-directory") - cfg.config.requireString("nexus-httpd", "port") - cfg.config.requireString("nexus-httpd", "unixpath") + cfg.config.requireNumber("nexus-ebics-submit", "frequency").apply { + if (this < 0) throw Exception("section 'nexus-ebics-submit' has negative frequency") + } + cfg.config.requireNumber("nexus-ebics-fetch", "frequency").apply { + if (this < 0) throw Exception("section 'nexus-ebics-fetch' has negative frequency") + } + 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") cfg.config.requireString("nexus-httpd-wire-gateway-facade", "enabled") cfg.config.requireString("nexus-httpd-wire-gateway-facade", "auth_method") @@ -442,6 +445,7 @@ class EbicsSetup: CliktCommand("Set up the EBICS subscriber") { cfg.config.requireString("nexus-httpd-revenue-facade", "auth_method") cfg.config.requireString("nexus-httpd-revenue-facade", "auth_token") } + return } // Config is sane. Go (maybe) making the private keys. val privsMaybe = preparePrivateKeys(cfg.clientPrivateKeysFilename) diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsSubmit.kt @@ -0,0 +1,105 @@ +/* + * This file is part of LibEuFin. + * Copyright (C) 2023 Stanisci and Dold. + + * 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 + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.option +import io.ktor.client.* +import tech.libeufin.nexus.ebics.submitPayment +import tech.libeufin.util.IbanPayto +import tech.libeufin.util.parsePayto +import kotlin.system.exitProcess + +/** + * Takes the initiated payment data, as it was returned from the + * database, sanity-checks it, makes the pain.001 document and finally + * submits it via EBICS to the bank. + * + * @param httpClient HTTP client to connect to the bank. + * @param cfg configuration handle. Contains the bank URL and EBICS IDs. + * @param clientPrivateKeysFile client's private EBICS keys. + * @param bankPublicKeysFile bank's public EBICS keys. + * @param initiatedPayment payment initiation from the database. + * @param debtor bank account information of the debited party. + * This values needs the BIC and the name. + * @return true on success, false otherwise. + */ +private suspend fun submitInitiatedPayment( + httpClient: HttpClient, + cfg: EbicsSetupConfig, + clientPrivateKeysFile: ClientPrivateKeysFile, + bankPublicKeysFile: BankPublicKeysFile, + initiatedPayment: InitiatedPayment, + debtor: IbanPayto +): Boolean { + val creditor = parsePayto(initiatedPayment.creditPaytoUri) + if (creditor?.receiverName == null) { + logger.error("Won't create pain.001 without the receiver name") + return false + } + if (debtor.bic == null || debtor.receiverName == null) { + logger.error("Won't create pain.001 without the debtor BIC and name") + return false + } + if (initiatedPayment.wireTransferSubject == null) { + logger.error("Won't create pain.001 without the wire transfer subject") + return false + } + val xml = createPain001( + requestUid = initiatedPayment.requestUid, + initiationTimestamp = initiatedPayment.initiationTime, + amount = initiatedPayment.amount, + creditAccount = creditor, + debitAccount = debtor, + wireTransferSubject = initiatedPayment.wireTransferSubject + ) + submitPayment(xml, cfg, clientPrivateKeysFile, bankPublicKeysFile, httpClient) + return true +} + +class EbicsSubmit : CliktCommand("Submits any initiated payment found in the database") { + private val configFile by option( + "--config", "-c", + help = "set the configuration file" + ) + + /** + * Submits any initiated payment that was not submitted + * so far and -- according to the configuration -- returns + * or long-polls for new payments. + */ + override fun run() { + val cfg = loadConfigOrFail(configFile) + val frequency: Int = doOrFail { + cfg.requireNumber("nexus-ebics-submit", "frequency") + } + if (frequency < 0) { + logger.error("Configuration error: cannot operate with a negative submit frequency ($frequency)") + exitProcess(1) + } + if (frequency == 0) { + logger.error("Long-polling not implemented, set frequency > 0") + exitProcess(1) + } + val dbCfg = cfg.extractDbConfigOrFail() + val db = Database(dbCfg.dbConnStr) + throw NotImplementedError("to be done") + } +} +\ No newline at end of file diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Main.kt @@ -312,7 +312,7 @@ fun TalerConfig.extractDbConfigOrFail(): DatabaseConfig = class LibeufinNexusCommand : CliktCommand() { init { versionOption(getVersion()) - subcommands(EbicsSetup(), DbInit()) + subcommands(EbicsSetup(), DbInit(), EbicsSubmit()) } override fun run() = Unit } diff --git a/nexus/src/test/kotlin/PostFinance.kt b/nexus/src/test/kotlin/PostFinance.kt @@ -15,6 +15,8 @@ import java.time.format.DateTimeFormatter import kotlin.test.assertNotNull import kotlin.test.assertTrue +// Tests only manual, that's why they are @Ignore + private fun prep(): EbicsSetupConfig { val handle = TalerConfig(NEXUS_CONFIG_SOURCE) val ebicsUserId = File("/tmp/pofi-ebics-user-id.txt").readText() @@ -23,6 +25,7 @@ private fun prep(): EbicsSetupConfig { return EbicsSetupConfig(handle) } +@Ignore class Iso20022 { @Test fun sendPayment() { @@ -36,13 +39,13 @@ class Iso20022 { parsePayto("payto://iban/CH9300762011623852957?receiver-name=NotGiven")!! ) runBlocking { - submitPayment( + assertTrue(submitPayment( xml, cfg, loadPrivateKeysFromDisk(cfg.clientPrivateKeysFilename)!!, loadBankKeys(cfg.bankPublicKeysFilename)!!, HttpClient() - ) + )) } } }