gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated: Implementing registration bonus


From: gnunet
Subject: [libeufin] branch master updated: Implementing registration bonus
Date: Fri, 22 Sep 2023 13:37:36 +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 25e30f22 Implementing registration bonus
25e30f22 is described below

commit 25e30f2266920b7a7f2f9605e2fa25c550d6cfe9
Author: MS <ms@taler.net>
AuthorDate: Fri Sep 22 13:36:52 2023 +0200

    Implementing registration bonus
---
 .../src/main/kotlin/tech/libeufin/bank/Database.kt | 26 ++++++++--
 .../tech/libeufin/bank/accountsMgmtHandlers.kt     | 42 +++++++++++++++--
 bank/src/main/kotlin/tech/libeufin/bank/helpers.kt | 55 ++++++++++++----------
 bank/src/test/kotlin/DatabaseTest.kt               | 22 ++++-----
 bank/src/test/kotlin/LibeuFinApiTest.kt            | 14 +++---
 bank/src/test/kotlin/TalerApiTest.kt               | 26 +++++-----
 6 files changed, 121 insertions(+), 64 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
index 9bf42dac..d5789396 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
@@ -291,8 +291,13 @@ class Database(private val dbConfig: String) {
     }
 
     // BANK ACCOUNTS
-    // Returns false on conflicts.
-    fun bankAccountCreate(bankAccount: BankAccount): Boolean {
+
+    /**
+     * Inserts a new bank account in the database, returning its
+     * row ID in the successful case.  If of unique constrain violation,
+     * it returns null and any other error will be thrown as 500.
+     */
+    fun bankAccountCreate(bankAccount: BankAccount): Long? {
         reconnect()
         if (bankAccount.balance != null)
             throw internalServerError(
@@ -307,7 +312,9 @@ class Database(private val dbConfig: String) {
               ,is_taler_exchange
               ,max_debt
               )
-            VALUES (?, ?, ?, ?, (?, ?)::taler_amount)
+            VALUES
+              (?, ?, ?, ?, (?, ?)::taler_amount)
+            RETURNING bank_account_id;
         """)
         stmt.setString(1, bankAccount.internalPaytoUri)
         stmt.setLong(2, bankAccount.owningCustomerId)
@@ -316,7 +323,18 @@ class Database(private val dbConfig: String) {
         stmt.setLong(5, bankAccount.maxDebt.value)
         stmt.setInt(6, bankAccount.maxDebt.frac)
         // using the default zero value for the balance.
-        return myExecute(stmt)
+        val res = try {
+            stmt.executeQuery()
+        } catch (e: SQLException) {
+            logger.error(e.message)
+            if (e.errorCode == 0) return null // unique constraint violation.
+            throw e // rethrow on other errors.
+        }
+        res.use {
+            if (!it.next())
+                throw internalServerError("SQL RETURNING gave no 
bank_account_id.")
+            return it.getLong("bank_account_id")
+        }
     }
 
     fun bankAccountSetMaxDebt(
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/accountsMgmtHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/accountsMgmtHandlers.kt
index 08e01c26..e1890b3f 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/accountsMgmtHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/accountsMgmtHandlers.kt
@@ -9,6 +9,7 @@ import net.taler.common.errorcodes.TalerErrorCode
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import tech.libeufin.util.CryptoUtil
+import tech.libeufin.util.getNowUs
 import tech.libeufin.util.maybeUriComponent
 
 private val logger: Logger = 
LoggerFactory.getLogger("tech.libeufin.bank.accountsMgmtHandlers")
@@ -95,7 +96,6 @@ fun Routing.accountsMgmtHandlers(db: Database) {
             if (this == null) throw internalServerError("Max debt not 
configured")
             parseTalerAmount(this)
         }
-        val bonus = db.configGet("registration_bonus")
         val newBankAccount = BankAccount(
             hasDebt = false,
             internalPaytoUri = req.internal_payto_uri ?: genIbanPaytoUri(),
@@ -104,8 +104,44 @@ fun Routing.accountsMgmtHandlers(db: Database) {
             isTalerExchange = req.is_taler_exchange,
             maxDebt = maxDebt
         )
-        if (!db.bankAccountCreate(newBankAccount))
-            throw internalServerError("Could not INSERT bank account despite 
all the checks.")
+        val newBankAccountId = db.bankAccountCreate(newBankAccount)
+            ?: throw internalServerError("Could not INSERT bank account 
despite all the checks.")
+
+        /**
+         * The new account got created, now optionally award the registration
+         * bonus to it.  The configuration gets either a Taler amount (of the
+         * bonus), or null if no bonus is meant to be awarded.
+         */
+        val bonusAmount = db.configGet("registration_bonus")
+        if (bonusAmount != null) {
+            // Double-checking that the currency is correct.
+            val internalCurrency = db.configGet("internal_currency")
+                ?: throw internalServerError("Bank own currency missing in the 
config")
+            val bonusAmountObj = parseTalerAmount2(bonusAmount, 
FracDigits.EIGHT)
+                ?: throw internalServerError("Bonus amount found invalid in 
the config.")
+            if (bonusAmountObj.currency != internalCurrency)
+                throw internalServerError("Bonus amount has the wrong 
currency: ${bonusAmountObj.currency}")
+            val adminCustomer = db.customerGetFromLogin("admin")
+                ?: throw internalServerError("Admin customer not found")
+            val adminBankAccount = 
db.bankAccountGetFromOwnerId(adminCustomer.expectRowId())
+                ?: throw internalServerError("Admin bank account not found")
+            val adminPaysBonus = BankInternalTransaction(
+                creditorAccountId = newBankAccountId,
+                debtorAccountId = adminBankAccount.expectRowId(),
+                amount = bonusAmountObj,
+                subject = "Registration bonus.",
+                transactionDate = getNowUs()
+            )
+            when(db.bankTransactionCreate(adminPaysBonus)) {
+                Database.BankTransactionResult.NO_CREDITOR ->
+                    throw internalServerError("Bonus impossible: creditor not 
found, despite its recent creation.")
+                Database.BankTransactionResult.NO_DEBTOR ->
+                    throw internalServerError("Bonus impossible: admin not 
found.")
+                Database.BankTransactionResult.CONFLICT ->
+                    throw internalServerError("Bonus impossible: admin has 
insufficient balance.")
+                Database.BankTransactionResult.SUCCESS -> {/* continue the 
execution */}
+            }
+        }
         call.respond(HttpStatusCode.Created)
         return@post
     }
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
index 199e5902..9db6f869 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
@@ -219,51 +219,33 @@ fun badRequest(
 // Generates a new Payto-URI with IBAN scheme.
 fun genIbanPaytoUri(): String = "payto://iban/SANDBOXX/${getIban()}"
 
-/**
- * This helper takes the serialized version of a Taler Amount
- * type and parses it into Libeufin's internal representation.
- * It returns a TalerAmount type, or throws a LibeufinBankException
- * it the input is invalid.  Such exception will be then caught by
- * Ktor, transformed into the appropriate HTTP error type, and finally
- * responded to the client.
- */
-fun parseTalerAmount(
+// Parses Taler amount, returning null if the input is invalid.
+fun parseTalerAmount2(
     amount: String,
-    fracDigits: FracDigits = FracDigits.EIGHT
-): TalerAmount {
+    fracDigits: FracDigits
+): TalerAmount? {
     val format = when (fracDigits) {
         FracDigits.TWO -> "^([A-Z]+):([0-9]+)(\\.[0-9][0-9]?)?$"
         FracDigits.EIGHT -> 
"^([A-Z]+):([0-9]+)(\\.[0-9][0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[0-9]?)?$"
     }
-    val match = Regex(format).find(amount) ?: throw LibeufinBankException(
-        httpStatus = HttpStatusCode.BadRequest,
-        talerError = TalerError(
-            code = TalerErrorCode.TALER_EC_BANK_BAD_FORMAT_AMOUNT.code,
-            hint = "Invalid amount: $amount"
-        ))
+    val match = Regex(format).find(amount) ?: return null
     val _value = match.destructured.component2()
     // Fraction is at most 8 digits, so it's always < than MAX_INT.
     val fraction: Int = match.destructured.component3().run {
         var frac = 0
         var power = FRACTION_BASE
         if (this.isNotEmpty())
-            // Skips the dot and processes the fractional chars.
+        // Skips the dot and processes the fractional chars.
             this.substring(1).forEach { chr ->
                 power /= 10
                 frac += power * chr.digitToInt()
-        }
+            }
         return@run frac
     }
     val value: Long = try {
         _value.toLong()
     } catch (e: NumberFormatException) {
-        throw LibeufinBankException(
-            httpStatus = HttpStatusCode.BadRequest,
-            talerError = TalerError(
-                code = TalerErrorCode.TALER_EC_BANK_BAD_FORMAT_AMOUNT.code,
-                hint = "Invalid amount: ${amount}, could not extract the value 
part."
-            )
-        )
+        return null
     }
     return TalerAmount(
         value = value,
@@ -271,6 +253,27 @@ fun parseTalerAmount(
         currency = match.destructured.component1()
     )
 }
+/**
+ * This helper takes the serialized version of a Taler Amount
+ * type and parses it into Libeufin's internal representation.
+ * It returns a TalerAmount type, or throws a LibeufinBankException
+ * it the input is invalid.  Such exception will be then caught by
+ * Ktor, transformed into the appropriate HTTP error type, and finally
+ * responded to the client.
+ */
+fun parseTalerAmount(
+    amount: String,
+    fracDigits: FracDigits = FracDigits.EIGHT
+): TalerAmount {
+    val maybeAmount = parseTalerAmount2(amount, fracDigits)
+        ?: throw LibeufinBankException(
+            httpStatus = HttpStatusCode.BadRequest,
+            talerError = TalerError(
+                code = TalerErrorCode.TALER_EC_BANK_BAD_FORMAT_AMOUNT.code,
+                hint = "Invalid amount: $amount"
+            ))
+    return maybeAmount
+}
 
 private fun normalizeAmount(amt: TalerAmount): TalerAmount {
     if (amt.frac > FRACTION_BASE) {
diff --git a/bank/src/test/kotlin/DatabaseTest.kt 
b/bank/src/test/kotlin/DatabaseTest.kt
index 9ff10cc2..e1b77937 100644
--- a/bank/src/test/kotlin/DatabaseTest.kt
+++ b/bank/src/test/kotlin/DatabaseTest.kt
@@ -93,8 +93,8 @@ class DatabaseTest {
         assert(fooId != null)
         val barId = db.customerCreate(customerBar)
         assert(barId != null)
-        assert(db.bankAccountCreate(bankAccountFoo))
-        assert(db.bankAccountCreate(bankAccountBar))
+        assert(db.bankAccountCreate(bankAccountFoo) != null)
+        assert(db.bankAccountCreate(bankAccountBar) != null)
         val res = db.talerTransferCreate(
             req = exchangeReq,
             exchangeBankAccountId = 1L,
@@ -127,8 +127,8 @@ class DatabaseTest {
         assert(fooId != null)
         val barId = db.customerCreate(customerBar)
         assert(barId != null)
-        assert(db.bankAccountCreate(bankAccountFoo))
-        assert(db.bankAccountCreate(bankAccountBar))
+        assert(db.bankAccountCreate(bankAccountFoo) != null)
+        assert(db.bankAccountCreate(bankAccountBar) != null)
         var fooAccount = db.bankAccountGetFromOwnerId(fooId!!)
         assert(fooAccount?.hasDebt == false) // Foo has NO debit.
         val currency = "KUDOS"
@@ -224,8 +224,8 @@ class DatabaseTest {
         val currency = "KUDOS"
         assert(db.bankAccountGetFromOwnerId(1L) == null)
         assert(db.customerCreate(customerFoo) != null)
-        assert(db.bankAccountCreate(bankAccountFoo))
-        assert(!db.bankAccountCreate(bankAccountFoo)) // Triggers conflict.
+        assert(db.bankAccountCreate(bankAccountFoo) != null)
+        assert(db.bankAccountCreate(bankAccountFoo) == null) // Triggers 
conflict.
         
assert(db.bankAccountGetFromOwnerId(1L)?.balance?.equals(TalerAmount(0, 0, 
currency)) == true)
     }
 
@@ -235,9 +235,9 @@ class DatabaseTest {
         val uuid = UUID.randomUUID()
         val currency = "KUDOS"
         assert(db.customerCreate(customerFoo) != null)
-        assert(db.bankAccountCreate(bankAccountFoo))
+        assert(db.bankAccountCreate(bankAccountFoo) != null)
         assert(db.customerCreate(customerBar) != null) // plays the exchange.
-        assert(db.bankAccountCreate(bankAccountBar))
+        assert(db.bankAccountCreate(bankAccountBar) != null)
         // insert new.
         assert(db.talerWithdrawalCreate(
             uuid,
@@ -305,9 +305,9 @@ class DatabaseTest {
         )
         val fooId = db.customerCreate(customerFoo)
         assert(fooId != null)
-        assert(db.bankAccountCreate(bankAccountFoo))
+        assert(db.bankAccountCreate(bankAccountFoo) != null)
         assert(db.customerCreate(customerBar) != null)
-        assert(db.bankAccountCreate(bankAccountBar))
+        assert(db.bankAccountCreate(bankAccountBar) != null)
         assert(db.cashoutCreate(op))
         val fromDb = db.cashoutGetFromUuid(op.cashoutUuid)
         assert(fromDb?.subject == op.subject && fromDb.tanConfirmationTime == 
null)
@@ -337,4 +337,4 @@ class DatabaseTest {
         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
+}
diff --git a/bank/src/test/kotlin/LibeuFinApiTest.kt 
b/bank/src/test/kotlin/LibeuFinApiTest.kt
index 13ffb835..9aecc484 100644
--- a/bank/src/test/kotlin/LibeuFinApiTest.kt
+++ b/bank/src/test/kotlin/LibeuFinApiTest.kt
@@ -60,9 +60,9 @@ class LibeuFinApiTest {
     fun testHistory() {
         val db = initDb()
         val fooId = db.customerCreate(customerFoo); assert(fooId != null)
-        assert(db.bankAccountCreate(genBankAccount(fooId!!)))
+        assert(db.bankAccountCreate(genBankAccount(fooId!!)) != null)
         val barId = db.customerCreate(customerBar); assert(barId != null)
-        assert(db.bankAccountCreate(genBankAccount(barId!!)))
+        assert(db.bankAccountCreate(genBankAccount(barId!!)) != null)
         for (i in 1..10) { db.bankTransactionCreate(genTx("test-$i")) }
         testApplication {
             application {
@@ -91,10 +91,10 @@ class LibeuFinApiTest {
         val db = initDb()
         // foo account
         val fooId = db.customerCreate(customerFoo); assert(fooId != null)
-        assert(db.bankAccountCreate(genBankAccount(fooId!!)))
+        assert(db.bankAccountCreate(genBankAccount(fooId!!)) != null)
         // bar account
         val barId = db.customerCreate(customerBar); assert(barId != null)
-        assert(db.bankAccountCreate(genBankAccount(barId!!)))
+        assert(db.bankAccountCreate(genBankAccount(barId!!)) != null)
         // accounts exist, now create one transaction.
         testApplication {
             application {
@@ -185,7 +185,7 @@ class LibeuFinApiTest {
                 maxDebt = TalerAmount(100, 0, "KUDOS"),
                 owningCustomerId = customerRowId!!
             )
-        ))
+        ) != null)
         testApplication {
             application {
                 corebankWebApp(db)
@@ -208,7 +208,7 @@ class LibeuFinApiTest {
                 internalPaytoUri = "payto://iban/SANDBOXX/ADMIN-IBAN",
                 maxDebt = TalerAmount(100, 0, "KUDOS"),
                 owningCustomerId = adminRowId!!
-            )))
+            )) != null)
             client.get("/accounts/foo") {
                 expectSuccess = true
                 basicAuth("admin", "admin")
@@ -290,4 +290,4 @@ class LibeuFinApiTest {
             assert(resp.status == HttpStatusCode.Created)
         }
     }
-}
\ No newline at end of file
+}
diff --git a/bank/src/test/kotlin/TalerApiTest.kt 
b/bank/src/test/kotlin/TalerApiTest.kt
index 2f6f3e0a..7cdcd0eb 100644
--- a/bank/src/test/kotlin/TalerApiTest.kt
+++ b/bank/src/test/kotlin/TalerApiTest.kt
@@ -49,9 +49,9 @@ class TalerApiTest {
         val db = initDb()
         // Creating the exchange and merchant accounts first.
         assert(db.customerCreate(customerFoo) != null)
-        assert(db.bankAccountCreate(bankAccountFoo))
+        assert(db.bankAccountCreate(bankAccountFoo) != null)
         assert(db.customerCreate(customerBar) != null)
-        assert(db.bankAccountCreate(bankAccountBar))
+        assert(db.bankAccountCreate(bankAccountBar) != null)
         // Give the exchange reasonable debt allowance:
         assert(db.bankAccountSetMaxDebt(
             1L,
@@ -126,9 +126,9 @@ class TalerApiTest {
     fun historyIncoming() {
         val db = initDb()
         assert(db.customerCreate(customerFoo) != null)
-        assert(db.bankAccountCreate(bankAccountFoo))
+        assert(db.bankAccountCreate(bankAccountFoo) != null)
         assert(db.customerCreate(customerBar) != null)
-        assert(db.bankAccountCreate(bankAccountBar))
+        assert(db.bankAccountCreate(bankAccountBar) != null)
         // Give Foo reasonable debt allowance:
         assert(db.bankAccountSetMaxDebt(
             1L,
@@ -161,9 +161,9 @@ class TalerApiTest {
     fun addIncoming() {
         val db = initDb()
         assert(db.customerCreate(customerFoo) != null)
-        assert(db.bankAccountCreate(bankAccountFoo))
+        assert(db.bankAccountCreate(bankAccountFoo) != null)
         assert(db.customerCreate(customerBar) != null)
-        assert(db.bankAccountCreate(bankAccountBar))
+        assert(db.bankAccountCreate(bankAccountBar) != null)
         // Give Bar reasonable debt allowance:
         assert(db.bankAccountSetMaxDebt(
             2L,
@@ -192,7 +192,7 @@ class TalerApiTest {
         val db = initDb()
         val uuid = UUID.randomUUID()
         assert(db.customerCreate(customerFoo) != null)
-        assert(db.bankAccountCreate(bankAccountFoo))
+        assert(db.bankAccountCreate(bankAccountFoo) != null)
         db.configSet(
             "suggested_exchange",
             "payto://suggested-exchange"
@@ -224,7 +224,7 @@ class TalerApiTest {
         val db = initDb()
         val uuid = UUID.randomUUID()
         assert(db.customerCreate(customerFoo) != null)
-        assert(db.bankAccountCreate(bankAccountFoo))
+        assert(db.bankAccountCreate(bankAccountFoo) != null)
         db.configSet(
             "suggested_exchange",
             "payto://suggested-exchange"
@@ -251,7 +251,7 @@ class TalerApiTest {
         val db = initDb()
         val uuid = UUID.randomUUID()
         assert(db.customerCreate(customerFoo) != null)
-        assert(db.bankAccountCreate(bankAccountFoo))
+        assert(db.bankAccountCreate(bankAccountFoo) != null)
         // insert new.
         assert(db.talerWithdrawalCreate(
             opUUID = uuid,
@@ -277,7 +277,7 @@ class TalerApiTest {
     fun withdrawalCreation() {
         val db = initDb()
         assert(db.customerCreate(customerFoo) != null)
-        assert(db.bankAccountCreate(bankAccountFoo))
+        assert(db.bankAccountCreate(bankAccountFoo) != null)
         testApplication {
             application {
                 corebankWebApp(db)
@@ -305,9 +305,9 @@ class TalerApiTest {
         val db = initDb()
         // Creating Foo as the wallet owner and Bar as the exchange.
         assert(db.customerCreate(customerFoo) != null)
-        assert(db.bankAccountCreate(bankAccountFoo))
+        assert(db.bankAccountCreate(bankAccountFoo) != null)
         assert(db.customerCreate(customerBar) != null)
-        assert(db.bankAccountCreate(bankAccountBar))
+        assert(db.bankAccountCreate(bankAccountBar) != null)
 
         // Artificially making a withdrawal operation for Foo.
         val uuid = UUID.randomUUID()
@@ -363,4 +363,4 @@ class TalerApiTest {
         )
         assert(withPort == 
"taler://withdraw/www.example.com:9876/taler-integration/my-id")
     }
-}
\ 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]