commit bc085ebfbe87c4028e9f1257605872c53e213b15
parent 6b27dd42075a24591aed00d34c67b23b26e9f65a
Author: Antoine A <>
Date: Tue, 22 Jul 2025 12:24:51 +0200
common: use default conversion rate with 0 ratio and 0.01 tiny amount
Diffstat:
10 files changed, 93 insertions(+), 103 deletions(-)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt
@@ -81,7 +81,24 @@ data class ConversionRate (
val cashout_tiny_amount: TalerAmount,
val cashout_rounding_mode: RoundingMode,
val cashout_min_amount: TalerAmount,
-)
+) {
+ fun check(cfg: BankConfig) {
+ for (regionalAmount in sequenceOf(cashin_fee, cashin_tiny_amount, cashout_min_amount)) {
+ cfg.checkRegionalCurrency(regionalAmount)
+ }
+ for (fiatAmount in sequenceOf(cashout_fee, cashout_tiny_amount, cashin_min_amount)) {
+ cfg.checkFiatCurrency(fiatAmount)
+ }
+
+ if (cashout_tiny_amount.isZero()) {
+ throw badRequest("cashout_tiny_amount must be > 0")
+ } else if (cashin_tiny_amount.isZero()) {
+ throw badRequest("cashin_tiny_amount must be > 0")
+ } else if (cashout_tiny_amount.isSubCent()) {
+ throw badRequest("Sub-cent amounts no supported by cashout, cashout_tiny_amount must be >= 0.01")
+ }
+ }
+}
/** Load bank config at [configPath] */
fun bankConfig(configPath: Path?): BankConfig = BANK_CONFIG_SOURCE.fromFile(configPath).loadBankConfig()
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt
@@ -342,8 +342,6 @@ data class TokenInfos (
data class Config(
val currency: String,
val currency_specification: CurrencySpecification,
- val fiat_currency: String? = null,
- val fiat_currency_specification: CurrencySpecification? = null,
val base_url: BaseURL?,
val bank_name: String,
val allow_conversion: Boolean,
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/api/ConversionApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/api/ConversionApi.kt
@@ -31,34 +31,19 @@ import tech.libeufin.bank.db.Database
import tech.libeufin.common.*
private suspend fun ApplicationCall.config(db: Database, cfg: BankConfig) {
- val rate = db.conversion.getDefaultRate()
- ?: throw apiError(
- HttpStatusCode.NotImplemented,
- "conversion rate not configured yet",
- TalerErrorCode.END
- )
respond(
ConversionConfig(
regional_currency = cfg.regionalCurrency,
regional_currency_specification = cfg.regionalCurrencySpec,
fiat_currency = cfg.fiatCurrency!!,
fiat_currency_specification = cfg.fiatCurrencySpec!!,
- conversion_rate = rate
+ conversion_rate = db.conversion.getDefaultRate()
)
)
}
private suspend fun ApplicationCall.setGlobal(db: Database, cfg: BankConfig) {
val req = receive<ConversionRate>()
- for (regionalAmount in sequenceOf(req.cashin_fee, req.cashin_tiny_amount, req.cashout_min_amount)) {
- cfg.checkRegionalCurrency(regionalAmount)
- }
- for (fiatAmount in sequenceOf(req.cashout_fee, req.cashout_tiny_amount, req.cashin_min_amount)) {
- cfg.checkFiatCurrency(fiatAmount)
- }
- // cashout conversion tiny amount must >= 0.01 as it is what EBICS supports
- if (req.cashout_tiny_amount.isSubCent()) {
- throw badRequest("Sub-cent amounts no supported by cashout, cashout_tiny_amount must be >= 0.01")
- }
+ req.check(cfg)
db.conversion.updateConfig(req)
respond(HttpStatusCode.NoContent)
}
@@ -82,11 +67,6 @@ fun Routing.conversionApi(db: Database, cfg: BankConfig) = conditional(cfg.allow
"$input is too small to be converted",
TalerErrorCode.BANK_BAD_CONVERSION
)
- ConversionResult.MissingConfig -> throw apiError(
- HttpStatusCode.NotImplemented,
- "conversion rate not configured yet",
- TalerErrorCode.END
- )
ConversionResult.IsExchange -> throw conflict(
"exchange accounts cannot cashout",
TalerErrorCode.BANK_ACCOUNT_IS_EXCHANGE
@@ -98,13 +78,7 @@ fun Routing.conversionApi(db: Database, cfg: BankConfig) = conditional(cfg.allow
}
}
get("/conversion-info/rate") {
- val rate = db.conversion.getDefaultRate()
- ?: throw apiError(
- HttpStatusCode.NotImplemented,
- "conversion rate not configured yet",
- TalerErrorCode.END
- )
- call.respond(rate)
+ call.respond(db.conversion.getDefaultRate())
}
get("/conversion-info/cashout-rate") {
val params = RateParams.extract(call.request.queryParameters)
@@ -142,11 +116,6 @@ fun Routing.conversionApi(db: Database, cfg: BankConfig) = conditional(cfg.allow
get("/conversion-rate-classes/{CLASS_ID}/conversion-info/rate") {
val id = call.longPath("CLASS_ID")
val rate = db.conversion.getClassRate(id)
- ?: throw apiError(
- HttpStatusCode.NotImplemented,
- "conversion rate not configured yet",
- TalerErrorCode.END
- )
call.respond(rate)
}
get("/conversion-rate-classes/{CLASS_ID}/conversion-info/cashout-rate") {
@@ -203,11 +172,6 @@ fun Routing.conversionApi(db: Database, cfg: BankConfig) = conditional(cfg.allow
optAuth(db, cfg.pwCrypto, TokenLogicalScope.readonly, cfg.basicAuthCompat, allowAdmin = true) {
get("/accounts/{USERNAME}/conversion-info/rate") {
val (isExchange, rate) = db.conversion.getUserRate(call.username)
- ?: throw apiError(
- HttpStatusCode.NotImplemented,
- "conversion rate not configured yet",
- TalerErrorCode.END
- )
if (!isExchange && !call.isAuthenticated) {
throw forbidden("Non exchange account rates are private")
}
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt
@@ -58,8 +58,6 @@ fun Routing.coreBankApi(db: Database, cfg: BankConfig) {
base_url = cfg.baseUrl,
currency = cfg.regionalCurrency,
currency_specification = cfg.regionalCurrencySpec,
- fiat_currency = cfg.fiatCurrency,
- fiat_currency_specification = cfg.fiatCurrencySpec,
allow_conversion = cfg.allowConversion,
allow_registrations = cfg.allowRegistration,
allow_deletions = cfg.allowAccountDeletion,
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt
@@ -561,7 +561,7 @@ class AccountDAO(private val db: Database) {
cashout_rounding_mode
FROM customers
JOIN bank_accounts ON customer_id=owning_customer_id
- LEFT JOIN LATERAL get_conversion_class_rate(conversion_rate_class_id) on true
+ CROSS JOIN LATERAL get_conversion_class_rate(conversion_rate_class_id)
WHERE username=?
"""
) {
@@ -679,7 +679,7 @@ class AccountDAO(private val db: Database) {
cashout_rounding_mode
FROM bank_accounts
JOIN customers ON owning_customer_id = customer_id
- LEFT JOIN LATERAL get_conversion_class_rate(conversion_rate_class_id) on true
+ CROSS JOIN LATERAL get_conversion_class_rate(conversion_rate_class_id)
WHERE
${if (params.usernameFilter != null) "name ILIKE ? AND" else ""}
${when (params.conversionRateClassId) {
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/ConversionDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/ConversionDAO.kt
@@ -29,8 +29,7 @@ import org.postgresql.util.PSQLState
class ConversionDAO(private val db: Database) {
companion object {
fun userRate(db: Database, it: ResultSet, username: String, isTalerExchange: Boolean): ConversionRate? {
- val hasRate = it.getObject("cashin_rounding_mode") != null
- return if (!hasRate || db.fiatCurrency == null) {
+ return if (db.fiatCurrency == null) {
null
} else if (username == "admin") {
ConversionRate(
@@ -38,7 +37,7 @@ class ConversionDAO(private val db: Database) {
cashin_fee = TalerAmount.zero(db.bankCurrency),
cashin_tiny_amount = TalerAmount.zero(db.bankCurrency),
cashin_rounding_mode = RoundingMode.zero,
- cashin_min_amount = TalerAmount.zero(db.fiatCurrency!!),
+ cashin_min_amount = TalerAmount.zero(db.fiatCurrency),
cashout_ratio = DecimalNumber.ZERO,
cashout_fee = TalerAmount.zero(db.fiatCurrency),
cashout_tiny_amount = TalerAmount.zero(db.fiatCurrency),
@@ -51,7 +50,7 @@ class ConversionDAO(private val db: Database) {
cashin_fee = it.getAmount("cashin_fee", db.bankCurrency),
cashin_tiny_amount = it.getAmount("cashin_tiny_amount", db.bankCurrency),
cashin_rounding_mode = it.getEnum("cashin_rounding_mode"),
- cashin_min_amount = it.getAmount("cashin_min_amount", db.fiatCurrency!!),
+ cashin_min_amount = it.getAmount("cashin_min_amount", db.fiatCurrency),
cashout_ratio = DecimalNumber.ZERO,
cashout_fee = TalerAmount.zero(db.fiatCurrency),
cashout_tiny_amount = TalerAmount.zero(db.fiatCurrency),
@@ -64,7 +63,7 @@ class ConversionDAO(private val db: Database) {
cashin_fee = TalerAmount.zero(db.bankCurrency),
cashin_tiny_amount = TalerAmount.zero(db.bankCurrency),
cashin_rounding_mode = RoundingMode.zero,
- cashin_min_amount = TalerAmount.zero(db.fiatCurrency!!),
+ cashin_min_amount = TalerAmount.zero(db.fiatCurrency),
cashout_ratio = it.getDecimal("cashout_ratio"),
cashout_fee = it.getAmount("cashout_fee", db.fiatCurrency),
cashout_tiny_amount = it.getAmount("cashout_tiny_amount", db.fiatCurrency),
@@ -96,7 +95,7 @@ class ConversionDAO(private val db: Database) {
}
/** Get default conversion rate */
- suspend fun getDefaultRate(): ConversionRate? = db.serializable("""
+ suspend fun getDefaultRate(): ConversionRate = db.serializable("""
SELECT
(cashin_ratio).val as cashin_ratio_val, (cashin_ratio).frac as cashin_ratio_frac,
(cashin_fee).val as cashin_fee_val, (cashin_fee).frac as cashin_fee_frac,
@@ -110,7 +109,7 @@ class ConversionDAO(private val db: Database) {
cashout_rounding_mode
FROM config_get_conversion_rate()
""") {
- oneOrNull {
+ one {
ConversionRate(
cashin_ratio = it.getDecimal("cashin_ratio"),
cashin_fee = it.getAmount("cashin_fee", db.bankCurrency),
@@ -127,7 +126,7 @@ class ConversionDAO(private val db: Database) {
}
/** Get conversion class rate */
- suspend fun getClassRate(conversionRateClassId: Long): ConversionRate? = db.serializable("""
+ suspend fun getClassRate(conversionRateClassId: Long): ConversionRate = db.serializable("""
SELECT
(cashin_ratio).val as cashin_ratio_val, (cashin_ratio).frac as cashin_ratio_frac,
(cashin_fee).val as cashin_fee_val, (cashin_fee).frac as cashin_fee_frac,
@@ -142,7 +141,7 @@ class ConversionDAO(private val db: Database) {
FROM get_conversion_class_rate(?)
""") {
bind(conversionRateClassId)
- oneOrNull {
+ one {
ConversionRate(
cashin_ratio = it.getDecimal("cashin_ratio"),
cashin_fee = it.getAmount("cashin_fee", db.bankCurrency),
@@ -159,7 +158,7 @@ class ConversionDAO(private val db: Database) {
}
/** Get user rate */
- suspend fun getUserRate(username: String): Pair<Boolean, ConversionRate>? = db.serializable("""
+ suspend fun getUserRate(username: String): Pair<Boolean, ConversionRate> = db.serializable("""
SELECT
(cashin_ratio).val as cashin_ratio_val, (cashin_ratio).frac as cashin_ratio_frac,
(cashin_fee).val as cashin_fee_val, (cashin_fee).frac as cashin_fee_frac,
@@ -178,7 +177,7 @@ class ConversionDAO(private val db: Database) {
WHERE username=?
""") {
bind(username)
- oneOrNull {
+ one {
val isTalerExchange = it.getBoolean("is_taler_exchange")
val rate = ConversionDAO.userRate(db, it, username,isTalerExchange)!!
Pair(isTalerExchange, rate)
@@ -196,21 +195,19 @@ class ConversionDAO(private val db: Database) {
sealed interface ConversionResult {
data class Success(val converted: TalerAmount): ConversionResult
data object ToSmall: ConversionResult
- data object MissingConfig: ConversionResult
data object IsExchange: ConversionResult
data object NotExchange: ConversionResult
}
/** Perform [direction] conversion of [amount] using in-db [function] */
private suspend fun conversion(amount: TalerAmount, function: String, direction: String, conversionRateClassId: Long?): ConversionResult = db.serializable(
- "SELECT too_small, no_config, (converted).val AS amount_val, (converted).frac AS amount_frac FROM conversion_$function((?, ?)::taler_amount, ?, ?)"
+ "SELECT too_small, (converted).val AS amount_val, (converted).frac AS amount_frac FROM conversion_$function((?, ?)::taler_amount, ?, ?)"
) {
bind(amount)
bind(direction)
bind(conversionRateClassId)
one {
when {
- it.getBoolean("no_config") -> ConversionResult.MissingConfig
it.getBoolean("too_small") -> ConversionResult.ToSmall
else -> ConversionResult.Success(
it.getAmount("amount", if (amount.currency == db.bankCurrency) db.fiatCurrency!! else db.bankCurrency)
@@ -223,7 +220,6 @@ class ConversionDAO(private val db: Database) {
SELECT
is_taler_exchange,
too_small,
- no_config,
(converted).val AS amount_val,
(converted).frac AS amount_frac
FROM bank_accounts
@@ -238,7 +234,6 @@ class ConversionDAO(private val db: Database) {
one {
val isExchange = it.getBoolean("is_taler_exchange")
when {
- it.getBoolean("no_config") -> ConversionResult.MissingConfig
direction == "cashout" && isExchange -> ConversionResult.IsExchange
direction == "cashin" && !isExchange -> ConversionResult.NotExchange
it.getBoolean("too_small") -> ConversionResult.ToSmall
diff --git a/bank/src/test/kotlin/ConversionApiTest.kt b/bank/src/test/kotlin/ConversionApiTest.kt
@@ -29,9 +29,9 @@ class ConversionApiTest {
// GET /conversion-info/config
@Test
fun config() = bankSetup {
- client.get("/conversion-info/config").assertOk()
- client.get("/conversion-rate-classes/1/conversion-info/config").assertOk()
- client.get("/accounts/merchant/conversion-info/config").assertOk()
+ client.get("/conversion-info/config").assertOkJson<ConversionConfig>()
+ client.get("/conversion-rate-classes/1/conversion-info/config").assertOkJson<ConversionConfig>()
+ client.get("/accounts/merchant/conversion-info/config").assertOkJson<ConversionConfig>()
}
// POST /conversion-info/conversion-rate
@@ -63,6 +63,17 @@ class ConversionApiTest {
"cashout_fee" to "CHF:0.003"
}
}.assertBadRequest(TalerErrorCode.GENERIC_CURRENCY_MISMATCH)
+ // Zero tiny amount
+ client.postAdmin("$prefix/conversion-info/conversion-rate") {
+ json(ok) {
+ "cashout_tiny_amount" to "EUR:0"
+ }
+ }.assertBadRequest(TalerErrorCode.GENERIC_JSON_INVALID)
+ client.postAdmin("$prefix/conversion-info/conversion-rate") {
+ json(ok) {
+ "cashin_tiny_amount" to "KUDOS:0"
+ }
+ }.assertBadRequest(TalerErrorCode.GENERIC_JSON_INVALID)
// Subcent cashout tiny amount
client.postAdmin("$prefix/conversion-info/conversion-rate") {
json(ok) {
@@ -172,15 +183,11 @@ class ConversionApiTest {
}
for (prefix in sequenceOf("", "/conversion-rate-classes/1", "/accounts/merchant")) {
client.getAdmin("$prefix/conversion-info/config")
- .assertNotImplemented()
- client.getAdmin("$prefix/conversion-info/cashin-rate")
- .assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MISSING)
+ .assertOkJson<ConversionConfig>()
client.getAdmin("$prefix/conversion-info/cashout-rate")
.assertBadRequest(TalerErrorCode.GENERIC_PARAMETER_MISSING)
- client.getAdmin("$prefix/conversion-info/cashin-rate?amount_credit=KUDOS:1")
- .assertNotImplemented()
client.getAdmin("$prefix/conversion-info/cashout-rate?amount_credit=EUR:1")
- .assertNotImplemented()
+ .assertConflict(TalerErrorCode.BANK_BAD_CONVERSION)
}
}
diff --git a/database-versioning/libeufin-bank-procedures.sql b/database-versioning/libeufin-bank-procedures.sql
@@ -1268,7 +1268,6 @@ CREATE FUNCTION cashin(
-- Error status
OUT out_no_account BOOLEAN,
OUT out_too_small BOOLEAN,
- OUT out_no_config BOOLEAN,
OUT out_balance_insufficient BOOLEAN
)
LANGUAGE plpgsql AS $$
@@ -1302,10 +1301,10 @@ SELECT bank_account_id
WHERE username = 'admin';
-- Perform conversion
-SELECT (converted).val, (converted).frac, too_small, no_config
- INTO converted_amount.val, converted_amount.frac, out_too_small, out_no_config
+SELECT (converted).val, (converted).frac, too_small
+ INTO converted_amount.val, converted_amount.frac, out_too_small
FROM conversion_to(in_amount, 'cashin'::text, exchange_conversion_rate_class_id);
-IF out_too_small OR out_no_config THEN
+IF out_too_small THEN
RETURN;
END IF;
@@ -1386,7 +1385,7 @@ ELSIF out_account_is_exchange OR out_no_cashout_payto THEN
END IF;
-- check conversion
-SELECT under_min, too_small OR no_config OR in_amount_credit!=converted
+SELECT under_min, too_small OR in_amount_credit!=converted
INTO out_under_min, out_bad_conversion
FROM conversion_to(in_amount_debit, 'cashout'::text, account_conversion_rate_class_id);
IF out_bad_conversion THEN
@@ -1803,6 +1802,12 @@ DECLARE
amount_numeric NUMERIC(33, 8); -- 16 digit for val, 8 for frac and 1 for rounding error
tiny_numeric NUMERIC(24);
BEGIN
+ -- Handle no config case
+ IF ratio = (0, 0)::taler_amount THEN
+ out_too_small=TRUE;
+ RETURN;
+ END IF;
+
-- Perform multiplication using big numbers
amount_numeric = (amount.val::numeric(24) * 100000000 + amount.frac::numeric(24)) * (ratio.val::numeric(24, 8) + ratio.frac::numeric(24, 8) / 100000000);
@@ -1848,6 +1853,12 @@ DECLARE
tiny_numeric NUMERIC(24);
roundtrip BOOLEAN;
BEGIN
+ -- Handle no config case
+ IF ratio = (0, 0)::taler_amount THEN
+ bad_value=TRUE;
+ RETURN;
+ END IF;
+
-- Apply fees
amount_numeric = (amount.val::numeric(24) * 100000000 + amount.frac::numeric(24)) + (fee.val::numeric(24) * 100000000 + fee.frac::numeric(24));
@@ -1885,8 +1896,7 @@ CREATE FUNCTION conversion_to(
IN conversion_rate_class_id INT8,
OUT converted taler_amount,
OUT too_small BOOLEAN,
- OUT under_min BOOLEAN,
- OUT no_config BOOLEAN
+ OUT under_min BOOLEAN
)
LANGUAGE plpgsql STABLE AS $$
DECLARE
@@ -1926,8 +1936,6 @@ BEGIN
mode
FROM get_conversion_class_rate(conversion_rate_class_id);
END IF;
- no_config = NOT FOUND;
- IF no_config THEN RETURN; END IF;
-- Check min amount
SELECT NOT ok INTO too_small FROM amount_left_minus_right(amount, min_amount);
@@ -1948,12 +1956,11 @@ CREATE FUNCTION conversion_from(
IN conversion_rate_class_id INT8,
OUT converted taler_amount,
OUT too_small BOOLEAN,
- OUT under_min BOOLEAN,
- OUT no_config BOOLEAN
+ OUT under_min BOOLEAN
)
LANGUAGE plpgsql STABLE AS $$
DECLARE
- at_ratio taler_amount;
+ ratio taler_amount;
out_fee taler_amount;
tiny_amount taler_amount;
reverse_tiny_amount taler_amount;
@@ -1970,7 +1977,7 @@ BEGIN
(cashin_min_amount).val, (cashin_min_amount).frac,
cashin_rounding_mode
INTO
- at_ratio.val, at_ratio.frac,
+ ratio.val, ratio.frac,
out_fee.val, out_fee.frac,
tiny_amount.val, tiny_amount.frac,
reverse_tiny_amount.val, reverse_tiny_amount.frac,
@@ -1986,7 +1993,7 @@ BEGIN
(cashout_min_amount).val, (cashout_min_amount).frac,
cashout_rounding_mode
INTO
- at_ratio.val, at_ratio.frac,
+ ratio.val, ratio.frac,
out_fee.val, out_fee.frac,
tiny_amount.val, tiny_amount.frac,
reverse_tiny_amount.val, reverse_tiny_amount.frac,
@@ -1994,13 +2001,14 @@ BEGIN
mode
FROM get_conversion_class_rate(conversion_rate_class_id);
END IF;
- no_config = NOT FOUND;
- IF no_config THEN RETURN; END IF;
-- Perform conversion
- SELECT (result).val, (result).frac INTO converted.val, converted.frac
- FROM conversion_revert_ratio(amount, at_ratio, out_fee, tiny_amount, mode, reverse_tiny_amount);
-
+ SELECT (result).val, (result).frac, bad_value INTO converted.val, converted.frac, too_small
+ FROM conversion_revert_ratio(amount, ratio, out_fee, tiny_amount, mode, reverse_tiny_amount);
+ IF too_small THEN
+ RETURN;
+ END IF;
+
-- Check min amount
SELECT NOT ok INTO too_small FROM amount_left_minus_right(converted, min_amount);
IF too_small THEN
@@ -2035,6 +2043,10 @@ LANGUAGE sql STABLE AS $$
(value->'cashout'->'min_amount'->'val', value->'cashout'->'min_amount'->'frac')::taler_amount,
(value->'cashout'->>'rounding_mode')::rounding_mode
FROM config WHERE key='conversion_rate'
+ UNION ALL
+ SELECT (0, 0)::taler_amount, (0, 0)::taler_amount, (0, 1000000)::taler_amount, (0, 0)::taler_amount, 'zero'::rounding_mode,
+ (0, 0)::taler_amount, (0, 0)::taler_amount, (0, 1000000)::taler_amount, (0, 0)::taler_amount, 'zero'::rounding_mode
+ LIMIT 1
$$;
CREATE FUNCTION get_conversion_class_rate(
diff --git a/database-versioning/libeufin-conversion-setup.sql b/database-versioning/libeufin-conversion-setup.sql
@@ -66,7 +66,6 @@ LANGUAGE plpgsql AS $$
too_small BOOLEAN;
balance_insufficient BOOLEAN;
no_account BOOLEAN;
- no_config BOOLEAN;
BEGIN
-- Only reserve transaction triggers cashin
IF NEW.type != 'reserve' THEN
@@ -78,8 +77,8 @@ LANGUAGE plpgsql AS $$
FROM libeufin_nexus.incoming_transactions
WHERE incoming_transaction_id = NEW.incoming_transaction_id;
SET search_path TO libeufin_bank;
- SELECT out_too_small, out_balance_insufficient, out_no_account, out_no_config
- INTO too_small, balance_insufficient, no_account, no_config
+ SELECT out_too_small, out_balance_insufficient, out_no_account
+ INTO too_small, balance_insufficient, no_account
FROM libeufin_bank.cashin(now_date, NEW.metadata, local_amount, local_subject);
SET search_path TO libeufin_nexus;
@@ -101,9 +100,6 @@ LANGUAGE plpgsql AS $$
END IF;
-- Error on hard failures
- IF no_config THEN
- RAISE EXCEPTION 'cashin currency conversion failed: missing conversion rates';
- END IF;
IF no_account THEN
RAISE EXCEPTION 'cashin failed: missing exchange account';
END IF;
diff --git a/testbench/src/test/kotlin/IntegrationTest.kt b/testbench/src/test/kotlin/IntegrationTest.kt
@@ -175,6 +175,7 @@ class IntegrationTest {
assertException("ERROR: cashin failed: missing exchange account") {
registerIncomingPayment(db, cfg, reservePayment)
}
+ db.checkCount(0, 0, 0)
// But KYC works
registerIncomingPayment(
@@ -184,13 +185,15 @@ class IntegrationTest {
subject = "Error test KYC:${EddsaPublicKey.randEdsaKey()}"
)
)
+ db.checkCount(1, 0, 1)
// Create exchange account
bankCmd.run("create-account $flags -u exchange -p exchange-password --name 'Mr Money' --exchange")
- assertException("ERROR: cashin currency conversion failed: missing conversion rates") {
- registerIncomingPayment(db, cfg, reservePayment)
- }
+
+ // Missing rates
+ registerIncomingPayment(db, cfg, reservePayment.copy(id = IncomingId(null, "rate_error", null)))
+ db.checkCount(2, 1, 1)
// Start server
server {
@@ -221,11 +224,11 @@ class IntegrationTest {
bankCmd.run("edit-account admin --debit_threshold KUDOS:100 $flags")
// Too small amount
- db.checkCount(1, 0, 1)
+ db.checkCount(2, 1, 1)
registerIncomingPayment(db, cfg, reservePayment.copy(
amount = TalerAmount("EUR:0.01"),
))
- db.checkCount(2, 1, 1)
+ db.checkCount(3, 2, 1)
client.getA("/accounts/exchange/transactions").assertNoContent()
// Check success
@@ -234,7 +237,7 @@ class IntegrationTest {
id = IncomingId(null, "success", null),
)
registerIncomingPayment(db, cfg, validPayment)
- db.checkCount(3, 1, 2)
+ db.checkCount(4, 2, 2)
client.getA("/accounts/exchange/transactions")
.assertOkJson<BankAccountTransactionsResponse>()
@@ -243,7 +246,7 @@ class IntegrationTest {
registerIncomingPayment(db, cfg, validPayment.copy(
subject="Success 2 $reservePub"
))
- db.checkCount(3, 1, 2)
+ db.checkCount(4, 2, 2)
}
}