gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated: nexus db: outgoing payments logic.


From: gnunet
Subject: [libeufin] branch master updated: nexus db: outgoing payments logic.
Date: Tue, 24 Oct 2023 15:33:35 +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 217975d6 nexus db: outgoing payments logic.
217975d6 is described below

commit 217975d6bc77d84aa391cb02bba2bf4f317576a5
Author: MS <ms@taler.net>
AuthorDate: Tue Oct 24 15:32:31 2023 +0200

    nexus db: outgoing payments logic.
---
 database-versioning/libeufin-nexus-procedures.sql  | 53 +++++++++++++++++
 .../main/kotlin/tech/libeufin/nexus/Database.kt    | 69 +++++++++++++++++++++-
 nexus/src/test/kotlin/Common.kt                    | 12 +++-
 nexus/src/test/kotlin/DatabaseTest.kt              | 44 ++++++++++++--
 4 files changed, 171 insertions(+), 7 deletions(-)

diff --git a/database-versioning/libeufin-nexus-procedures.sql 
b/database-versioning/libeufin-nexus-procedures.sql
index f807fb21..b6150cec 100644
--- a/database-versioning/libeufin-nexus-procedures.sql
+++ b/database-versioning/libeufin-nexus-procedures.sql
@@ -1,6 +1,59 @@
 BEGIN;
 SET search_path TO libeufin_nexus;
 
+CREATE OR REPLACE FUNCTION create_outgoing_tx(
+  IN in_amount taler_amount
+  ,IN in_wire_transfer_subject TEXT
+  ,IN in_execution_time BIGINT
+  ,IN in_credit_payto_uri TEXT
+  ,IN in_bank_transfer_id TEXT
+  ,IN in_initiated_id BIGINT
+  ,OUT out_nx_initiated BOOLEAN
+)
+LANGUAGE plpgsql AS $$
+DECLARE
+new_outgoing_transaction_id BIGINT;
+BEGIN
+
+IF in_initiated_id IS NULL THEN
+  out_nx_initiated = FALSE;
+ELSE
+  PERFORM 1
+    FROM initiated_outgoing_transactions
+    WHERE initiated_outgoing_transaction_id = in_initiated_id;
+    IF NOT FOUND THEN
+      out_nx_initiated = TRUE;
+      RETURN;
+      END IF;
+END IF;
+
+INSERT INTO outgoing_transactions (
+  amount
+  ,wire_transfer_subject
+  ,execution_time
+  ,credit_payto_uri
+  ,bank_transfer_id
+) VALUES (
+  in_amount
+  ,in_wire_transfer_subject
+  ,in_execution_time
+  ,in_credit_payto_uri
+  ,in_bank_transfer_id
+)
+  RETURNING outgoing_transaction_id
+    INTO new_outgoing_transaction_id;
+
+IF in_initiated_id IS NOT NULL
+THEN
+  UPDATE initiated_outgoing_transactions
+    SET outgoing_transaction_id = new_outgoing_transaction_id
+    WHERE initiated_outgoing_transaction_id = in_initiated_id;
+END IF;
+END $$;
+
+COMMENT ON FUNCTION create_outgoing_tx(taler_amount, TEXT, BIGINT, TEXT, TEXT, 
BIGINT)
+  IS 'Creates a new outgoing payment and optionally reconciles the related 
initiated payment with it.  If the initiated payment to reconcile is not found, 
it inserts NOTHING.';
+
 CREATE OR REPLACE FUNCTION bounce_payment(
   IN in_incoming_transaction_id BIGINT
   ,IN in_initiation_time BIGINT
diff --git a/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt 
b/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt
index 2c1b4400..e8d8bd83 100644
--- a/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt
+++ b/nexus/src/main/kotlin/tech/libeufin/nexus/Database.kt
@@ -25,7 +25,7 @@ data class TalerAmount(
  */
 data class IncomingPayment(
     val amount: TalerAmount,
-    val wireTransferSubject: String,
+    val wireTransferSubject: String?,
     val debitPaytoUri: String,
     val executionTime: Instant,
     val bankTransferId: String,
@@ -56,6 +56,25 @@ enum class PaymentInitiationOutcome {
     SUCCESS
 }
 
+// OUTGOING PAYMENTS STRUCTS
+
+data class OutgoingPayment(
+    val amount: TalerAmount,
+    val wireTransferSubject: String?,
+    val executionTime: Instant,
+    val creditPaytoUri: String,
+    val bankTransferId: String
+)
+
+/**
+ * Witnesses the outcome of inserting an outgoing
+ * payment into the database.
+ */
+enum class OutgoingPaymentOutcome {
+    INITIATED_COUNTERPART_NOT_FOUND,
+    SUCCESS
+}
+
 /**
  * Performs a INSERT, UPDATE, or DELETE operation.
  *
@@ -111,6 +130,54 @@ class Database(dbConfig: String): java.io.Closeable {
         }
     }
 
+    // OUTGOING PAYMENTS METHODS
+
+    /**
+     * Creates one outgoing payment OPTIONALLY reconciling it with its
+     * initiated payment counterpart.
+     *
+     * @param paymentData information about the outgoing payment.
+     * @param reconcileId optional row ID of the initiated payment
+     *        that will reference this one.  Note: if this value is
+     *        not found, then NO row gets inserted in the database.
+     * @return operation outcome enum.
+     */
+    suspend fun outgoingPaymentCreate(
+        paymentData: OutgoingPayment,
+        reconcileId: Long? = null
+    ): OutgoingPaymentOutcome = runConn {
+        val stmt = it.prepareStatement("""
+            SELECT out_nx_initiated
+              FROM create_outgoing_tx(
+                (?,?)::taler_amount
+                ,?
+                ,?
+                ,?
+                ,?
+                ,?
+              )"""
+        )
+        val executionTime = paymentData.executionTime.toDbMicros()
+            ?: throw Exception("Could not convert outgoing payment 
execution_time to microseconds")
+        stmt.setLong(1, paymentData.amount.value)
+        stmt.setInt(2, paymentData.amount.fraction)
+        stmt.setString(3, paymentData.wireTransferSubject)
+        stmt.setLong(4, executionTime)
+        stmt.setString(5, paymentData.creditPaytoUri)
+        stmt.setString(6, paymentData.bankTransferId)
+        if (reconcileId == null)
+            stmt.setNull(7, java.sql.Types.BIGINT)
+        else
+            stmt.setLong(7, reconcileId)
+
+        stmt.executeQuery().use {
+            if (!it.next()) throw Exception("Inserting outgoing payment gave 
no outcome.")
+            if (it.getBoolean("out_nx_initiated"))
+                return@runConn 
OutgoingPaymentOutcome.INITIATED_COUNTERPART_NOT_FOUND
+        }
+        return@runConn OutgoingPaymentOutcome.SUCCESS
+    }
+
     // INCOMING PAYMENTS METHODS
 
     /**
diff --git a/nexus/src/test/kotlin/Common.kt b/nexus/src/test/kotlin/Common.kt
index 3e89c854..23faab6b 100644
--- a/nexus/src/test/kotlin/Common.kt
+++ b/nexus/src/test/kotlin/Common.kt
@@ -82,7 +82,7 @@ fun genInitPay(subject: String, rowUuid: String? = null) =
     )
 
 // Generates an incoming payment, given its subject.
-fun genIncPay(subject: String, rowUuid: String? = null) =
+fun genIncPay(subject: String? = null, rowUuid: String? = null) =
     IncomingPayment(
         amount = TalerAmount(44, 0, "KUDOS"),
         debitPaytoUri = "payto://iban/not-used",
@@ -90,4 +90,14 @@ fun genIncPay(subject: String, rowUuid: String? = null) =
         executionTime = Instant.now(),
         bounced = false,
         bankTransferId = "entropic"
+    )
+
+// Generates an outgoing payment, given its subject.
+fun genOutPay(subject: String? = null) =
+    OutgoingPayment(
+        amount = TalerAmount(44, 0, "KUDOS"),
+        creditPaytoUri = "payto://iban/not-used",
+        wireTransferSubject = subject,
+        executionTime = Instant.now(),
+        bankTransferId = "entropic"
     )
\ No newline at end of file
diff --git a/nexus/src/test/kotlin/DatabaseTest.kt 
b/nexus/src/test/kotlin/DatabaseTest.kt
index 8eae1040..367aa650 100644
--- a/nexus/src/test/kotlin/DatabaseTest.kt
+++ b/nexus/src/test/kotlin/DatabaseTest.kt
@@ -1,14 +1,46 @@
 import kotlinx.coroutines.runBlocking
 import org.junit.Test
-import tech.libeufin.nexus.InitiatedPayment
-import tech.libeufin.nexus.NEXUS_CONFIG_SOURCE
-import tech.libeufin.nexus.PaymentInitiationOutcome
-import tech.libeufin.nexus.TalerAmount
+import tech.libeufin.nexus.*
 import java.time.Instant
 import kotlin.test.assertEquals
 import kotlin.test.assertFalse
 import kotlin.test.assertTrue
 
+
+class OutgoingPaymentsTest {
+
+    /**
+     * Tests the insertion of outgoing payments, including
+     * the case where we reconcile with an initiated payment.
+     */
+    @Test
+    fun outgoingPaymentCreation() {
+        val db = prepDb(TalerConfig(NEXUS_CONFIG_SOURCE))
+        runBlocking {
+            // inserting without reconciling
+            assertEquals(
+                OutgoingPaymentOutcome.SUCCESS,
+                db.outgoingPaymentCreate(genOutPay("paid by nexus"))
+            )
+            // inserting trying to reconcile with a non-existing initiated 
payment.
+            assertEquals(
+                OutgoingPaymentOutcome.INITIATED_COUNTERPART_NOT_FOUND,
+                db.outgoingPaymentCreate(genOutPay("paid by nexus"), 5)
+            )
+            // initiating a payment to reconcile later.  Takes row ID == 1
+            assertEquals(
+                PaymentInitiationOutcome.SUCCESS,
+                db.initiatedPaymentCreate(genInitPay("waiting for 
reconciliation"))
+            )
+            // Creating an outgoing payment, reconciling it with the one above.
+            assertEquals(
+                OutgoingPaymentOutcome.SUCCESS,
+                db.outgoingPaymentCreate(genOutPay(), 1)
+            )
+        }
+    }
+}
+
 class IncomingPaymentsTest {
     // Tests the function that flags incoming payments as bounced.
     @Test
@@ -50,13 +82,15 @@ class IncomingPaymentsTest {
                 assertTrue(res.next())
                 assertEquals(0, res.getInt("how_many"))
             }
-            db.incomingPaymentCreate(genIncPay("singleton"))
+            assertTrue(db.incomingPaymentCreate(genIncPay("singleton")))
             // Asserting the table has one.
             db.runConn {
                 val res = it.execSQLQuery(countRows)
                 assertTrue(res.next())
                 assertEquals(1, res.getInt("how_many"))
             }
+            // Checking insertion of null (allowed) subjects.
+            assertTrue(db.incomingPaymentCreate(genIncPay()))
         }
     }
 }

-- 
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]