gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] 02/02: bank DB: conditional cash-out deletion.


From: gnunet
Subject: [libeufin] 02/02: bank DB: conditional cash-out deletion.
Date: Sat, 02 Sep 2023 11:01:25 +0200

This is an automated email from the git hooks/post-receive script.

ms pushed a commit to branch master
in repository libeufin.

commit d6f75f52ed1c9a5508effeef669c513509e88536
Author: MS <ms@taler.net>
AuthorDate: Sat Sep 2 11:00:52 2023 +0200

    bank DB: conditional cash-out deletion.
---
 database-versioning/new/procedures.sql             |  20 ++++
 .../main/kotlin/tech/libeufin/sandbox/Database.kt  | 123 ++++++++++++++++++++-
 sandbox/src/test/kotlin/DatabaseTest.kt            |  72 +++++++-----
 3 files changed, 186 insertions(+), 29 deletions(-)

diff --git a/database-versioning/new/procedures.sql 
b/database-versioning/new/procedures.sql
index 17fbde32..8fd3c8c1 100644
--- a/database-versioning/new/procedures.sql
+++ b/database-versioning/new/procedures.sql
@@ -330,4 +330,24 @@ SET
 WHERE bank_account_id=in_creditor_account_id;
 RETURN;
 END $$;
+
+CREATE OR REPLACE FUNCTION cashout_delete(
+  IN in_cashout_uuid UUID,
+  OUT out_already_confirmed BOOLEAN
+)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+  PERFORM
+    FROM cashout_operations
+    WHERE cashout_uuid=in_cashout_uuid AND tan_confirmation_time IS NOT NULL;
+  IF FOUND
+  THEN
+    out_already_confirmed=TRUE;
+    RETURN;
+  END IF;
+  out_already_confirmed=FALSE;
+  DELETE FROM cashout_operations WHERE cashout_uuid=in_cashout_uuid;
+  RETURN;
+END $$;
 COMMIT;
diff --git a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Database.kt 
b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Database.kt
index ec6a3196..243efe2c 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Database.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Database.kt
@@ -330,9 +330,18 @@ class Database(private val dbConfig: String) {
         val rs = stmt.executeQuery()
         rs.use {
             if (!rs.next()) throw internalServerError("Bank transaction didn't 
properly return")
-            if (rs.getBoolean("out_nx_debtor")) return 
BankTransactionResult.NO_DEBTOR
-            if (rs.getBoolean("out_nx_creditor")) return 
BankTransactionResult.NO_CREDITOR
-            if (rs.getBoolean("out_balance_insufficient")) return 
BankTransactionResult.CONFLICT
+            if (rs.getBoolean("out_nx_debtor")) {
+                logger.error("No debtor account found")
+                return BankTransactionResult.NO_DEBTOR
+            }
+            if (rs.getBoolean("out_nx_creditor")) {
+                logger.error("No creditor account found")
+                return BankTransactionResult.NO_CREDITOR
+            }
+            if (rs.getBoolean("out_balance_insufficient")) {
+                logger.error("Balance insufficient")
+                return BankTransactionResult.CONFLICT
+            }
             return BankTransactionResult.SUCCESS
         }
     }
@@ -509,7 +518,7 @@ class Database(private val dbConfig: String) {
               ,bank_account
               ,cashout_address
               ,cashout_currency
-               )
+           )
             VALUES (
              ?
              ,(?,?)::taler_amount
@@ -548,6 +557,112 @@ class Database(private val dbConfig: String) {
         return myExecute(stmt)
     }
 
+    fun cashoutConfirm(
+        opUuid: UUID,
+        tanConfirmationTimestamp: Long,
+        bankTransaction: Long // regional payment backing the operation
+    ): Boolean {
+        reconnect()
+        val stmt = prepare("""
+            UPDATE cashout_operations
+              SET tan_confirmation_time = ?, local_transaction = ?
+              WHERE cashout_uuid=?;
+        """)
+        stmt.setLong(1, tanConfirmationTimestamp)
+        stmt.setLong(2, bankTransaction)
+        stmt.setObject(3, opUuid)
+        return myExecute(stmt)
+    }
+    // used by /abort
+    enum class CashoutDeleteResult {
+        SUCCESS,
+        CONFLICT_ALREADY_CONFIRMED
+    }
+    fun cashoutDelete(opUuid: UUID): CashoutDeleteResult {
+        val stmt = prepare("""
+           SELECT out_already_confirmed
+             FROM cashout_delete(?)
+        """)
+        stmt.setObject(1, opUuid)
+        stmt.executeQuery().use {
+            if (!it.next()) {
+                throw internalServerError("Cashout deletion gave no result")
+            }
+            if (it.getBoolean("out_already_confirmed")) return 
CashoutDeleteResult.CONFLICT_ALREADY_CONFIRMED
+            return CashoutDeleteResult.SUCCESS
+        }
+    }
+    fun cashoutGetFromUuid(opUuid: UUID): Cashout? {
+        val stmt = prepare("""
+           SELECT
+             (amount_debit).val as amount_debit_val
+             ,(amount_debit).frac as amount_debit_frac
+             ,(amount_credit).val as amount_credit_val
+             ,(amount_credit).frac as amount_credit_frac
+             ,buy_at_ratio
+             ,(buy_in_fee).val as buy_in_fee_val
+             ,(buy_in_fee).frac as buy_in_fee_frac
+             ,sell_at_ratio
+             ,(sell_out_fee).val as sell_out_fee_val
+             ,(sell_out_fee).frac as sell_out_fee_frac
+             ,subject
+             ,creation_time
+             ,tan_channel
+             ,tan_code
+             ,bank_account
+             ,cashout_address
+             ,cashout_currency
+            ,tan_confirmation_time
+            ,local_transaction
+             FROM cashout_operations
+             WHERE cashout_uuid=?;
+        """)
+        stmt.setObject(1, opUuid)
+        stmt.executeQuery().use {
+            if (!it.next()) return null
+            return Cashout(
+                amountDebit = TalerAmount(
+                    value = it.getLong("amount_debit_val"),
+                    frac = it.getInt("amount_debit_frac")
+                ),
+                amountCredit = TalerAmount(
+                    value = it.getLong("amount_credit_val"),
+                    frac = it.getInt("amount_credit_frac")
+                ),
+                bankAccount = it.getLong("bank_account"),
+                buyAtRatio = it.getInt("buy_at_ratio"),
+                buyInFee = TalerAmount(
+                    value = it.getLong("buy_in_fee_val"),
+                    frac = it.getInt("buy_in_fee_frac")
+                ),
+                cashoutAddress = it.getString("cashout_address"),
+                cashoutCurrency = it.getString("cashout_currency"),
+                cashoutUuid = opUuid,
+                creationTime = it.getLong("creation_time"),
+                sellAtRatio = it.getInt("sell_at_ratio"),
+                sellOutFee = TalerAmount(
+                    value = it.getLong("sell_out_fee_val"),
+                    frac = it.getInt("sell_out_fee_frac")
+                ),
+                subject = it.getString("subject"),
+                tanChannel = it.getString("tan_channel").run {
+                    when(this) {
+                        "sms" -> TanChannel.sms
+                        "email" -> TanChannel.email
+                        "file" -> TanChannel.file
+                        else -> throw internalServerError("TAN channel $this 
unsupported")
+                    }
+                },
+                tanCode = it.getString("tan_code"),
+                localTransaction = it.getLong("local_transaction"),
+                tanConfirmationTime = it.getLong("tan_confirmation_time").run {
+                    if (this == 0L) return@run null
+                    return@run this
+                }
+            )
+        }
+    }
+
     // NOTE: EBICS not needed for BFH and NB.
 
 }
diff --git a/sandbox/src/test/kotlin/DatabaseTest.kt 
b/sandbox/src/test/kotlin/DatabaseTest.kt
index cbe59d08..7716cd26 100644
--- a/sandbox/src/test/kotlin/DatabaseTest.kt
+++ b/sandbox/src/test/kotlin/DatabaseTest.kt
@@ -50,28 +50,27 @@ class DatabaseTest {
             throwIfFails = true
         )
         val db = Database("jdbc:postgresql:///libeufincheck")
-        // Need accounts first.
-        db.customerCreate(customerFoo)
-        db.customerCreate(customerBar)
-        db.bankAccountCreate(bankAccountFoo)
-        db.bankAccountCreate(bankAccountBar)
-        db.bankAccountSetMaxDebt(
-            "foo",
-            TalerAmount(100, 0)
-        )
-        db.bankAccountSetMaxDebt(
-            "bar",
-            TalerAmount(50, 0)
-        )
         return db
     }
 
     @Test
     fun bankTransactionsTest() {
         val db = initDb()
+        assert(db.customerCreate(customerFoo))
+        assert(db.customerCreate(customerBar))
+        assert(db.bankAccountCreate(bankAccountFoo))
+        assert(db.bankAccountCreate(bankAccountBar))
         var fooAccount = db.bankAccountGetFromLabel("foo")
         assert(fooAccount?.hasDebt == false) // Foo has NO debit.
         // Preparing the payment data.
+        db.bankAccountSetMaxDebt(
+            "foo",
+            TalerAmount(100, 0)
+        )
+        db.bankAccountSetMaxDebt(
+            "bar",
+            TalerAmount(50, 0)
+        )
         val fooPaysBar = BankInternalTransaction(
             creditorAccountId = 2,
             debtorAccountId = 1,
@@ -163,17 +162,9 @@ class DatabaseTest {
     fun bankAccountTest() {
         val db = initDb()
         assert(db.bankAccountGetFromLabel("foo") == null)
-        val bankAccount = BankAccount(
-            iban = "not used",
-            bic = "not used",
-            bankAccountLabel = "foo",
-            lastNexusFetchRowId = 1L,
-            owningCustomerId = 1L,
-            hasDebt = false
-        )
-        db.customerCreate(customerFoo) // Satisfies the REFERENCE
-        assert(db.bankAccountCreate(bankAccount))
-        assert(!db.bankAccountCreate(bankAccount)) // Triggers conflict.
+        assert(db.customerCreate(customerFoo))
+        assert(db.bankAccountCreate(bankAccountFoo))
+        assert(!db.bankAccountCreate(bankAccountFoo)) // Triggers conflict.
         assert(db.bankAccountGetFromLabel("foo")?.bankAccountLabel == "foo")
         
assert(db.bankAccountGetFromLabel("foo")?.balance?.equals(TalerAmount(0, 0)) == 
true)
     }
@@ -182,6 +173,8 @@ class DatabaseTest {
     fun withdrawalTest() {
         val db = initDb()
         val uuid = UUID.randomUUID()
+        assert(db.customerCreate(customerFoo))
+        assert(db.bankAccountCreate(bankAccountFoo))
         // insert new.
         assert(db.talerWithdrawalCreate(
             uuid,
@@ -234,6 +227,35 @@ class DatabaseTest {
             tanChannel = TanChannel.sms,
             tanCode = "secret",
         )
+        assert(db.customerCreate(customerFoo))
+        assert(db.bankAccountCreate(bankAccountFoo))
+        assert(db.customerCreate(customerBar))
+        assert(db.bankAccountCreate(bankAccountBar))
         assert(db.cashoutCreate(op))
-    }
+        val fromDb = db.cashoutGetFromUuid(op.cashoutUuid)
+        assert(fromDb?.subject == op.subject && fromDb.tanConfirmationTime == 
null)
+        assert(db.cashoutDelete(op.cashoutUuid) == 
Database.CashoutDeleteResult.SUCCESS)
+        assert(db.cashoutCreate(op))
+        db.bankAccountSetMaxDebt(
+            "foo",
+            TalerAmount(100, 0)
+        )
+        assert(db.bankTransactionCreate(BankInternalTransaction(
+            creditorAccountId = 2,
+            debtorAccountId = 1,
+            subject = "backing the cash-out",
+            amount = TalerAmount(10, 0),
+            accountServicerReference = "acct-svcr-ref",
+            endToEndId = "end-to-end-id",
+            paymentInformationId = "pmtinfid",
+            transactionDate = 100000L
+        )) == Database.BankTransactionResult.SUCCESS)
+        // Confirming the cash-out
+        assert(db.cashoutConfirm(op.cashoutUuid, 1L, 1L))
+        // Checking the confirmation took place.
+        assert(db.cashoutGetFromUuid(op.cashoutUuid)?.tanConfirmationTime != 
null)
+        // Deleting the operation.
+        assert(db.cashoutDelete(op.cashoutUuid) == 
Database.CashoutDeleteResult.CONFLICT_ALREADY_CONFIRMED)
+        assert(db.cashoutGetFromUuid(op.cashoutUuid) != null) // previous 
didn't delete.
+     }
 }
\ No newline at end of file

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