gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libeufin] branch master updated: nexus fetch


From: gnunet
Subject: [libeufin] branch master updated: nexus fetch
Date: Tue, 21 Nov 2023 10:01:17 +0100

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 28d3c8f0 nexus fetch
28d3c8f0 is described below

commit 28d3c8f0608632174cb88379a75742a6c0b1d375
Author: MS <ms@taler.net>
AuthorDate: Tue Nov 21 09:55:23 2023 +0100

    nexus fetch
    
    bouncing payments whose amounts are lower than
    a configurable threshold.  The bouncing however
    ends with the creation of the related database
    row (as a logging mean), but they never get reimbursed.
---
 .../main/kotlin/tech/libeufin/nexus/Database.kt    | 20 +++--
 .../main/kotlin/tech/libeufin/nexus/EbicsFetch.kt  | 88 ++++++++++++++++++----
 nexus/src/test/kotlin/DatabaseTest.kt              |  2 +-
 nexus/src/test/kotlin/Parsing.kt                   | 14 +++-
 4 files changed, 103 insertions(+), 21 deletions(-)

diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt
index d80fd665..7e4c98e5 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt
@@ -11,7 +11,7 @@ import java.sql.SQLException
 import java.time.Instant
 
 // Remove this once TalerAmount from the bank
-// module gets moved to the 'util' module.
+// module gets moved to the 'util' module (#7987).
 data class TalerAmount(
     val value: Long,
     val fraction: Int, // has at most 8 digits.
@@ -287,10 +287,14 @@ class Database(dbConfig: String): java.io.Closeable {
      * @param paymentData information related to the incoming payment.
      * @param requestUid unique identifier of the outgoing payment to
      *                   initiate, in order to reimburse the bounced tx.
+     * @param refundAmount amount to send back to the original debtor.  If
+     *                     null, it defaults to the amount of the bounced
+     *                     incoming payment.
      */
     suspend fun incomingPaymentCreateBounced(
         paymentData: IncomingPayment,
-        requestUid: String
+        requestUid: String,
+        refundAmount: TalerAmount? = null
         ): Boolean = runConn { conn ->
         val refundTimestamp = Instant.now().toDbMicros()
             ?: throw Exception("Could not convert refund execution time from 
Instant.now() to microsends.")
@@ -306,8 +310,12 @@ class Database(dbConfig: String): java.io.Closeable {
               ,?
               ,?
             )""")
-        stmt.setLong(1, paymentData.amount.value)
-        stmt.setInt(2, paymentData.amount.fraction)
+
+        var finalAmount = paymentData.amount
+        if (refundAmount != null) finalAmount = refundAmount
+
+        stmt.setLong(1, finalAmount.value)
+        stmt.setInt(2, finalAmount.fraction)
         stmt.setString(3, paymentData.wireTransferSubject)
         stmt.setLong(4, executionTime)
         stmt.setString(5, paymentData.debitPaytoUri)
@@ -548,8 +556,8 @@ class Database(dbConfig: String): java.io.Closeable {
              ,initiation_time
              ,request_uid
              FROM initiated_outgoing_transactions
-             WHERE submitted='unsubmitted'
-               OR submitted='transient_failure';
+             WHERE (submitted='unsubmitted' OR submitted='transient_failure')
+               AND ((amount).val != 0 OR (amount).frac != 0);
         """)
         val maybeMap = mutableMapOf<Long, InitiatedPayment>()
         stmt.executeQuery().use {
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt
index 53bf2ea7..768dde2f 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/EbicsFetch.kt
@@ -51,16 +51,21 @@ data class FetchContext(
     /**
      * EBICS version.  For the HAC message type, version gets switched to 
EBICS 2.
      */
-    var ebicsVersion: EbicsVersion = EbicsVersion.three,
+    var ebicsVersion: EbicsVersion,
     /**
-     * Start date of the returned documents.  Only
-     * used in --transient mode.
+     * Logs to STDERR the init phase of an EBICS download request.
      */
-    var pinnedStart: Instant? = null,
+    val ebicsExtraLog: Boolean,
     /**
-     * Logs to STDERR the init phase of an EBICS download request.
+     * Not triggering any Taler logic, if the incoming amount
+     * is below the following value.
+     */
+    val minimumAmount: TalerAmount?,
+    /**
+     * Start date of the returned documents.  Only
+     * used in --transient mode.
      */
-    val ebicsExtraLog: Boolean = false
+    var pinnedStart: Instant? = null
 )
 
 /**
@@ -191,13 +196,14 @@ private fun makeTalerFrac(bankFrac: String): Int {
  */
 fun getTalerAmount(
     noCurrencyAmount: String,
-    currency: String
+    currency: String,
+    errorMessagePrefix: String = ""
 ): TalerAmount {
-    if (currency.isEmpty()) throw Exception("Currency is empty")
+    if (currency.isEmpty()) throw Exception("Wrong helper invocation: currency 
is empty")
     val split = noCurrencyAmount.split(".")
     // only 1 (no fraction) or 2 (with fraction) sizes allowed.
-    if (split.size != 1 && split.size != 2) throw Exception("Invalid amount: 
${noCurrencyAmount}")
-    val value = split[0].toLongOrNull() ?: throw Exception("value part not a 
long")
+    if (split.size != 1 && split.size != 2) throw 
Exception("${errorMessagePrefix}invalid amount: $noCurrencyAmount")
+    val value = split[0].toLongOrNull() ?: throw 
Exception("${errorMessagePrefix}value part not a long")
     if (split.size == 1) return TalerAmount(
         value = value,
         fraction = 0,
@@ -322,6 +328,7 @@ private suspend fun ingestOutgoingPayment(
  */
 private suspend fun ingestIncomingPayment(
     db: Database,
+    ctx: FetchContext,
     incomingPayment: IncomingPayment
 ) {
     logger.debug("Ingesting incoming payment UID: 
${incomingPayment.bankTransferId}, subject: 
${incomingPayment.wireTransferSubject}")
@@ -329,16 +336,55 @@ private suspend fun ingestIncomingPayment(
         logger.debug("Incoming payment with UID 
'${incomingPayment.bankTransferId}' already seen.")
         return
     }
+    if (
+        ctx.minimumAmount != null &&
+        firstLessThanSecond(
+            incomingPayment.amount,
+            ctx.minimumAmount
+        )) {
+        /**
+         * Setting the refund amount to zero makes the initiated
+         * payment _never_ paid back.  Inserting this row merely
+         * logs the incoming payment event, for which the policy
+         * has no reimbursement.
+         */
+        db.incomingPaymentCreateBounced(
+            incomingPayment,
+            UUID.randomUUID().toString().take(35),
+            TalerAmount(0, 0, ctx.cfg.currency)
+        )
+        return
+    }
     val reservePub = getTalerReservePub(db, incomingPayment)
     if (reservePub == null) {
         db.incomingPaymentCreateBounced(
-            incomingPayment, UUID.randomUUID().toString().take(35)
+            incomingPayment,
+            UUID.randomUUID().toString().take(35)
         )
         return
     }
     db.incomingTalerablePaymentCreate(incomingPayment, reservePub)
 }
 
+/**
+ * Compares amounts.
+ *
+ * @param a first argument
+ * @param b second argument
+ * @return true if the first argument
+ *         is less than the second
+ */
+fun firstLessThanSecond(
+    a: TalerAmount,
+    b: TalerAmount
+): Boolean {
+    if (a.currency != b.currency)
+        throw Exception("different currencies: ${a.currency} vs. 
${b.currency}")
+    if (a.value == b.value)
+        return a.fraction < b.fraction
+    return a.value < b.value
+}
+
 /**
  * Parses the response of an EBICS notification looking for
  * incoming payments.  As a result, it either creates a Taler
@@ -391,7 +437,7 @@ private fun ingestNotification(
     try {
         runBlocking {
             incomingPayments.forEach {
-                ingestIncomingPayment(db, it)
+                ingestIncomingPayment(db, ctx, it)
             }
             outgoingPayments.forEach {
                 ingestOutgoingPayment(db, it)
@@ -562,13 +608,29 @@ class EbicsFetch: CliktCommand("Fetches bank records.  
Defaults to camt.054 noti
             }
             return
         }
+        val minAmountCfg: String? = cfg.config.lookupString(
+            "nexus-fetch",
+            "minimum_amount"
+        )
+        var minAmount: TalerAmount? = null
+        if (minAmountCfg != null) {
+            minAmount = doOrFail {
+                getTalerAmount(
+                    cfg.currency,
+                    minAmountCfg,
+                    "[nexus-fetch]/minimum_amount, "
+                )
+            }
+        }
         val ctx = FetchContext(
             cfg,
             HttpClient(),
             clientKeys,
             bankKeys,
             whichDoc,
-            ebicsExtraLog = ebicsExtraLog
+            EbicsVersion.three,
+            ebicsExtraLog,
+            minAmount
         )
         if (transient) {
             logger.info("Transient mode: fetching once and returning.")
diff --git a/nexus/src/test/kotlin/DatabaseTest.kt 
b/nexus/src/test/kotlin/DatabaseTest.kt
index 0f2cc4fa..cacec157 100644
--- a/nexus/src/test/kotlin/DatabaseTest.kt
+++ b/nexus/src/test/kotlin/DatabaseTest.kt
@@ -176,7 +176,7 @@ class PaymentInitiationsTest {
             assertEquals(db.initiatedPaymentCreate(initPay), 
PaymentInitiationOutcome.SUCCESS)
             assertEquals(db.initiatedPaymentCreate(initPay), 
PaymentInitiationOutcome.UNIQUE_CONSTRAINT_VIOLATION)
             val haveOne = db.initiatedPaymentsSubmittableGet("KUDOS")
-            assertTrue {
+            assertTrue("Size ${haveOne.size} instead of 1") {
                 haveOne.size == 1
                         && haveOne.containsKey(1)
                         && haveOne[1]?.requestUid == "unique"
diff --git a/nexus/src/test/kotlin/Parsing.kt b/nexus/src/test/kotlin/Parsing.kt
index 45737e1f..4d833d1e 100644
--- a/nexus/src/test/kotlin/Parsing.kt
+++ b/nexus/src/test/kotlin/Parsing.kt
@@ -11,6 +11,18 @@ import kotlin.test.assertTrue
 
 class Parsing {
 
+    @Test // move eventually to util (#7987)
+    fun amountComparison() {
+        val one = TalerAmount(1, 0, "KUDOS")
+        val two = TalerAmount(2, 0, "KUDOS")
+        val moreFrac = TalerAmount(2, 4, "KUDOS")
+        val lessFrac = TalerAmount(2, 3, "KUDOS")
+        val zeroMoreFrac = TalerAmount(0, 4, "KUDOS")
+        val zeroLessFrac = TalerAmount(0, 3, "KUDOS")
+        assertTrue(firstLessThanSecond(one, two))
+        assertTrue(firstLessThanSecond(lessFrac, moreFrac))
+        assertTrue(firstLessThanSecond(zeroLessFrac, zeroMoreFrac))
+    }
     @Test
     fun gregorianTime() {
         parseCamtTime("2023-11-06T20:00:00")
@@ -120,7 +132,7 @@ class Parsing {
             getTalerAmount("1.", "KUDOS")
         }
         assertThrows<Exception> {
-            getTalerAmount("0.123456789", "KUDOS")
+            getTalerAmount("0.123", "KUDOS")
         }
         assertThrows<Exception> {
             getTalerAmount("noise", "KUDOS")

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]