gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated: Starting the Taler API for the SPA.


From: gnunet
Subject: [libeufin] branch master updated: Starting the Taler API for the SPA.
Date: Tue, 19 Sep 2023 13:57:39 +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 26097f20 Starting the Taler API for the SPA.
26097f20 is described below

commit 26097f20f8003b981a6d231fcd91592e818323d0
Author: MS <ms@taler.net>
AuthorDate: Tue Sep 19 13:56:46 2023 +0200

    Starting the Taler API for the SPA.
    
    Introducing helpers to check if a balance is enough
    before initiating the withdrawal.
---
 .../src/main/kotlin/tech/libeufin/bank/Database.kt |  3 +-
 bank/src/main/kotlin/tech/libeufin/bank/Main.kt    |  3 +-
 bank/src/main/kotlin/tech/libeufin/bank/helpers.kt | 80 +++++++++++++++++++++-
 .../kotlin/tech/libeufin/bank/talerWebHandlers.kt  | 55 +++++++++++++++
 .../kotlin/tech/libeufin/bank/tokenHandlers.kt     | 19 +++++
 bank/src/main/kotlin/tech/libeufin/bank/types.kt   | 12 +++-
 bank/src/test/kotlin/AmountTest.kt                 | 49 +++++++++++++
 7 files changed, 216 insertions(+), 5 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
index 34689a47..8b45e146 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
@@ -30,7 +30,8 @@ import kotlin.math.abs
 private const val DB_CTR_LIMIT = 1000000
 
 
-fun Customer.expectRowId(): Long = this.dbRowId ?: throw 
internalServerError("Cutsomer '$login' had no DB row ID")
+fun Customer.expectRowId(): Long = this.dbRowId ?: throw 
internalServerError("Cutsomer '$login' had no DB row ID.")
+fun BankAccount.expectBalance(): TalerAmount = this.balance ?: throw 
internalServerError("Bank account '${this.internalPaytoUri}' lacks balance.")
 
 
 class Database(private val dbConfig: String) {
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
index 33333597..cf5b48a9 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -50,6 +50,7 @@ val logger: Logger = 
LoggerFactory.getLogger("tech.libeufin.bank")
 val db = Database(System.getProperty("BANK_DB_CONNECTION_STRING"))
 const val GENERIC_UNDEFINED = -1 // Filler for ECs that don't exist yet.
 val TOKEN_DEFAULT_DURATION_US = Duration.ofDays(1L).seconds * 1000000
+const val FRACTION_BASE = 100000000
 
 
 /**
@@ -224,7 +225,7 @@ val webApp: Application.() -> Unit = {
         this.accountsMgmtHandlers()
         this.tokenHandlers()
         this.transactionsHandlers()
-        // this.talerHandlers()
+        this.talerWebHandlers()
         // this.walletIntegrationHandlers()
     }
 }
\ No newline at end of file
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
index 258c1f96..1431ff4f 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
@@ -230,7 +230,7 @@ fun parseTalerAmount(
     // 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 = 100000000
+        var power = FRACTION_BASE
         if (this.isNotEmpty())
             // Skips the dot and processes the fractional chars.
             this.substring(1).forEach { chr ->
@@ -257,4 +257,82 @@ fun parseTalerAmount(
     )
 }
 
+private fun normalizeAmount(amt: TalerAmount): TalerAmount {
+    if (amt.frac > FRACTION_BASE) {
+        val normalValue = amt.value + (amt.frac / FRACTION_BASE)
+        val normalFrac = amt.frac % FRACTION_BASE
+        return TalerAmount(
+            value = normalValue,
+            frac = normalFrac,
+            maybeCurrency = amt.currency
+        )
+    }
+    return amt
+}
+
+
+// Adds two amounts and returns the normalized version.
+private fun amountAdd(first: TalerAmount, second: TalerAmount): TalerAmount {
+    if (first.currency != second.currency)
+        throw badRequest(
+            "Currency mismatch, balance '${first.currency}', price 
'${second.currency}'",
+            TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH
+        )
+    val valueAdd = first.value + second.value
+    if (valueAdd < first.value)
+        throw badRequest("Amount value overflowed")
+    val fracAdd = first.frac + second.frac
+    if (fracAdd < first.frac)
+        throw badRequest("Amount fraction overflowed")
+    return normalizeAmount(TalerAmount(
+        value = valueAdd,
+        frac = fracAdd,
+        maybeCurrency = first.currency
+    ))
+}
+
+/**
+ * Checks whether the balance could cover the due amount.  Returns true
+ * when it does, false otherwise.  Note: this function is only a checker,
+ * meaning that no actual business state should change after it runs.
+ * The place where business states change is in the SQL that's loaded in
+ * the database.
+ */
+fun isBalanceEnough(
+    balance: TalerAmount,
+    due: TalerAmount,
+    maxDebt: TalerAmount,
+    hasBalanceDebt: Boolean
+): Boolean {
+    val normalMaxDebt = normalizeAmount(maxDebt) // Very unlikely to be needed.
+    if (hasBalanceDebt) {
+        val chargedBalance = amountAdd(balance, due)
+        if (chargedBalance.value > normalMaxDebt.value) return false // max 
debt surpassed
+        if (
+            (chargedBalance.value == normalMaxDebt.value) &&
+            (chargedBalance.frac > maxDebt.frac)
+            )
+            return false
+        return true
+    }
+    /**
+     * Balance doesn't have debt, but it MIGHT get one.  The following
+     * block calculates how much debt the balance would get, should a
+     * subtraction of 'due' occur.
+     */
+    if (balance.currency != due.currency)
+        throw badRequest(
+            "Currency mismatch, balance '${balance.currency}', due 
'${due.currency}'",
+            TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH
+        )
+    val valueDiff = if (balance.value < due.value) due.value - balance.value 
else 0L
+    val fracDiff = if (balance.frac < due.frac) due.frac - balance.frac else 0
+    // Getting the normalized version of such diff.
+    val normalDiff = normalizeAmount(TalerAmount(valueDiff, fracDiff, 
balance.currency))
+    // Failing if the normalized diff surpasses the max debt.
+    if (normalDiff.value > normalMaxDebt.value) return false
+    if ((normalDiff.value == normalMaxDebt.value) &&
+        (normalDiff.frac > normalMaxDebt.frac)) return false
+    return true
+}
 fun getBankCurrency(): String = db.configGet("internal_currency") ?: throw 
internalServerError("Bank lacks currency")
\ No newline at end of file
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/talerWebHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/talerWebHandlers.kt
new file mode 100644
index 00000000..6bb27374
--- /dev/null
+++ b/bank/src/main/kotlin/tech/libeufin/bank/talerWebHandlers.kt
@@ -0,0 +1,55 @@
+/*
+ * This file is part of LibEuFin.
+ * Copyright (C) 2019 Stanisci and Dold.
+
+ * LibEuFin is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3, or
+ * (at your option) any later version.
+
+ * LibEuFin is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
+ * Public License for more details.
+
+ * You should have received a copy of the GNU Affero General Public
+ * License along with LibEuFin; see the file COPYING.  If not, see
+ * <http://www.gnu.org/licenses/>
+ */
+
+/* This file contains all the Taler handlers that do NOT
+ * communicate with wallets, therefore any handler that serves
+ * to SPAs or CLI HTTP clients.
+ */
+
+package tech.libeufin.bank
+
+import io.ktor.server.application.*
+import io.ktor.server.request.*
+import io.ktor.server.routing.*
+
+fun Routing.talerWebHandlers() {
+    post("/accounts/{USERNAME}/withdrawals") {
+        val c = call.myAuth(TokenScope.readwrite) ?: throw unauthorized()
+        // Admin not allowed to withdraw in the name of customers:
+        val accountName = call.expectUriComponent("USERNAME")
+        if (c.login != accountName)
+            throw unauthorized("User ${c.login} not allowed to withdraw for 
account '${accountName}'")
+        val req = call.receive<BankAccountCreateWithdrawalRequest>()
+        // Checking that the user has enough funds.
+        val b = db.bankAccountGetFromOwnerId(c.expectRowId())
+            ?: throw internalServerError("Customer '${c.login}' lacks bank 
account.")
+
+        throw NotImplementedError()
+    }
+    get("/accounts/{USERNAME}/withdrawals/{W_ID}") {
+        throw NotImplementedError()
+    }
+    post("/accounts/{USERNAME}/withdrawals/abort") {
+        throw NotImplementedError()
+    }
+    post("/accounts/{USERNAME}/withdrawals/confirm") {
+        throw NotImplementedError()
+    }
+}
+
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/tokenHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/tokenHandlers.kt
index 0b665069..803c0f36 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/tokenHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/tokenHandlers.kt
@@ -1,3 +1,22 @@
+/*
+ * This file is part of LibEuFin.
+ * Copyright (C) 2019 Stanisci and Dold.
+
+ * LibEuFin is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation; either version 3, or
+ * (at your option) any later version.
+
+ * LibEuFin is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
+ * Public License for more details.
+
+ * You should have received a copy of the GNU Affero General Public
+ * License along with LibEuFin; see the file COPYING.  If not, see
+ * <http://www.gnu.org/licenses/>
+ */
+
 package tech.libeufin.bank
 
 import io.ktor.server.application.*
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/types.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/types.kt
index fd65d244..92567634 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/types.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/types.kt
@@ -364,7 +364,8 @@ data class BankAccountTransactionCreate(
     val amount: String
 )
 
-// GET /transactions/T_ID
+/* History element, either from GET /transactions/T_ID
+  or from GET /transactions */
 @Serializable
 data class BankAccountTransactionInfo(
     val creditor_payto_uri: String,
@@ -376,7 +377,14 @@ data class BankAccountTransactionInfo(
     val date: Long
 )
 
+// Response type for histories, namely GET /transactions
 @Serializable
 data class BankAccountTransactionsResponse(
     val transactions: MutableList<BankAccountTransactionInfo>
-)
\ No newline at end of file
+)
+
+// Taler withdrawal request.
+@Serializable
+data class BankAccountCreateWithdrawalRequest(
+    val amount: String
+)
diff --git a/bank/src/test/kotlin/AmountTest.kt 
b/bank/src/test/kotlin/AmountTest.kt
index efdede5d..4368d9c1 100644
--- a/bank/src/test/kotlin/AmountTest.kt
+++ b/bank/src/test/kotlin/AmountTest.kt
@@ -21,9 +21,58 @@
 import org.junit.Test
 import tech.libeufin.bank.FracDigits
 import tech.libeufin.bank.TalerAmount
+import tech.libeufin.bank.isBalanceEnough
 import tech.libeufin.bank.parseTalerAmount
 
 class AmountTest {
+    @Test
+    fun amountAdditionTest() {
+        // Balance enough, assert for true
+        assert(isBalanceEnough(
+            balance = TalerAmount(10, 0, "KUDOS"),
+            due = TalerAmount(8, 0, "KUDOS"),
+            hasBalanceDebt = false,
+            maxDebt = TalerAmount(100, 0, "KUDOS")
+        ))
+        // Balance still sufficient, thanks for big enough debt permission.  
Assert true.
+        assert(isBalanceEnough(
+            balance = TalerAmount(10, 0, "KUDOS"),
+            due = TalerAmount(80, 0, "KUDOS"),
+            hasBalanceDebt = false,
+            maxDebt = TalerAmount(100, 0, "KUDOS")
+        ))
+        // Balance not enough, max debt cannot cover, asserting for false.
+        assert(!isBalanceEnough(
+            balance = TalerAmount(10, 0, "KUDOS"),
+            due = TalerAmount(80, 0, "KUDOS"),
+            hasBalanceDebt = true,
+            maxDebt = TalerAmount(50, 0, "KUDOS")
+        ))
+        // Balance becomes enough, due to a larger max debt, asserting for 
true.
+        assert(isBalanceEnough(
+            balance = TalerAmount(10, 0, "KUDOS"),
+            due = TalerAmount(80, 0, "KUDOS"),
+            hasBalanceDebt = false,
+            maxDebt = TalerAmount(70, 0, "KUDOS")
+        ))
+        // Max debt not enough for the smallest fraction, asserting for false
+        assert(!isBalanceEnough(
+            balance = TalerAmount(0, 0, "KUDOS"),
+            due = TalerAmount(0, 2, "KUDOS"),
+            hasBalanceDebt = false,
+            maxDebt = TalerAmount(0, 1, "KUDOS")
+        ))
+        // Same as above, but already in debt.
+        assert(!isBalanceEnough(
+            balance = TalerAmount(0, 1, "KUDOS"),
+            due = TalerAmount(0, 1, "KUDOS"),
+            hasBalanceDebt = true,
+            maxDebt = TalerAmount(0, 1, "KUDOS")
+        ))
+
+
+    }
+
     /* Testing that currency is fetched from the config
        and set in the TalerAmount dedicated field. */
     @Test

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