libeufin

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

commit e4f20549847d1477c083dd9335f9ffc35b65be74
parent 9d15c7304e3eaea5c98373c8ed465c824203e56d
Author: Antoine A <>
Date:   Fri, 17 Nov 2023 16:53:02 +0000

Apply new spec

Diffstat:
Mbank/conf/test.conf | 6+++---
Mbank/conf/test_no_tan.conf | 6+++---
Mbank/src/main/kotlin/tech/libeufin/bank/Config.kt | 26++++++++++++++++----------
Mbank/src/main/kotlin/tech/libeufin/bank/ConversionApi.kt | 8+++++---
Mbank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt | 8++++----
Mbank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt | 35+++++++++++++++++------------------
Mbank/src/main/kotlin/tech/libeufin/bank/WireGatewayApi.kt | 4+++-
Mbank/src/test/kotlin/ConversionApiTest.kt | 20++++++++++----------
Mbank/src/test/kotlin/CoreBankApiTest.kt | 8++++----
Mbank/src/test/kotlin/StatsTest.kt | 26+++++++++++++-------------
Mcontrib/currencies.conf | 19-------------------
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"} -