gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated (91e6a439 -> d6f75f52)


From: gnunet
Subject: [libeufin] branch master updated (91e6a439 -> d6f75f52)
Date: Sat, 02 Sep 2023 11:01:23 +0200

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

ms pushed a change to branch master
in repository libeufin.

    from 91e6a439 Bank DB: getting the history query to return.
     new 6da1ee06 bank DB: creating cash-out operations.
     new d6f75f52 bank DB: conditional cash-out deletion.

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 database-versioning/new/procedures.sql             |  20 ++
 .../main/kotlin/tech/libeufin/sandbox/Database.kt  | 211 ++++++++++++++++++++-
 sandbox/src/test/kotlin/DatabaseTest.kt            |  91 ++++++---
 3 files changed, 291 insertions(+), 31 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 dde9d47b..243efe2c 100644
--- a/sandbox/src/main/kotlin/tech/libeufin/sandbox/Database.kt
+++ b/sandbox/src/main/kotlin/tech/libeufin/sandbox/Database.kt
@@ -37,7 +37,11 @@ data class BankAccount(
 )
 
 enum class TransactionDirection {
-    Credit, Debit
+    credit, debit
+}
+
+enum class TanChannel {
+    sms, email, file
 }
 
 data class BankInternalTransaction(
@@ -79,6 +83,25 @@ data class TalerWithdrawalOperation(
     val walletBankAccount: Long
 )
 
+data class Cashout(
+    val cashoutUuid: UUID,
+    val localTransaction: Long? = null,
+    val amountDebit: TalerAmount,
+    val amountCredit: TalerAmount,
+    val buyAtRatio: Int,
+    val buyInFee: TalerAmount,
+    val sellAtRatio: Int,
+    val sellOutFee: TalerAmount,
+    val subject: String,
+    val creationTime: Long,
+    val tanConfirmationTime: Long? = null,
+    val tanChannel: TanChannel,
+    val tanCode: String,
+    val bankAccount: Long,
+    val cashoutAddress: String,
+    val cashoutCurrency: String
+)
+
 class Database(private val dbConfig: String) {
     private var dbConn: PgConnection? = null
     private var dbCtr: Int = 0
@@ -119,7 +142,8 @@ class Database(private val dbConfig: String) {
             stmt.execute()
         } catch (e: SQLException) {
             logger.error(e.message)
-            if (e.errorCode == 0) return false // unique key violation.
+            // NOTE: it seems that _every_ error gets the 0 code.
+            if (e.errorCode == 0) return false
             // rethrowing, not to hide other types of errors.
             throw e
         }
@@ -306,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
         }
     }
@@ -367,8 +400,8 @@ class Database(private val dbConfig: String) {
                         endToEndId = it.getString("end_to_end_id"),
                         direction = it.getString("direction").run {
                             when(this) {
-                                "credit" -> TransactionDirection.Credit
-                                "debit" -> TransactionDirection.Debit
+                                "credit" -> TransactionDirection.credit
+                                "debit" -> TransactionDirection.debit
                                 else -> throw internalServerError("Wrong 
direction in transaction: $this")
                             }
                         },
@@ -466,6 +499,170 @@ class Database(private val dbConfig: String) {
         stmt.setObject(1, opUUID)
         return myExecute(stmt)
     }
+
+    fun cashoutCreate(op: Cashout): Boolean {
+        reconnect()
+        val stmt = prepare("""
+            INSERT INTO cashout_operations (
+              cashout_uuid
+              ,amount_debit 
+              ,amount_credit 
+              ,buy_at_ratio
+              ,buy_in_fee 
+              ,sell_at_ratio
+              ,sell_out_fee
+              ,subject
+              ,creation_time
+              ,tan_channel
+              ,tan_code
+              ,bank_account
+              ,cashout_address
+              ,cashout_currency
+           )
+            VALUES (
+             ?
+             ,(?,?)::taler_amount
+             ,(?,?)::taler_amount
+             ,?
+             ,(?,?)::taler_amount
+             ,?
+             ,(?,?)::taler_amount
+             ,?
+             ,?
+             ,?::tan_enum
+             ,?
+             ,?
+             ,?
+             ,?
+           );
+        """)
+        stmt.setObject(1, op.cashoutUuid)
+        stmt.setLong(2, op.amountDebit.value)
+        stmt.setInt(3, op.amountDebit.frac)
+        stmt.setLong(4, op.amountCredit.value)
+        stmt.setInt(5, op.amountCredit.frac)
+        stmt.setInt(6, op.buyAtRatio)
+        stmt.setLong(7, op.buyInFee.value)
+        stmt.setInt(8, op.buyInFee.frac)
+        stmt.setInt(9, op.sellAtRatio)
+        stmt.setLong(10, op.sellOutFee.value)
+        stmt.setInt(11, op.sellOutFee.frac)
+        stmt.setString(12, op.subject)
+        stmt.setLong(13, op.creationTime)
+        stmt.setString(14, op.tanChannel.name)
+        stmt.setString(15, op.tanCode)
+        stmt.setLong(16, op.bankAccount)
+        stmt.setString(17, op.cashoutAddress)
+        stmt.setString(18, op.cashoutCurrency)
+        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 2ca6aeb3..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,
@@ -215,4 +208,54 @@ class DatabaseTest {
         )
         assert(res.isEmpty())
     }
+    @Test
+    fun cashoutTest() {
+        val db = initDb()
+        val op = Cashout(
+            cashoutUuid = UUID.randomUUID(),
+            amountDebit = TalerAmount(1, 0),
+            amountCredit = TalerAmount(2, 0),
+            bankAccount = 1L,
+            buyAtRatio = 3,
+            buyInFee = TalerAmount(0, 22),
+            sellAtRatio = 2,
+            sellOutFee = TalerAmount(0, 44),
+            cashoutAddress = "IBAN",
+            cashoutCurrency = "KUDOS",
+            creationTime = 3L,
+            subject = "31st",
+            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]