commit e4f20549847d1477c083dd9335f9ffc35b65be74
parent 9d15c7304e3eaea5c98373c8ed465c824203e56d
Author: Antoine A <>
Date: Fri, 17 Nov 2023 16:53:02 +0000
Apply new spec
Diffstat:
11 files changed, 78 insertions(+), 88 deletions(-)
diff --git a/bank/conf/test.conf b/bank/conf/test.conf
@@ -4,8 +4,8 @@ DEFAULT_CUSTOMER_DEBT_LIMIT = KUDOS:100
DEFAULT_ADMIN_DEBT_LIMIT = KUDOS:10000
REGISTRATION_BONUS_ENABLED = NO
SUGGESTED_WITHDRAWAL_EXCHANGE = https://exchange.example.com
-have_cashout = YES
-fiat_currency = FIAT
+allow_conversion = YES
+fiat_currency = EUR
tan_sms = libeufin-tan-file.sh
tan_email = libeufin-tan-fail.sh
@@ -19,5 +19,5 @@ cashin_fee = KUDOS:0.02
cashin_tiny_amount = KUDOS:0.01
cashin_rounding_mode = nearest
cashout_ratio = 1.25
-cashout_fee = FIAT:0.003
+cashout_fee = EUR:0.003
cashout_min_amount = KUDOS:0.1
diff --git a/bank/conf/test_no_tan.conf b/bank/conf/test_no_tan.conf
@@ -4,8 +4,8 @@ DEFAULT_CUSTOMER_DEBT_LIMIT = KUDOS:100
DEFAULT_ADMIN_DEBT_LIMIT = KUDOS:10000
REGISTRATION_BONUS_ENABLED = NO
SUGGESTED_WITHDRAWAL_EXCHANGE = https://exchange.example.com
-have_cashout = YES
-fiat_currency = FIAT
+allow_conversion = YES
+fiat_currency = EUR
[libeufin-bankdb-postgres]
SQL_DIR = $DATADIR/sql/
@@ -17,5 +17,5 @@ cashin_fee = KUDOS:0.02
cashin_tiny_amount = KUDOS:0.01
cashin_rounding_mode = nearest
cashout_ratio = 1.25
-cashout_fee = FIAT:0.003
+cashout_fee = EUR:0.003
cashout_min_amount = KUDOS:0.1
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt
@@ -77,8 +77,9 @@ data class BankConfig(
* SPA is located.
*/
val spaCaptchaURL: String?,
- val haveCashout: Boolean,
+ val allowConversion: Boolean,
val fiatCurrency: String?,
+ val fiatCurrencySpecification: CurrencySpecification?,
val conversionInfo: ConversionInfo?,
val tanSms: String?,
val tanEmail: String?,
@@ -125,18 +126,18 @@ fun TalerConfig.loadServerConfig(): ServerConfig = catchError {
fun TalerConfig.loadBankConfig(): BankConfig = catchError {
val currency = requireString("libeufin-bank", "currency")
- val currencySpecification = sections.find {
- it.startsWith("CURRENCY-") && requireBoolean(it, "enabled") && requireString(it, "code") == currency
- }?.let { loadCurrencySpecification(it) } ?: throw TalerConfigError("missing currency specification for $currency")
var fiatCurrency: String? = null;
+ var fiatCurrencySpecification: CurrencySpecification? = null
var conversionInfo: ConversionInfo? = null;
- val haveCashout = lookupBoolean("libeufin-bank", "have_cashout") ?: false;
- if (haveCashout) {
+ val allowConversion = lookupBoolean("libeufin-bank", "allow_conversion") ?: false;
+ if (allowConversion) {
fiatCurrency = requireString("libeufin-bank", "fiat_currency");
+ fiatCurrencySpecification = currencySpecificationFor(fiatCurrency)
conversionInfo = loadConversionInfo(currency, fiatCurrency)
}
BankConfig(
currency = currency,
+ currencySpecification = currencySpecificationFor(currency),
restrictRegistration = lookupBoolean("libeufin-bank", "restrict_registration") ?: false,
defaultCustomerDebtLimit = requireAmount("libeufin-bank", "default_customer_debt_limit", currency),
registrationBonusEnabled = lookupBoolean("libeufin-bank", "registration_bonus_enabled") ?: false,
@@ -145,15 +146,21 @@ fun TalerConfig.loadBankConfig(): BankConfig = catchError {
defaultAdminDebtLimit = requireAmount("libeufin-bank", "default_admin_debt_limit", currency),
spaCaptchaURL = lookupString("libeufin-bank", "spa_captcha_url"),
restrictAccountDeletion = lookupBoolean("libeufin-bank", "restrict_account_deletion") ?: true,
- currencySpecification = currencySpecification,
- haveCashout = haveCashout,
+ allowConversion = allowConversion,
fiatCurrency = fiatCurrency,
+ fiatCurrencySpecification = fiatCurrencySpecification,
conversionInfo = conversionInfo,
tanSms = lookupPath("libeufin-bank", "tan_sms"),
tanEmail = lookupPath("libeufin-bank", "tan_email"),
)
}
+fun TalerConfig.currencySpecificationFor(currency: String): CurrencySpecification = catchError {
+ sections.find {
+ it.startsWith("CURRENCY-") && requireBoolean(it, "enabled") && requireString(it, "code") == currency
+ }?.let { loadCurrencySpecification(it) } ?: throw TalerConfigError("missing currency specification for $currency")
+}
+
private fun TalerConfig.loadConversionInfo(currency: String, fiatCurrency: String): ConversionInfo = catchError {
ConversionInfo(
cashin_ratio = requireDecimalNumber("libeufin-bank-conversion", "cashin_ratio"),
@@ -172,11 +179,10 @@ private fun TalerConfig.loadConversionInfo(currency: String, fiatCurrency: Strin
private fun TalerConfig.loadCurrencySpecification(section: String): CurrencySpecification = catchError {
CurrencySpecification(
name = requireString(section, "name"),
- decimal_separator = requireString(section, "decimal_separator"),
+ code = requireString(section, "code"),
num_fractional_input_digits = requireNumber(section, "fractional_input_digits"),
num_fractional_normal_digits = requireNumber(section, "fractional_normal_digits"),
num_fractional_trailing_zero_digits = requireNumber(section, "fractional_trailing_zero_digits"),
- is_currency_name_leading = requireBoolean(section, "is_currency_name_leading"),
alt_unit_names = Json.decodeFromString(requireString(section, "alt_unit_names"))
)
}
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/ConversionApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/ConversionApi.kt
@@ -27,12 +27,14 @@ import java.util.*
import tech.libeufin.util.*
import net.taler.common.errorcodes.TalerErrorCode
-fun Routing.conversionApi(db: Database, ctx: BankConfig) = conditional(ctx.haveCashout) {
+fun Routing.conversionApi(db: Database, ctx: BankConfig) = conditional(ctx.allowConversion) {
get("/conversion-info/config") {
call.respond(
ConversionConfig(
- currency = ctx.currency,
- fiat_currency = ctx.fiatCurrency,
+ regional_currency = ctx.currency,
+ regional_currency_specification = ctx.currencySpecification,
+ fiat_currency = ctx.fiatCurrency!!,
+ fiat_currency_specification = ctx.fiatCurrencySpecification!!,
cashin_ratio = ctx.conversionInfo!!.cashin_ratio,
cashin_fee = ctx.conversionInfo.cashin_fee,
cashin_tiny_amount = ctx.conversionInfo.cashin_tiny_amount,
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
@@ -48,9 +48,9 @@ fun Routing.coreBankApi(db: Database, ctx: BankConfig) {
get("/config") {
call.respond(
Config(
- currency = ctx.currencySpecification,
- have_cashout = ctx.haveCashout,
- fiat_currency = ctx.fiatCurrency,
+ currency = ctx.currency,
+ currency_specification = ctx.currencySpecification,
+ allow_conversion = ctx.allowConversion,
allow_registrations = !ctx.restrictRegistration,
allow_deletions = !ctx.restrictAccountDeletion
)
@@ -438,7 +438,7 @@ private fun Routing.coreBankWithdrawalApi(db: Database, ctx: BankConfig) {
}
}
-private fun Routing.coreBankCashoutApi(db: Database, ctx: BankConfig) = conditional(ctx.haveCashout) {
+private fun Routing.coreBankCashoutApi(db: Database, ctx: BankConfig) = conditional(ctx.allowConversion) {
val TAN_RETRY_COUNTER: Int = 3;
val TAN_VALIDITY_PERIOD: Duration = Duration.ofHours(1)
val TAN_RETRANSMISSION_PERIOD: Duration = Duration.ofMinutes(1)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt
@@ -199,12 +199,11 @@ data class BearerToken(
val bankCustomer: Long
)
-// Type to return as GET /config response
@Serializable
data class Config(
- val currency: CurrencySpecification,
- val have_cashout: Boolean,
- val fiat_currency: String?,
+ val currency: String,
+ val currency_specification: CurrencySpecification,
+ val allow_conversion: Boolean,
val allow_registrations: Boolean,
val allow_deletions: Boolean
) {
@@ -214,8 +213,10 @@ data class Config(
@Serializable
data class ConversionConfig(
- val currency: String,
- val fiat_currency: String?,
+ val regional_currency: String,
+ val regional_currency_specification: CurrencySpecification,
+ val fiat_currency: String,
+ val fiat_currency_specification: CurrencySpecification,
val cashin_ratio: DecimalNumber,
val cashin_fee: TalerAmount,
val cashin_tiny_amount: TalerAmount,
@@ -231,6 +232,15 @@ data class ConversionConfig(
val version: String = "0:0:0"
}
+@Serializable
+data class TalerIntegrationConfigResponse(
+ val currency: String,
+ val currency_specification: CurrencySpecification,
+) {
+ val name: String = "taler-bank-integration";
+ val version: String = "0:0:0";
+}
+
enum class CreditDebitInfo {
credit, debit
}
@@ -327,24 +337,13 @@ data class BankAccountGetWithdrawalResponse(
val selected_exchange_account: String? = null
)
-// GET /config response from the Taler Integration API.
-@Serializable
-data class TalerIntegrationConfigResponse(
- val currency: String,
- val currency_specification: CurrencySpecification,
-) {
- val name: String = "taler-bank-integration";
- val version: String = "0:0:0";
-}
-
@Serializable
data class CurrencySpecification(
val name: String,
- val decimal_separator: String,
+ val code: String,
val num_fractional_input_digits: Int,
val num_fractional_normal_digits: Int,
val num_fractional_trailing_zero_digits: Int,
- val is_currency_name_leading: Boolean,
val alt_unit_names: Map<String, String>
)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApi.kt
@@ -37,7 +37,9 @@ private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.nexus")
fun Routing.wireGatewayApi(db: Database, ctx: BankConfig) {
get("/taler-wire-gateway/config") {
- call.respond(TWGConfigResponse(currency = ctx.currency))
+ call.respond(TWGConfigResponse(
+ currency = ctx.currency
+ ))
return@get
}
auth(db, TokenScope.readwrite) {
diff --git a/bank/src/test/kotlin/ConversionApiTest.kt b/bank/src/test/kotlin/ConversionApiTest.kt
@@ -43,12 +43,12 @@ class ConversionApiTest {
// 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)
+ assertEquals(TalerAmount("EUR:1.247"), it.amount_credit)
}
// Check conversion from
- client.get("/conversion-info/cashout-rate?amount_credit=FIAT:1.247").assertOkJson<ConversionResponse> {
+ client.get("/conversion-info/cashout-rate?amount_credit=EUR:1.247").assertOkJson<ConversionResponse> {
assertEquals(TalerAmount("KUDOS:1"), it.amount_debit)
- assertEquals(TalerAmount("FIAT:1.247"), it.amount_credit)
+ assertEquals(TalerAmount("EUR:1.247"), it.amount_credit)
}
// Too small
@@ -58,7 +58,7 @@ class ConversionApiTest {
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")
+ client.get("/conversion-info/cashout-rate?amount_debit=EUR:1&amount_credit=KUDOS:1")
.assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
// Wrong format
client.get("/conversion-info/cashout-rate?amount_debit=1")
@@ -66,7 +66,7 @@ class ConversionApiTest {
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")
+ client.get("/conversion-info/cashout-rate?amount_debit=EUR:1")
.assertBadRequest(TalerErrorCode.GENERIC_CURRENCY_MISMATCH)
client.get("/conversion-info/cashout-rate?amount_credit=KUDOS:1")
.assertBadRequest(TalerErrorCode.GENERIC_CURRENCY_MISMATCH)
@@ -79,14 +79,14 @@ class ConversionApiTest {
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> {
+ client.get("/conversion-info/cashin-rate?amount_debit=EUR:$amount").assertOkJson<ConversionResponse> {
assertEquals(TalerAmount("KUDOS:$converted"), it.amount_credit)
- assertEquals(TalerAmount("FIAT:$amount"), it.amount_debit)
+ assertEquals(TalerAmount("EUR:$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)
+ assertEquals(TalerAmount("EUR:$amount"), it.amount_debit)
}
}
@@ -94,7 +94,7 @@ class ConversionApiTest {
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")
+ client.get("/conversion-info/cashin-rate?amount_debit=KUDOS:1&amount_credit=EUR:1")
.assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
// Wrong format
client.get("/conversion-info/cashin-rate?amount_debit=1")
@@ -104,7 +104,7 @@ class ConversionApiTest {
// 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")
+ client.get("/conversion-info/cashin-rate?amount_credit=EUR:1")
.assertBadRequest(TalerErrorCode.GENERIC_CURRENCY_MISMATCH)
}
diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt
@@ -993,7 +993,7 @@ class CoreBankCashoutApiTest {
}.assertBadRequest(TalerErrorCode.GENERIC_CURRENCY_MISMATCH)
client.postA("/accounts/customer/cashouts") {
json(req) {
- "amount_credit" to "EUR:1"
+ "amount_credit" to "KUDOS:1"
}
}.assertBadRequest(TalerErrorCode.GENERIC_CURRENCY_MISMATCH)
}
@@ -1149,10 +1149,10 @@ class CoreBankCashoutApiTest {
cashin_fee = TalerAmount("KUDOS:0.1"),
cashin_tiny_amount = TalerAmount("KUDOS:0.0001"),
cashin_rounding_mode = RoundingMode.nearest,
- cashin_min_amount = TalerAmount("FIAT:0.0001"),
+ cashin_min_amount = TalerAmount("EUR:0.0001"),
cashout_ratio = DecimalNumber("1"),
- cashout_fee = TalerAmount("FIAT:0.1"),
- cashout_tiny_amount = TalerAmount("FIAT:0.0001"),
+ cashout_fee = TalerAmount("EUR:0.1"),
+ cashout_tiny_amount = TalerAmount("EUR:0.0001"),
cashout_rounding_mode = RoundingMode.nearest,
cashout_min_amount = TalerAmount("KUDOS:0.0001"),
))
diff --git a/bank/src/test/kotlin/StatsTest.kt b/bank/src/test/kotlin/StatsTest.kt
@@ -80,8 +80,8 @@ class StatsTest {
monitorTalerIn(0, "KUDOS:0")
monitorTalerOut(0, "KUDOS:0")
- monitorCashin(0, "KUDOS:0", "FIAT:0")
- monitorCashout(0, "KUDOS:0", "FIAT:0")
+ monitorCashin(0, "KUDOS:0", "EUR:0")
+ monitorCashout(0, "KUDOS:0", "EUR:0")
addIncoming("KUDOS:3")
monitorTalerIn(1, "KUDOS:3")
@@ -97,24 +97,24 @@ class StatsTest {
transfer("KUDOS:42")
monitorTalerOut(3, "KUDOS:82.5")
- cashin("FIAT:10")
- monitorCashin(1, "KUDOS:7.98", "FIAT:10")
- cashin("FIAT:20")
- monitorCashin(2, "KUDOS:23.96", "FIAT:30")
- cashin("FIAT:40")
- monitorCashin(3, "KUDOS:55.94", "FIAT:70")
+ cashin("EUR:10")
+ monitorCashin(1, "KUDOS:7.98", "EUR:10")
+ cashin("EUR:20")
+ monitorCashin(2, "KUDOS:23.96", "EUR:30")
+ cashin("EUR:40")
+ monitorCashin(3, "KUDOS:55.94", "EUR:70")
cashout("KUDOS:3")
- monitorCashout(1, "KUDOS:3", "FIAT:3.747")
+ monitorCashout(1, "KUDOS:3", "EUR:3.747")
cashout("KUDOS:7.6")
- monitorCashout(2, "KUDOS:10.6", "FIAT:13.244")
+ monitorCashout(2, "KUDOS:10.6", "EUR:13.244")
cashout("KUDOS:12.3")
- monitorCashout(3, "KUDOS:22.9", "FIAT:28.616")
+ monitorCashout(3, "KUDOS:22.9", "EUR:28.616")
monitorTalerIn(3, "KUDOS:22.9")
monitorTalerOut(3, "KUDOS:82.5")
- monitorCashin(3, "KUDOS:55.94", "FIAT:70")
- monitorCashout(3, "KUDOS:22.9", "FIAT:28.616")
+ monitorCashin(3, "KUDOS:55.94", "EUR:70")
+ monitorCashout(3, "KUDOS:22.9", "EUR:28.616")
}
@Test
diff --git a/contrib/currencies.conf b/contrib/currencies.conf
@@ -2,98 +2,79 @@
ENABLED = YES
name = "Euro"
code = "EUR"
-decimal_separator = ","
fractional_input_digits = 2
fractional_normal_digits = 2
fractional_trailing_zero_digits = 2
-is_currency_name_leading = NO
alt_unit_names = {"0":"€"}
[currency-swiss-francs]
ENABLED = YES
name = "Swiss Francs"
code = "CHF"
-decimal_separator = "."
fractional_input_digits = 2
fractional_normal_digits = 2
fractional_trailing_zero_digits = 2
-is_currency_name_leading = YES
alt_unit_names = {"0":"Fr.","-2":"Rp."}
[currency-forint]
ENABLED = NO
name = "Hungarian Forint"
code = "HUF"
-decimal_separator = ","
fractional_input_digits = 0
fractional_normal_digits = 0
fractional_trailing_zero_digits = 0
-is_currency_name_leading = NO
alt_unit_names = {"0":"Ft"}
[currency-us-dollar]
ENABLED = NO
name = "US Dollar"
code = "USD"
-decimal_separator = "."
fractional_input_digits = 2
fractional_normal_digits = 2
fractional_trailing_zero_digits = 2
-is_currency_name_leading = YES
alt_unit_names = {"0":"$"}
[currency-kudos]
ENABLED = YES
name = "Kudos (Taler Demonstrator)"
code = "KUDOS"
-decimal_separator = ","
fractional_input_digits = 2
fractional_normal_digits = 2
fractional_trailing_zero_digits = 2
-is_currency_name_leading = NO
alt_unit_names = {"0":"ク"}
[currency-testkudos]
ENABLED = YES
name = "Test-kudos (Taler Demonstrator)"
code = "TESTKUDOS"
-decimal_separator = "."
fractional_input_digits = 2
fractional_normal_digits = 2
fractional_trailing_zero_digits = 2
-is_currency_name_leading = NO
alt_unit_names = {"0":"テ","3":"kテ","-3":"mテ"}
[currency-japanese-yen]
ENABLED = NO
name = "Japanese Yen"
code = "JPY"
-decimal_separator = "."
fractional_input_digits = 2
fractional_normal_digits = 0
fractional_trailing_zero_digits = 2
-is_currency_name_leading = YES
alt_unit_names = {"0":"¥"}
[currency-bitcoin-mainnet]
ENABLED = NO
name = "Bitcoin (Mainnet)"
code = "BITCOINBTC"
-decimal_separator = "."
fractional_input_digits = 8
fractional_normal_digits = 3
fractional_trailing_zero_digits = 0
-is_currency_name_leading = NO
alt_unit_names = {"0":"BTC","-3":"mBTC"}
[currency-ethereum]
ENABLED = NO
name = "WAI-ETHER (Ethereum)"
code = "EthereumWAI"
-decimal_separator = "."
fractional_input_digits = 0
fractional_normal_digits = 0
fractional_trailing_zero_digits = 0
-is_currency_name_leading = NO
alt_unit_names = {"0":"WAI","3":"KWAI","6":"MWAI","9":"GWAI","12":"Szabo","15":"Finney","18":"Ether","21":"KEther","24":"MEther"}
-