libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit b0e1a2b3cd3eb0ce3c6f6567570f019fe9a36900
parent 26097f20f8003b981a6d231fcd91592e818323d0
Author: MS <ms@taler.net>
Date:   Tue, 19 Sep 2023 14:55:30 +0200

Taler URI generator.

Diffstat:
Mbank/src/main/kotlin/tech/libeufin/bank/Database.kt | 1+
Mbank/src/main/kotlin/tech/libeufin/bank/helpers.kt | 38++++++++++++++++++++++++++++++++++++--
Mbank/src/main/kotlin/tech/libeufin/bank/talerWebHandlers.kt | 31++++++++++++++++++++++++++++++-
Mbank/src/main/kotlin/tech/libeufin/bank/types.kt | 7+++++++
Abank/src/test/kotlin/TalerTest.kt | 35+++++++++++++++++++++++++++++++++++
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