commit 2bc6907cb1f3b1bb6b207e85294bfa57c28bb2e9
parent 41628115dcac19c5f1532fdafb06e361a409fa8a
Author: Antoine A <>
Date: Fri, 10 Nov 2023 11:35:18 +0000
Improve conversion endpoints errors and use internal and external names everywhere
Diffstat:
14 files changed, 60 insertions(+), 40 deletions(-)
diff --git a/bank/conf/test.conf b/bank/conf/test.conf
@@ -5,7 +5,7 @@ DEFAULT_ADMIN_DEBT_LIMIT = KUDOS:10000
REGISTRATION_BONUS_ENABLED = NO
SUGGESTED_WITHDRAWAL_EXCHANGE = https://exchange.example.com
have_cashout = YES
-fiat_currency = FIAT
+external_currency = FIAT
tan_sms = libeufin-tan-file.sh
tan_email = libeufin-tan-fail.sh
diff --git a/bank/conf/test_no_tan.conf b/bank/conf/test_no_tan.conf
@@ -5,7 +5,7 @@ DEFAULT_ADMIN_DEBT_LIMIT = KUDOS:10000
REGISTRATION_BONUS_ENABLED = NO
SUGGESTED_WITHDRAWAL_EXCHANGE = https://exchange.example.com
have_cashout = YES
-fiat_currency = FIAT
+external_currency = FIAT
[libeufin-bankdb-postgres]
SQL_DIR = $DATADIR/sql/
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt
@@ -78,7 +78,7 @@ data class BankConfig(
*/
val spaCaptchaURL: String?,
val haveCashout: Boolean,
- val fiatCurrency: String?,
+ val externalCurrency: String?,
val conversionInfo: ConversionInfo?,
val tanSms: String?,
val tanEmail: String?,
@@ -128,12 +128,12 @@ fun TalerConfig.loadBankConfig(): BankConfig = catchError {
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 externalCurrency: String? = null;
var conversionInfo: ConversionInfo? = null;
val haveCashout = lookupBoolean("libeufin-bank", "have_cashout") ?: false;
if (haveCashout) {
- fiatCurrency = requireString("libeufin-bank", "fiat_currency");
- conversionInfo = loadConversionInfo(currency, fiatCurrency)
+ externalCurrency = requireString("libeufin-bank", "external_currency");
+ conversionInfo = loadConversionInfo(currency, externalCurrency)
}
BankConfig(
currency = currency,
@@ -147,23 +147,23 @@ fun TalerConfig.loadBankConfig(): BankConfig = catchError {
restrictAccountDeletion = lookupBoolean("libeufin-bank", "restrict_account_deletion") ?: true,
currencySpecification = currencySpecification,
haveCashout = haveCashout,
- fiatCurrency = fiatCurrency,
+ externalCurrency = externalCurrency,
conversionInfo = conversionInfo,
tanSms = lookupPath("libeufin-bank", "tan_sms"),
tanEmail = lookupPath("libeufin-bank", "tan_email"),
)
}
-private fun TalerConfig.loadConversionInfo(currency: String, fiatCurrency: String): ConversionInfo = catchError {
+private fun TalerConfig.loadConversionInfo(currency: String, externalCurrency: String): ConversionInfo = catchError {
ConversionInfo(
buy_ratio = requireDecimalNumber("libeufin-bank-conversion", "buy_ratio"),
buy_fee = requireDecimalNumber("libeufin-bank-conversion", "buy_fee"),
buy_tiny_amount = amount("libeufin-bank-conversion", "buy_tiny_amount", currency) ?: TalerAmount(0, 1, currency),
buy_rounding_mode = RoundingMode("libeufin-bank-conversion", "buy_rounding_mode") ?: RoundingMode.zero,
- buy_min_amount = amount("libeufin-bank-conversion", "buy_min_amount", fiatCurrency) ?: TalerAmount(0, 0, fiatCurrency),
+ buy_min_amount = amount("libeufin-bank-conversion", "buy_min_amount", externalCurrency) ?: TalerAmount(0, 0, externalCurrency),
sell_ratio = requireDecimalNumber("libeufin-bank-conversion", "sell_ratio"),
sell_fee = requireDecimalNumber("libeufin-bank-conversion", "sell_fee"),
- sell_tiny_amount = amount("libeufin-bank-conversion", "sell_tiny_amount", fiatCurrency) ?: TalerAmount(0, 1, fiatCurrency),
+ sell_tiny_amount = amount("libeufin-bank-conversion", "sell_tiny_amount", externalCurrency) ?: TalerAmount(0, 1, externalCurrency),
sell_rounding_mode = RoundingMode("libeufin-bank-conversion", "sell_rounding_mode") ?: RoundingMode.zero,
sell_min_amount = amount("libeufin-bank-conversion", "sell_min_amount", currency) ?: TalerAmount(0, 0, currency),
)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
@@ -47,7 +47,7 @@ fun Routing.coreBankApi(db: Database, ctx: BankConfig) {
Config(
currency = ctx.currencySpecification,
have_cashout = ctx.haveCashout,
- fiat_currency = ctx.fiatCurrency,
+ external_currency = ctx.externalCurrency,
conversion_info = ctx.conversionInfo,
allow_registrations = !ctx.restrictRegistration,
allow_deletions = !ctx.restrictAccountDeletion
@@ -476,7 +476,7 @@ private fun Routing.coreBankCashoutApi(db: Database, ctx: BankConfig) {
val req = call.receive<CashoutRequest>()
ctx.checkInternalCurrency(req.amount_debit)
- ctx.checkFiatCurrency(req.amount_credit)
+ ctx.checkexternalCurrency(req.amount_credit)
val tanChannel = req.tan_channel ?: TanChannel.sms
val tanScript = when (tanChannel) {
@@ -639,10 +639,10 @@ private fun Routing.coreBankCashoutApi(db: Database, ctx: BankConfig) {
val params = RateParams.extract(call.request.queryParameters)
params.debit?.let { ctx.checkInternalCurrency(it) }
- params.credit?.let { ctx.checkFiatCurrency(it) }
+ params.credit?.let { ctx.checkexternalCurrency(it) }
if (params.debit != null) {
- val credit = db.conversion.internalToFiat(params.debit) ?:
+ val credit = db.conversion.internalToExternal(params.debit) ?:
throw conflict(
"${params.debit} is too small to be converted",
TalerErrorCode.BANK_BAD_CONVERSION
@@ -655,11 +655,11 @@ private fun Routing.coreBankCashoutApi(db: Database, ctx: BankConfig) {
get("/cashin-rate") {
val params = RateParams.extract(call.request.queryParameters)
- params.debit?.let { ctx.checkFiatCurrency(it) }
+ params.debit?.let { ctx.checkexternalCurrency(it) }
params.credit?.let { ctx.checkInternalCurrency(it) }
if (params.debit != null) {
- val credit = db.conversion.fiatToInternal(params.debit) ?:
+ val credit = db.conversion.externalToInternal(params.debit) ?:
throw conflict(
"${params.debit} is too small to be converted",
TalerErrorCode.BANK_BAD_CONVERSION
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Error.kt b/bank/src/main/kotlin/tech/libeufin/bank/Error.kt
@@ -91,9 +91,9 @@ fun BankConfig.checkInternalCurrency(amount: TalerAmount) {
)
}
-fun BankConfig.checkFiatCurrency(amount: TalerAmount) {
- if (amount.currency != fiatCurrency) throw badRequest(
- "Wrong currency: expected fiat currency $fiatCurrency got ${amount.currency}",
+fun BankConfig.checkexternalCurrency(amount: TalerAmount) {
+ if (amount.currency != externalCurrency) throw badRequest(
+ "Wrong currency: expected external currency $externalCurrency got ${amount.currency}",
TalerErrorCode.GENERIC_CURRENCY_MISMATCH
)
}
\ No newline at end of file
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -303,7 +303,7 @@ class ServeBank : CliktCommand("Run libeufin-bank HTTP server", name = "serve")
logger.info("Can only serve libeufin-bank via TCP")
exitProcess(1)
}
- val db = Database(dbCfg.dbConnStr, ctx.currency, ctx.fiatCurrency)
+ val db = Database(dbCfg.dbConnStr, ctx.currency, ctx.externalCurrency)
runBlocking {
if (!maybeCreateAdminAccount(db, ctx)) // logs provided by the helper
exitProcess(1)
@@ -326,7 +326,7 @@ class ChangePw : CliktCommand("Change account password", name = "passwd") {
val cfg = talerConfig(configFile)
val ctx = cfg.loadBankConfig()
val dbCfg = cfg.loadDbConfig()
- val db = Database(dbCfg.dbConnStr, ctx.currency, ctx.fiatCurrency)
+ val db = Database(dbCfg.dbConnStr, ctx.currency, ctx.externalCurrency)
runBlocking {
if (!maybeCreateAdminAccount(db, ctx)) // logs provided by the helper
exitProcess(1)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Params.kt b/bank/src/main/kotlin/tech/libeufin/bank/Params.kt
@@ -36,21 +36,21 @@ import java.time.temporal.*
import java.util.*
fun Parameters.expect(name: String): String
- = get(name) ?: throw badRequest("Missing '$name' parameter")
+ = get(name) ?: throw badRequest("Missing '$name' parameter", TalerErrorCode.GENERIC_PARAMETER_MISSING)
fun Parameters.int(name: String): Int?
- = get(name)?.run { toIntOrNull() ?: throw badRequest("Param 'which' not a number") }
+ = get(name)?.run { toIntOrNull() ?: throw badRequest("Param 'which' not a number", TalerErrorCode.GENERIC_PARAMETER_MALFORMED) }
fun Parameters.expectInt(name: String): Int
- = int(name) ?: throw badRequest("Missing '$name' number parameter")
+ = int(name) ?: throw badRequest("Missing '$name' number parameter", TalerErrorCode.GENERIC_PARAMETER_MISSING)
fun Parameters.long(name: String): Long?
- = get(name)?.run { toLongOrNull() ?: throw badRequest("Param 'which' not a number") }
+ = get(name)?.run { toLongOrNull() ?: throw badRequest("Param 'which' not a number", TalerErrorCode.GENERIC_PARAMETER_MALFORMED) }
fun Parameters.expectLong(name: String): Long
- = long(name) ?: throw badRequest("Missing '$name' number parameter")
+ = long(name) ?: throw badRequest("Missing '$name' number parameter", TalerErrorCode.GENERIC_PARAMETER_MISSING)
fun Parameters.amount(name: String): TalerAmount?
= get(name)?.run {
try {
TalerAmount(this)
} catch (e: Exception) {
- throw badRequest("Param '$name' not a taler amount")
+ throw badRequest("Param '$name' not a taler amount", TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
}
}
@@ -114,8 +114,10 @@ data class RateParams(
val debit = params.amount("amount_debit")
val credit = params.amount("amount_credit")
if (debit == null && credit == null) {
- throw badRequest("Either param 'amount_debit' or 'amount_credit' is required")
- }
+ throw badRequest("Either param 'amount_debit' or 'amount_credit' is required", TalerErrorCode.GENERIC_PARAMETER_MISSING)
+ } else if (debit != null && credit != null) {
+ throw badRequest("Cannot have both 'amount_debit' and 'amount_credit' params", TalerErrorCode.GENERIC_PARAMETER_MALFORMED)
+ }
return RateParams(debit, credit)
}
}
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt
@@ -254,7 +254,7 @@ data class TalerWithdrawalOperation(
data class Config(
val currency: CurrencySpecification,
val have_cashout: Boolean,
- val fiat_currency: String?,
+ val external_currency: String?,
val conversion_info: ConversionInfo?,
val allow_registrations: Boolean,
val allow_deletions: Boolean
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/CashoutDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/CashoutDAO.kt
@@ -234,7 +234,7 @@ class CashoutDAO(private val db: Database) {
amount_credit = TalerAmount(
value = it.getLong("amount_credit_val"),
frac = it.getInt("amount_credit_frac"),
- db.fiatCurrency!!
+ db.externalCurrency!!
),
subject = it.getString("subject"),
creation_time = TalerProtocolTimestamp(
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/ConversionDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/ConversionDAO.kt
@@ -82,6 +82,6 @@ class ConversionDAO(private val db: Database) {
}
}
- suspend fun internalToFiat(amount: TalerAmount): TalerAmount? = conversion(amount, "sell", db.fiatCurrency!!)
- suspend fun fiatToInternal(amount: TalerAmount): TalerAmount? = conversion(amount, "buy", db.bankCurrency)
+ suspend fun internalToExternal(amount: TalerAmount): TalerAmount? = conversion(amount, "sell", db.externalCurrency!!)
+ suspend fun externalToInternal(amount: TalerAmount): TalerAmount? = conversion(amount, "buy", db.bankCurrency)
}
\ No newline at end of file
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/Database.kt
@@ -55,7 +55,7 @@ private val logger: Logger = LoggerFactory.getLogger("tech.libeufin.bank.Databas
internal fun faultyTimestampByBank() = internalServerError("Bank took overflowing timestamp")
internal fun faultyDurationByClient() = badRequest("Overflowing duration, please specify 'forever' instead.")
-class Database(dbConfig: String, internal val bankCurrency: String, internal val fiatCurrency: String?): java.io.Closeable {
+class Database(dbConfig: String, internal val bankCurrency: String, internal val externalCurrency: String?): java.io.Closeable {
val dbPool: HikariDataSource
internal val notifWatcher: NotificationWatcher
@@ -871,7 +871,7 @@ class Database(dbConfig: String, internal val bankCurrency: String, internal val
stmt.setNull(2, java.sql.Types.INTEGER)
}
stmt.oneOrNull {
- fiatCurrency?.run {
+ externalCurrency?.run {
MonitorWithConversion(
cashinCount = it.getLong("cashin_count"),
cashinInternalVolume = TalerAmount(
diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt
@@ -1506,11 +1506,20 @@ class CoreBankCashoutApiTest {
assertEquals(TalerAmount("FIAT:1.247"), resp.amount_credit)
}
- // No amount
- client.get("/cashout-rate").assertBadRequest()
// 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)
@@ -1532,7 +1541,16 @@ class CoreBankCashoutApiTest {
}
// No amount
- client.get("/cashin-rate").assertBadRequest()
+ 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)
diff --git a/bank/src/test/kotlin/helpers.kt b/bank/src/test/kotlin/helpers.kt
@@ -25,7 +25,7 @@ fun setup(
resetDatabaseTables(dbCfg, "libeufin-bank")
initializeDatabaseTables(dbCfg, "libeufin-bank")
val ctx = config.loadBankConfig()
- Database(dbCfg.dbConnStr, ctx.currency, ctx.fiatCurrency).use {
+ Database(dbCfg.dbConnStr, ctx.currency, ctx.externalCurrency).use {
runBlocking {
ctx.conversionInfo?.run { it.conversion.updateConfig(this) }
lambda(it, ctx)
diff --git a/database-versioning/libeufin-bank-0001.sql b/database-versioning/libeufin-bank-0001.sql
@@ -69,7 +69,7 @@ CREATE TABLE IF NOT EXISTS customers
);
COMMENT ON COLUMN customers.cashout_payto
- IS 'RFC 8905 payto URI to collect fiat payments that come from the conversion of regional currency cash-out operations.';
+ IS 'RFC 8905 payto URI to collect external payments that come from the conversion of internal currency cash-out operations.';
COMMENT ON COLUMN customers.name
IS 'Full name of the customer.';