libeufin

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

commit 5e46be49604cd727c8ee0fb3f63b3a5a8d627005
parent d34f0a94bd14d9ce328c48937c9f7c5f61845e3f
Author: Antoine A <>
Date:   Tue, 27 May 2025 16:17:06 +0200

nexus: improve bounce fee logic

Diffstat:
Mcontrib/nexus.conf | 6++++++
Mnexus/conf/skip.conf | 2+-
Mnexus/src/main/kotlin/tech/libeufin/nexus/Config.kt | 20+++++++++++++-------
Mnexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsFetch.kt | 10++++++++--
Mnexus/src/test/kotlin/RegistrationTest.kt | 19+++++++++++++++++--
5 files changed, 45 insertions(+), 12 deletions(-)

diff --git a/contrib/nexus.conf b/contrib/nexus.conf @@ -66,6 +66,12 @@ CHECKPOINT_TIME_OF_DAY = 19:00 # Ignore all malformed transactions prior to a certain date, useful when you want to import old transactions without bouncing the malformed ones a second time # IGNORE_BOUNCES_BEFORE = YYYY-MM-DD +# Wether to deduce the fee payed by the exchange account from the bounced amount +# BOUNCE_DEDUCE_FEE = NO + +# An additional fee to deduce from the bounced amount +# BOUNCE_FEE = KUDOS:0 + [nexus-submit] # How often should ebics-fetch submit pending transactions FREQUENCY = 30m diff --git a/nexus/conf/skip.conf b/nexus/conf/skip.conf @@ -1,5 +1,5 @@ [nexus-ebics] -CURRENCY = CHF +CURRENCY = KUDOS BANK_DIALECT = postfinance HOST_BASE_URL = https://isotest.postfinance.ch/ebicsweb/ebicsweb BANK_PUBLIC_KEYS_FILE = test/tmp/bank-keys.json diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Config.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/Config.kt @@ -1,6 +1,6 @@ /* * This file is part of LibEuFin. - * Copyright (C) 2024 Taler Systems S.A. + * Copyright (C) 2024-2025 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 @@ -31,21 +31,25 @@ val NEXUS_CONFIG_SOURCE = ConfigSource("libeufin", "libeufin-nexus", "libeufin-n data class NexusIngestConfig( val accountType: AccountType, val ignoreTransactionsBefore: Instant, - val ignoreBouncesBefore: Instant + val ignoreBouncesBefore: Instant, + val bounceDeduceFee: Boolean, + val bounceFee: TalerAmount ) { companion object { - fun default(accountType: AccountType) - = NexusIngestConfig(accountType, Instant.MIN, Instant.MIN) + fun default(accountType: AccountType, currency: String = "KUDOS") + = NexusIngestConfig(accountType, Instant.MIN, Instant.MIN, false, TalerAmount.zero(currency)) } } -class NexusFetchConfig(config: TalerConfig) { +class NexusFetchConfig(config: TalerConfig, currency: String) { private val section = config.section("nexus-fetch") val frequency = section.duration("frequency").require() val frequencyRaw = section.string("frequency").require() val checkpointTime = section.time("checkpoint_time_of_day").require() val ignoreTransactionsBefore = section.date("ignore_transactions_before").default(Instant.MIN) val ignoreBouncesBefore = section.date("ignore_bounces_before").default(Instant.MIN) + val bounceDeduceFee = section.boolean("bounce_deduce_fee").default(false) + val bounceFee = section.amount("bounce_fee", currency).default(TalerAmount.zero(currency)) } class NexusSubmitConfig(config: TalerConfig) { @@ -111,14 +115,16 @@ class NexusConfig internal constructor (val cfg: TalerConfig) { "exchange" to AccountType.exchange )).require() - val fetch by lazy { NexusFetchConfig(cfg) } + val fetch by lazy { NexusFetchConfig(cfg, currency) } val submit by lazy { NexusSubmitConfig(cfg) } val ebics by lazy { NexusEbicsConfig(sect) } val ingest get() = NexusIngestConfig( accountType, fetch.ignoreTransactionsBefore, - fetch.ignoreBouncesBefore + fetch.ignoreBouncesBefore, + fetch.bounceDeduceFee, + fetch.bounceFee ) val wireGatewayApiCfg = cfg.section("nexus-httpd-wire-gateway-api").apiConf() diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsFetch.kt b/nexus/src/main/kotlin/tech/libeufin/nexus/cli/EbicsFetch.kt @@ -127,14 +127,20 @@ suspend fun registerIncomingPayment( logRes(res, suffix = "ignored bounce: $msg") } else { var bounceAmount = payment.amount - if (payment.creditFee != null) { + if (payment.creditFee != null && cfg.bounceDeduceFee) { if (payment.creditFee > bounceAmount) { val res = db.payment.registerIncoming(payment) - logRes(res, suffix = "skip bounce (fee higher than amount): $msg") + logRes(res, suffix = "skip bounce (transfer fee higher than amount): $msg") return } bounceAmount -= payment.creditFee } + if (cfg.bounceFee > bounceAmount) { + val res = db.payment.registerIncoming(payment) + logRes(res, suffix = "skip bounce (bounce fee higher than amount): $msg") + return + } + bounceAmount -= cfg.bounceFee val res = db.payment.registerMalformedIncoming( payment, bounceAmount, diff --git a/nexus/src/test/kotlin/RegistrationTest.kt b/nexus/src/test/kotlin/RegistrationTest.kt @@ -185,6 +185,7 @@ class RegistrationTest { ) } } + println(bounced_tx) assertContentEquals(bounced, bounced_tx) } @@ -554,17 +555,31 @@ class RegistrationTest { bounced = listOf( OutgoingPayment( id = OutgoingId(null, null, null), - amount = TalerAmount("CHF:0.8"), + amount = TalerAmount("CHF:1"), subject = "bounce: 7371795e-62fa-42dd-93b7-da89cc120faa", executionTime = Instant.EPOCH, creditor = ibanPayto("CH7389144832588726658", "Mr Test") ), OutgoingPayment( id = OutgoingId(null, null, null), - amount = TalerAmount("CHF:0.3"), + amount = TalerAmount("CHF:0.5"), subject = "bounce: 50523424675.0001", executionTime = Instant.EPOCH, creditor = ibanPayto("CH7389144832588726658", "Grothoff Hans") + ), + OutgoingPayment( + id = OutgoingId(null, null, null), + amount = TalerAmount("CHF:0.15"), + subject = "bounce: f203fbb4-6e13-4c78-9b2a-d852fea6374a", + executionTime = Instant.EPOCH, + creditor = ibanPayto("CH7389144832588726658", "Grothoff Hans") + ), + OutgoingPayment( + id = OutgoingId(null, null, null), + amount = TalerAmount("CHF:0.1"), + subject = "bounce: 81b0d8c6-a677-4577-b75e-a639dcc03681", + executionTime = Instant.EPOCH, + creditor = ibanPayto("CH7389144832588726658", "Grothoff Hans") ) ) )