gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] branch master updated: Bank: implementing /admin/add-incoming


From: gnunet
Subject: [libeufin] branch master updated: Bank: implementing /admin/add-incoming.
Date: Wed, 20 Sep 2023 23:49:52 +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 10190a79 Bank: implementing /admin/add-incoming.
10190a79 is described below

commit 10190a79c63b97ca9e2c241775050e18c06f07ea
Author: MS <ms@taler.net>
AuthorDate: Wed Sep 20 23:49:21 2023 +0200

    Bank: implementing /admin/add-incoming.
---
 .../src/main/kotlin/tech/libeufin/bank/Database.kt | 24 +++++-
 bank/src/main/kotlin/tech/libeufin/bank/Main.kt    |  2 +-
 .../tech/libeufin/bank/talerIntegrationHandlers.kt |  6 ++
 .../tech/libeufin/bank/talerWireGatewayHandlers.kt | 96 ++++++++++++++++++++++
 bank/src/main/kotlin/tech/libeufin/bank/types.kt   | 29 +++++++
 bank/src/test/kotlin/TalerApiTest.kt               | 60 ++++++++++----
 6 files changed, 198 insertions(+), 19 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
index 6b558441..ebc827bd 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
@@ -399,7 +399,7 @@ class Database(private val dbConfig: String) {
         NO_CREDITOR,
         NO_DEBTOR,
         SUCCESS,
-        CONFLICT
+        CONFLICT // balance insufficient
     }
     fun bankTransactionCreate(
         tx: BankInternalTransaction
@@ -438,6 +438,28 @@ class Database(private val dbConfig: String) {
         }
     }
 
+    /**
+     * Only checks if a bank transaction with the given subject
+     * exists.  That's only used in the /admin/add-incoming, to
+     * prevent a public key from being reused.
+     *
+     * Returns the row ID if found, null otherwise.
+     */
+    fun bankTransactionCheckExists(subject: String): Long? {
+        reconnect()
+        val stmt = prepare("""
+            SELECT bank_transaction_id
+            FROM bank_account_transactions
+            WHERE subject = ?;           
+        """)
+        stmt.setString(1, subject)
+        val res = stmt.executeQuery()
+        res.use {
+            if (!it.next()) return null
+            return it.getLong("bank_transaction_id")
+        }
+    }
+
     // Get the bank transaction whose row ID is rowId
     fun bankTransactionGetFromInternalId(rowId: Long): BankAccountTransaction? 
{
         reconnect()
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
index b0e9e46c..179f4212 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -227,6 +227,6 @@ val webApp: Application.() -> Unit = {
         this.transactionsHandlers()
         this.talerWebHandlers()
         this.talerIntegrationHandlers()
-        // this.talerWireGatewayHandlers()
+        this.talerWireGatewayHandlers()
     }
 }
\ No newline at end of file
diff --git 
a/bank/src/main/kotlin/tech/libeufin/bank/talerIntegrationHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/talerIntegrationHandlers.kt
index 665691dc..02d980d0 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/talerIntegrationHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/talerIntegrationHandlers.kt
@@ -77,6 +77,12 @@ fun Routing.talerIntegrationHandlers() {
                 )
         }
         val dbSuccess: Boolean = if (!op.selectionDone) {
+            // Check if reserve pub. was used in _another_ withdrawal.
+            if (db.bankTransactionCheckExists(req.reserve_pub) != null)
+                throw conflict(
+                    "Reserve pub. already used",
+                    TalerErrorCode.TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT
+                )
             val exchangePayto = req.selected_exchange
                 ?: (db.configGet("suggested_exchange")
                     ?: throw internalServerError("Suggested exchange not 
found")
diff --git 
a/bank/src/main/kotlin/tech/libeufin/bank/talerWireGatewayHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/talerWireGatewayHandlers.kt
new file mode 100644
index 00000000..672d9bdb
--- /dev/null
+++ b/bank/src/main/kotlin/tech/libeufin/bank/talerWireGatewayHandlers.kt
@@ -0,0 +1,96 @@
+/*
+ * 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 the Taler Wire Gateway API handlers.
+
+package tech.libeufin.bank
+
+import io.ktor.server.application.*
+import io.ktor.server.request.*
+import io.ktor.server.response.*
+import io.ktor.server.routing.*
+import net.taler.common.errorcodes.TalerErrorCode
+import tech.libeufin.util.getNowUs
+
+fun Routing.talerWireGatewayHandlers() {
+    get("/accounts/{USERNAME}/taler-wire-gateway/config") {
+        val internalCurrency = db.configGet("internal_currency")
+            ?: throw internalServerError("Could not find bank own currency.")
+        call.respond(TWGConfigResponse(currency = internalCurrency))
+        return@get
+    }
+    get("/accounts/{USERNAME}/taler-wire-gateway/history/incoming") {
+        return@get
+    }
+    post("/accounts/{USERNAME}/taler-wire-gateway/transfer") {
+        return@post
+    }
+    post("/accounts/{USERNAME}/taler-wire-gateway/admin/add-incoming") {
+        val c = call.myAuth(TokenScope.readwrite) ?: throw unauthorized()
+        if (!call.getResourceName("USERNAME").canI(c, withAdmin = false)) 
throw forbidden()
+        val req = call.receive<AddIncomingRequest>()
+        val amount = parseTalerAmount(req.amount)
+        val internalCurrency = db.configGet("internal_currency")
+            ?: throw internalServerError("Bank didn't find own currency.")
+        if (amount.currency != internalCurrency)
+            throw badRequest(
+                "Currency mismatch",
+                TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH
+            )
+        if (db.bankTransactionCheckExists(req.reserve_pub) != null)
+            throw conflict(
+                "Reserve pub. already used",
+                TalerErrorCode.TALER_EC_BANK_DUPLICATE_RESERVE_PUB_SUBJECT
+            )
+        val walletAccount = 
db.bankAccountGetFromInternalPayto(req.debit_account)
+            ?: throw notFound(
+                "debit_account not found",
+                TalerErrorCode.TALER_EC_BANK_UNKNOWN_ACCOUNT
+            )
+        val exchangeAccount = db.bankAccountGetFromOwnerId(c.expectRowId())
+            ?: throw internalServerError("exchange bank account not found, 
despite it's a customer")
+        val txTimestamp = getNowUs()
+        val op = BankInternalTransaction(
+            debtorAccountId = walletAccount.expectRowId(),
+            amount = amount,
+            creditorAccountId = exchangeAccount.expectRowId(),
+            transactionDate = txTimestamp,
+            subject = req.reserve_pub
+        )
+        val res = db.bankTransactionCreate(op)
+        /**
+         * Other possible errors are highly unlikely, because of the
+         * previous checks on the existence of the involved bank accounts.
+         */
+        if (res == Database.BankTransactionResult.CONFLICT)
+            throw conflict(
+                "Insufficient balance",
+                TalerErrorCode.TALER_EC_BANK_UNALLOWED_DEBIT
+            )
+        val rowId = db.bankTransactionCheckExists(req.reserve_pub)
+            ?: throw internalServerError("Could not find the just inserted 
bank transaction")
+        call.respond(
+            AddIncomingResponse(
+                row_id = rowId,
+                timestamp = txTimestamp
+        ))
+        return@post
+    }
+}
+
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/types.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/types.kt
index 8daaf8ce..6311e3e9 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/types.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/types.kt
@@ -492,8 +492,37 @@ data class BankWithdrawalOperationPostRequest(
     val selected_exchange: String? = null // Use suggested exchange if that's 
missing.
 )
 
+/**
+ * Response to the wallet after it selects the exchange
+ * and the reserve pub.
+ */
 @Serializable
 data class BankWithdrawalOperationPostResponse(
     val transfer_done: Boolean,
     val confirm_transfer_url: String? = null
+)
+
+/**
+ * Request to an /admin/add-incoming request from
+ * the Taler Wire Gateway API.
+ */
+@Serializable
+data class AddIncomingRequest(
+    val amount: String,
+    val reserve_pub: String,
+    val debit_account: String
+)
+
+// Response to /admin/add-incoming
+@Serializable
+data class AddIncomingResponse(
+    val timestamp: Long,
+    val row_id: Long
+)
+
+@Serializable
+data class TWGConfigResponse(
+    val name: String = "taler-wire-gateway",
+    val version: String = "0:0:0:",
+    val currency: String
 )
\ No newline at end of file
diff --git a/bank/src/test/kotlin/TalerApiTest.kt 
b/bank/src/test/kotlin/TalerApiTest.kt
index 06e4cbe4..7f5c407b 100644
--- a/bank/src/test/kotlin/TalerApiTest.kt
+++ b/bank/src/test/kotlin/TalerApiTest.kt
@@ -27,6 +27,49 @@ class TalerApiTest {
         hasDebt = false,
         maxDebt = TalerAmount(10, 1, "KUDOS")
     )
+    val bankAccountBar = BankAccount(
+        internalPaytoUri = "BAR-IBAN-ABC",
+        lastNexusFetchRowId = 1L,
+        owningCustomerId = 2L,
+        hasDebt = false,
+        maxDebt = TalerAmount(10, 1, "KUDOS")
+    )
+    val customerBar = Customer(
+        login = "bar",
+        passwordHash = "hash",
+        name = "Bar",
+        phone = "+00",
+        email = "foo@b.ar",
+        cashoutPayto = "payto://external-IBAN",
+        cashoutCurrency = "KUDOS"
+    )
+    @Test
+    fun addIncoming() {
+        val db = initDb()
+        assert(db.customerCreate(customerFoo) != null)
+        assert(db.bankAccountCreate(bankAccountFoo))
+        assert(db.customerCreate(customerBar) != null)
+        assert(db.bankAccountCreate(bankAccountBar))
+        assert(db.bankAccountSetMaxDebt(
+            2L,
+            TalerAmount(1000, 0)
+        ))
+        testApplication {
+            application(webApp)
+            client.post("/accounts/foo/taler-wire-gateway/admin/add-incoming") 
{
+                expectSuccess = true
+                contentType(ContentType.Application.Json)
+                basicAuth("foo", "pw")
+                setBody("""
+                    {"amount": "KUDOS:44",
+                     "reserve_pub": "RESERVE-PUB-TEST",
+                      "debit_account": "BAR-IBAN-ABC"
+                      }
+                """.trimIndent())
+            }
+        }
+
+    }
     // Selecting withdrawal details from the Integrtion API endpoint.
     @Test
     fun intSelect() {
@@ -136,23 +179,6 @@ class TalerApiTest {
     @Test
     fun withdrawalConfirmation() {
         val db = initDb()
-        val bankAccountBar = BankAccount(
-            internalPaytoUri = "BAR-IBAN-ABC",
-            lastNexusFetchRowId = 1L,
-            owningCustomerId = 2L,
-            hasDebt = false,
-            maxDebt = TalerAmount(10, 1, "KUDOS")
-        )
-        val customerBar = Customer(
-            login = "bar",
-            passwordHash = "hash",
-            name = "Bar",
-            phone = "+00",
-            email = "foo@b.ar",
-            cashoutPayto = "payto://external-IBAN",
-            cashoutCurrency = "KUDOS"
-        )
-
         // Creating Foo as the wallet owner and Bar as the exchange.
         assert(db.customerCreate(customerFoo) != null)
         assert(db.bankAccountCreate(bankAccountFoo))

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