aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntoine A <>2023-11-17 15:43:13 +0000
committerAntoine A <>2023-11-17 15:43:13 +0000
commitc553a2991ec6aeb910351658092cef1762e413d2 (patch)
tree0be8f4a8a929aa47d1151f8888ae87045e548154
parentffb74ead5035f3218f137b855e85edc91a8e4552 (diff)
downloadlibeufin-c553a2991ec6aeb910351658092cef1762e413d2.tar.gz
libeufin-c553a2991ec6aeb910351658092cef1762e413d2.tar.bz2
libeufin-c553a2991ec6aeb910351658092cef1762e413d2.zip
Add Taler Conversion Info API
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/ConversionApi.kt93
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt45
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/Main.kt1
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt20
-rw-r--r--bank/src/test/kotlin/ConversionApiTest.kt118
-rw-r--r--bank/src/test/kotlin/CoreBankApiTest.kt76
-rw-r--r--bank/src/test/kotlin/helpers.kt2
7 files changed, 232 insertions, 123 deletions
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/ConversionApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/ConversionApi.kt
new file mode 100644
index 00000000..b8d540e0
--- /dev/null
+++ b/bank/src/main/kotlin/tech/libeufin/bank/ConversionApi.kt
@@ -0,0 +1,93 @@
+/*
+ * This file is part of LibEuFin.
+ * Copyright (C) 2023 Taler Systems S.A.
+
+ * 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.http.*
+import io.ktor.server.application.*
+import io.ktor.server.request.*
+import io.ktor.server.response.*
+import io.ktor.server.routing.*
+import java.util.*
+import tech.libeufin.util.*
+import net.taler.common.errorcodes.TalerErrorCode
+
+fun Routing.conversionApi(db: Database, ctx: BankConfig) = conditional(ctx.haveCashout) {
+ get("/conversion-info/config") {
+ call.respond(
+ ConversionConfig(
+ currency = ctx.currency,
+ fiat_currency = ctx.fiatCurrency,
+ cashin_ratio = ctx.conversionInfo!!.cashin_ratio,
+ cashin_fee = ctx.conversionInfo.cashin_fee,
+ cashin_tiny_amount = ctx.conversionInfo.cashin_tiny_amount,
+ cashin_rounding_mode = ctx.conversionInfo.cashin_rounding_mode,
+ cashin_min_amount = ctx.conversionInfo.cashin_min_amount,
+ cashout_ratio = ctx.conversionInfo.cashout_ratio,
+ cashout_fee = ctx.conversionInfo.cashout_fee,
+ cashout_tiny_amount = ctx.conversionInfo.cashout_tiny_amount,
+ cashout_rounding_mode = ctx.conversionInfo.cashout_rounding_mode,
+ cashout_min_amount = ctx.conversionInfo.cashout_min_amount
+ )
+ )
+ }
+ get("/conversion-info/cashout-rate") {
+ val params = RateParams.extract(call.request.queryParameters)
+
+ params.debit?.let { ctx.checkRegionalCurrency(it) }
+ params.credit?.let { ctx.checkFiatCurrency(it) }
+
+ if (params.debit != null) {
+ val credit = db.conversion.toCashout(params.debit) ?:
+ throw conflict(
+ "${params.debit} is too small to be converted",
+ TalerErrorCode.BANK_BAD_CONVERSION
+ )
+ call.respond(ConversionResponse(params.debit, credit))
+ } else {
+ val debit = db.conversion.fromCashout(params.credit!!) ?:
+ throw conflict(
+ "${params.debit} is too small to be converted",
+ TalerErrorCode.BANK_BAD_CONVERSION
+ )
+ call.respond(ConversionResponse(debit, params.credit))
+ }
+ }
+ get("/conversion-info/cashin-rate") {
+ val params = RateParams.extract(call.request.queryParameters)
+
+ params.debit?.let { ctx.checkFiatCurrency(it) }
+ params.credit?.let { ctx.checkRegionalCurrency(it) }
+
+ if (params.debit != null) {
+ val credit = db.conversion.toCashin(params.debit) ?:
+ throw conflict(
+ "${params.debit} is too small to be converted",
+ TalerErrorCode.BANK_BAD_CONVERSION
+ )
+ call.respond(ConversionResponse(params.debit, credit))
+ } else {
+ val debit = db.conversion.fromCashin(params.credit!!) ?:
+ throw conflict(
+ "${params.debit} is too small to be converted",
+ TalerErrorCode.BANK_BAD_CONVERSION
+ )
+ call.respond(ConversionResponse(debit, params.credit))
+ }
+ }
+} \ No newline at end of file
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
index 0cb7e519..d86e12ce 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
@@ -51,7 +51,6 @@ fun Routing.coreBankApi(db: Database, ctx: BankConfig) {
currency = ctx.currencySpecification,
have_cashout = ctx.haveCashout,
fiat_currency = ctx.fiatCurrency,
- conversion_info = ctx.conversionInfo,
allow_registrations = !ctx.restrictRegistration,
allow_deletions = !ctx.restrictAccountDeletion
)
@@ -609,48 +608,4 @@ private fun Routing.coreBankCashoutApi(db: Database, ctx: BankConfig) = conditio
}
}
}
- get("/cashout-rate") {
- val params = RateParams.extract(call.request.queryParameters)
-
- params.debit?.let { ctx.checkRegionalCurrency(it) }
- params.credit?.let { ctx.checkFiatCurrency(it) }
-
- if (params.debit != null) {
- val credit = db.conversion.toCashout(params.debit) ?:
- throw conflict(
- "${params.debit} is too small to be converted",
- TalerErrorCode.BANK_BAD_CONVERSION
- )
- call.respond(ConversionResponse(params.debit, credit))
- } else {
- val debit = db.conversion.fromCashout(params.credit!!) ?:
- throw conflict(
- "${params.debit} is too small to be converted",
- TalerErrorCode.BANK_BAD_CONVERSION
- )
- call.respond(ConversionResponse(debit, params.credit))
- }
- }
- get("/cashin-rate") {
- val params = RateParams.extract(call.request.queryParameters)
-
- params.debit?.let { ctx.checkFiatCurrency(it) }
- params.credit?.let { ctx.checkRegionalCurrency(it) }
-
- if (params.debit != null) {
- val credit = db.conversion.toCashin(params.debit) ?:
- throw conflict(
- "${params.debit} is too small to be converted",
- TalerErrorCode.BANK_BAD_CONVERSION
- )
- call.respond(ConversionResponse(params.debit, credit))
- } else {
- val debit = db.conversion.fromCashin(params.credit!!) ?:
- throw conflict(
- "${params.debit} is too small to be converted",
- TalerErrorCode.BANK_BAD_CONVERSION
- )
- call.respond(ConversionResponse(debit, params.credit))
- }
- }
}
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
index 7ec57cf9..3d0bf956 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -209,6 +209,7 @@ fun Application.corebankWebApp(db: Database, ctx: BankConfig) {
}
routing {
coreBankApi(db, ctx)
+ conversionApi(db, ctx)
bankIntegrationApi(db, ctx)
wireGatewayApi(db, ctx)
revenueApi(db)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt
index bf9593ab..e97a7a5c 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt
@@ -205,7 +205,6 @@ data class Config(
val currency: CurrencySpecification,
val have_cashout: Boolean,
val fiat_currency: String?,
- val conversion_info: ConversionInfo?,
val allow_registrations: Boolean,
val allow_deletions: Boolean
) {
@@ -213,6 +212,25 @@ data class Config(
val version: String = "0:0:0"
}
+@Serializable
+data class ConversionConfig(
+ val currency: String,
+ val fiat_currency: String?,
+ val cashin_ratio: DecimalNumber,
+ val cashin_fee: TalerAmount,
+ val cashin_tiny_amount: TalerAmount,
+ val cashin_rounding_mode: RoundingMode,
+ val cashin_min_amount: TalerAmount,
+ val cashout_ratio: DecimalNumber,
+ val cashout_fee: TalerAmount,
+ val cashout_tiny_amount: TalerAmount,
+ val cashout_rounding_mode: RoundingMode,
+ val cashout_min_amount: TalerAmount,
+) {
+ val name: String = "taler-conversion-info"
+ val version: String = "0:0:0"
+}
+
enum class CreditDebitInfo {
credit, debit
}
diff --git a/bank/src/test/kotlin/ConversionApiTest.kt b/bank/src/test/kotlin/ConversionApiTest.kt
new file mode 100644
index 00000000..30cc7706
--- /dev/null
+++ b/bank/src/test/kotlin/ConversionApiTest.kt
@@ -0,0 +1,118 @@
+/*
+ * This file is part of LibEuFin.
+ * Copyright (C) 2023 Taler Systems S.A.
+
+ * 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/>
+ */
+
+import io.ktor.client.plugins.*
+import io.ktor.client.request.*
+import io.ktor.client.statement.*
+import io.ktor.http.*
+import io.ktor.server.testing.*
+import java.util.*
+import kotlin.test.*
+import kotlinx.coroutines.*
+import kotlinx.serialization.json.*
+import net.taler.common.errorcodes.TalerErrorCode
+import org.junit.Test
+import tech.libeufin.bank.*
+
+class ConversionApiTest {
+ // GET /conversion-info/config
+ @Test
+ fun config() = bankSetup { _ ->
+ client.get("/conversion-info/config").assertOk()
+ }
+
+ // GET /conversion-info/cashout-rate
+ @Test
+ fun cashoutRate() = bankSetup { _ ->
+ // Check conversion to
+ client.get("/conversion-info/cashout-rate?amount_debit=KUDOS:1").assertOkJson<ConversionResponse> {
+ assertEquals(TalerAmount("KUDOS:1"), it.amount_debit)
+ assertEquals(TalerAmount("FIAT:1.247"), it.amount_credit)
+ }
+ // Check conversion from
+ client.get("/conversion-info/cashout-rate?amount_credit=FIAT:1.247").assertOkJson<ConversionResponse> {
+ assertEquals(TalerAmount("KUDOS:1"), it.amount_debit)
+ assertEquals(TalerAmount("FIAT:1.247"), it.amount_credit)
+ }
+
+ // Too small
+ client.get("/conversion-info/cashout-rate?amount_debit=KUDOS:0.08")
+ .assertConflict(TalerErrorCode.BANK_BAD_CONVERSION)
+ // No amount
+ client.get("/conversion-info/cashout-rate")
+ .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MISSING)
+ // Both amount
+ client.get("/conversion-info/cashout-rate?amount_debit=FIAT:1&amount_credit=KUDOS:1")
+ .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
+ // Wrong format
+ client.get("/conversion-info/cashout-rate?amount_debit=1")
+ .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
+ client.get("/conversion-info/cashout-rate?amount_credit=1")
+ .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
+ // Wrong currency
+ client.get("/conversion-info/cashout-rate?amount_debit=FIAT:1")
+ .assertBadRequest(TalerErrorCode.GENERIC_CURRENCY_MISMATCH)
+ client.get("/conversion-info/cashout-rate?amount_credit=KUDOS:1")
+ .assertBadRequest(TalerErrorCode.GENERIC_CURRENCY_MISMATCH)
+ }
+
+ // GET /conversion-info/cashin-rate
+ @Test
+ fun cashinRate() = bankSetup { _ ->
+ for ((amount, converted) in listOf(
+ Pair(0.75, 0.58), Pair(0.32, 0.24), Pair(0.66, 0.51)
+ )) {
+ // Check conversion to
+ client.get("/conversion-info/cashin-rate?amount_debit=FIAT:$amount").assertOkJson<ConversionResponse> {
+ assertEquals(TalerAmount("KUDOS:$converted"), it.amount_credit)
+ assertEquals(TalerAmount("FIAT:$amount"), it.amount_debit)
+ }
+ // Check conversion from
+ client.get("/conversion-info/cashin-rate?amount_credit=KUDOS:$converted").assertOkJson<ConversionResponse> {
+ assertEquals(TalerAmount("KUDOS:$converted"), it.amount_credit)
+ assertEquals(TalerAmount("FIAT:$amount"), it.amount_debit)
+ }
+ }
+
+ // No amount
+ client.get("/conversion-info/cashin-rate")
+ .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MISSING)
+ // Both amount
+ client.get("/conversion-info/cashin-rate?amount_debit=KUDOS:1&amount_credit=FIAT:1")
+ .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
+ // Wrong format
+ client.get("/conversion-info/cashin-rate?amount_debit=1")
+ .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
+ client.get("/conversion-info/cashin-rate?amount_credit=1")
+ .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
+ // Wrong currency
+ client.get("/conversion-info/cashin-rate?amount_debit=KUDOS:1")
+ .assertBadRequest(TalerErrorCode.GENERIC_CURRENCY_MISMATCH)
+ client.get("/conversion-info/cashin-rate?amount_credit=FIAT:1")
+ .assertBadRequest(TalerErrorCode.GENERIC_CURRENCY_MISMATCH)
+ }
+
+ @Test
+ fun notImplemented() = bankSetup("test_restrict.conf") { _ ->
+ client.get("/conversion-info/cashin-rate")
+ .assertNotImplemented()
+ client.get("/conversion-info/cashout-rate")
+ .assertNotImplemented()
+ }
+} \ No newline at end of file
diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt
index c6ae9758..f25629ea 100644
--- a/bank/src/test/kotlin/CoreBankApiTest.kt
+++ b/bank/src/test/kotlin/CoreBankApiTest.kt
@@ -1342,84 +1342,8 @@ class CoreBankCashoutApiTest {
}.assertHistory(10)
}
- // GET /cashout-rate
- @Test
- fun cashoutRate() = bankSetup { _ ->
- // Check conversion to
- client.get("/cashout-rate?amount_debit=KUDOS:1").assertOkJson<ConversionResponse> {
- assertEquals(TalerAmount("KUDOS:1"), it.amount_debit)
- assertEquals(TalerAmount("FIAT:1.247"), it.amount_credit)
- }
- // Check conversion from
- client.get("/cashout-rate?amount_credit=FIAT:1.247").assertOkJson<ConversionResponse> {
- assertEquals(TalerAmount("KUDOS:1"), it.amount_debit)
- assertEquals(TalerAmount("FIAT:1.247"), it.amount_credit)
- }
-
- // Too small
- client.get("/cashout-rate?amount_debit=KUDOS:0.08")
- .assertConflict(TalerErrorCode.BANK_BAD_CONVERSION)
- // No amount
- client.get("/cashout-rate")
- .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MISSING)
- // Both amount
- client.get("/cashout-rate?amount_debit=FIAT:1&amount_credit=KUDOS:1")
- .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
- // Wrong format
- client.get("/cashout-rate?amount_debit=1")
- .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
- client.get("/cashout-rate?amount_credit=1")
- .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
- // Wrong currency
- client.get("/cashout-rate?amount_debit=FIAT:1")
- .assertBadRequest(TalerErrorCode.GENERIC_CURRENCY_MISMATCH)
- client.get("/cashout-rate?amount_credit=KUDOS:1")
- .assertBadRequest(TalerErrorCode.GENERIC_CURRENCY_MISMATCH)
- }
-
- // GET /cashin-rate
- @Test
- fun cashinRate() = bankSetup { _ ->
-
- for ((amount, converted) in listOf(
- Pair(0.75, 0.58), Pair(0.32, 0.24), Pair(0.66, 0.51)
- )) {
- // Check conversion to
- client.get("/cashin-rate?amount_debit=FIAT:$amount").assertOkJson<ConversionResponse> {
- assertEquals(TalerAmount("KUDOS:$converted"), it.amount_credit)
- assertEquals(TalerAmount("FIAT:$amount"), it.amount_debit)
- }
- // Check conversion from
- client.get("/cashin-rate?amount_credit=KUDOS:$converted").assertOkJson<ConversionResponse> {
- assertEquals(TalerAmount("KUDOS:$converted"), it.amount_credit)
- assertEquals(TalerAmount("FIAT:$amount"), it.amount_debit)
- }
- }
-
- // No amount
- client.get("/cashin-rate")
- .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MISSING)
- // Both amount
- client.get("/cashin-rate?amount_debit=KUDOS:1&amount_credit=FIAT:1")
- .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
- // Wrong format
- client.get("/cashin-rate?amount_debit=1")
- .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
- client.get("/cashin-rate?amount_credit=1")
- .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
- // Wrong currency
- client.get("/cashin-rate?amount_debit=KUDOS:1")
- .assertBadRequest(TalerErrorCode.GENERIC_CURRENCY_MISMATCH)
- client.get("/cashin-rate?amount_credit=FIAT:1")
- .assertBadRequest(TalerErrorCode.GENERIC_CURRENCY_MISMATCH)
- }
-
@Test
fun notImplemented() = bankSetup("test_restrict.conf") { _ ->
- client.get("/cashin-rate")
- .assertNotImplemented()
- client.get("/cashout-rate")
- .assertNotImplemented()
client.get("/accounts/customer/cashouts")
.assertNotImplemented()
}
diff --git a/bank/src/test/kotlin/helpers.kt b/bank/src/test/kotlin/helpers.kt
index 41302ce5..ace1e013 100644
--- a/bank/src/test/kotlin/helpers.kt
+++ b/bank/src/test/kotlin/helpers.kt
@@ -201,7 +201,7 @@ suspend fun ApplicationTestBuilder.withdrawalSelect(uuid: String) {
}
suspend fun ApplicationTestBuilder.convert(amount: String): TalerAmount {
- return client.get("/cashout-rate?amount_debit=$amount")
+ return client.get("/conversion-info/cashout-rate?amount_debit=$amount")
.assertOkJson<ConversionResponse>().amount_credit
}