commit b0e1a2b3cd3eb0ce3c6f6567570f019fe9a36900
parent 26097f20f8003b981a6d231fcd91592e818323d0
Author: MS <ms@taler.net>
Date: Tue, 19 Sep 2023 14:55:30 +0200
Taler URI generator.
Diffstat:
5 files changed, 109 insertions(+), 3 deletions(-)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Database.kt b/bank/src/main/kotlin/tech/libeufin/bank/Database.kt
@@ -32,6 +32,7 @@ private const val DB_CTR_LIMIT = 1000000
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.")
+fun BankAccount.expectRowId(): Long = this.bankAccountId ?: throw internalServerError("Bank account '${this.internalPaytoUri}' lacks database row ID.")
class Database(private val dbConfig: String) {
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
@@ -21,10 +21,12 @@ package tech.libeufin.bank
import io.ktor.http.*
import io.ktor.server.application.*
+import io.ktor.server.util.*
import net.taler.common.errorcodes.TalerErrorCode
import net.taler.wallet.crypto.Base32Crockford
import tech.libeufin.util.*
import java.lang.NumberFormatException
+import java.net.URL
fun ApplicationCall.expectUriComponent(componentName: String) =
this.maybeUriComponent(componentName) ?: throw badRequest(
@@ -335,4 +337,36 @@ fun isBalanceEnough(
(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
+fun getBankCurrency(): String = db.configGet("internal_currency") ?: throw internalServerError("Bank lacks currency")
+
+/**
+ * Builds the taler://withdraw-URI. Such URI will serve the requests
+ * from wallets, when they need to manage the operation. For example,
+ * a URI like taler://withdraw/$BANK_URL/taler-integration/$WO_ID needs
+ * the bank to implement the Taler integratino API at the following base URL:
+ *
+ * https://$BANK_URL/taler-integration
+ */
+fun getTalerWithdrawUri(baseUrl: String, woId: String) =
+ url {
+ val baseUrlObj = URL(baseUrl)
+ protocol = URLProtocol(
+ name = "taler".plus(if (baseUrlObj.protocol.lowercase() == "http") "+http" else ""),
+ defaultPort = -1
+ )
+ host = "withdraw"
+ val pathSegments = mutableListOf(
+ // adds the hostname(+port) of the actual bank that will serve the withdrawal request.
+ baseUrlObj.host.plus(
+ if (baseUrlObj.port != -1)
+ ":${baseUrlObj.port}"
+ else ""
+ )
+ )
+ // Removing potential double slashes.
+ baseUrlObj.path.split("/").forEach {
+ if (it.isNotEmpty()) pathSegments.add(it)
+ }
+ pathSegments.add("taler-integration/${woId}")
+ this.appendPathSegments(pathSegments)
+ }
+\ 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
@@ -26,7 +26,10 @@ 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 java.util.*
fun Routing.talerWebHandlers() {
post("/accounts/{USERNAME}/withdrawals") {
@@ -39,8 +42,34 @@ fun Routing.talerWebHandlers() {
// Checking that the user has enough funds.
val b = db.bankAccountGetFromOwnerId(c.expectRowId())
?: throw internalServerError("Customer '${c.login}' lacks bank account.")
+ val withdrawalAmount = parseTalerAmount(req.amount)
+ if (
+ !isBalanceEnough(
+ balance = b.expectBalance(),
+ due = withdrawalAmount,
+ maxDebt = b.maxDebt,
+ hasBalanceDebt = b.hasDebt
+ ))
+ throw forbidden(
+ hint = "Insufficient funds to withdraw with Taler",
+ talerErrorCode = TalerErrorCode.TALER_EC_NONE // FIXME: need EC.
+ )
+ // Auth and funds passed, create the operation now!
+ val opId = UUID.randomUUID()
+ if(
+ !db.talerWithdrawalCreate(
+ opId,
+ b.expectRowId(),
+ withdrawalAmount
+ )
+ )
+ throw internalServerError("Bank failed at creating the withdraw operation.")
- throw NotImplementedError()
+ call.respond(BankAccountCreateWithdrawalResponse(
+ withdrawal_id = opId.toString(),
+ taler_withdraw_uri = "FIXME"
+ ))
+ return@post
}
get("/accounts/{USERNAME}/withdrawals/{W_ID}") {
throw NotImplementedError()
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/types.kt b/bank/src/main/kotlin/tech/libeufin/bank/types.kt
@@ -388,3 +388,10 @@ data class BankAccountTransactionsResponse(
data class BankAccountCreateWithdrawalRequest(
val amount: String
)
+
+// Taler withdrawal response.
+@Serializable
+data class BankAccountCreateWithdrawalResponse(
+ val withdrawal_id: String,
+ val taler_withdraw_uri: String
+)
diff --git a/bank/src/test/kotlin/TalerTest.kt b/bank/src/test/kotlin/TalerTest.kt
@@ -0,0 +1,34 @@
+import org.junit.Test
+import tech.libeufin.bank.getTalerWithdrawUri
+
+class TalerTest {
+ // Testing the generation of taler://withdraw-URIs.
+ @Test
+ fun testWithdrawUri() {
+ // Checking the taler+http://-style.
+ val withHttp = getTalerWithdrawUri(
+ "http://example.com",
+ "my-id"
+ )
+ assert(withHttp == "taler+http://withdraw/example.com/taler-integration/my-id")
+ // Checking the taler://-style
+ val onlyTaler = getTalerWithdrawUri(
+ "https://example.com/",
+ "my-id"
+ )
+ // Note: this tests as well that no double slashes belong to the result
+ assert(onlyTaler == "taler://withdraw/example.com/taler-integration/my-id")
+ // Checking the removal of subsequent slashes
+ val manySlashes = getTalerWithdrawUri(
+ "https://www.example.com//////",
+ "my-id"
+ )
+ assert(manySlashes == "taler://withdraw/www.example.com/taler-integration/my-id")
+ // Checking with specified port number
+ val withPort = getTalerWithdrawUri(
+ "https://www.example.com:9876",
+ "my-id"
+ )
+ assert(withPort == "taler://withdraw/www.example.com:9876/taler-integration/my-id")
+ }
+}
+\ No newline at end of file