commit 70157b68c559073179410f62469eb8b504e4b35b
parent 61a3b2c89d4d6786469992f344ba379a1ab8db10
Author: Antoine A <>
Date: Mon, 6 Nov 2023 19:17:24 +0000
Improve cashout endpoints
Diffstat:
4 files changed, 61 insertions(+), 10 deletions(-)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt
@@ -587,12 +587,16 @@ private fun Routing.coreBankCashoutApi(db: Database, ctx: BankConfig) {
)
CashoutConfirmationResult.NO_CASHOUT_PAYTO -> throw conflict(
"Missing cashout payto uri",
- TalerErrorCode.BANK_MISSING_TAN_INFO
+ TalerErrorCode.BANK_CONFIRM_INCOMPLETE
)
CashoutConfirmationResult.BALANCE_INSUFFICIENT -> throw conflict(
"Insufficient funds",
TalerErrorCode.BANK_UNALLOWED_DEBIT
)
+ CashoutConfirmationResult.BAD_CONVERSION -> throw conflict(
+ "Wrong currency conversion",
+ TalerErrorCode.BANK_BAD_CONVERSION
+ )
CashoutConfirmationResult.SUCCESS -> call.respond(HttpStatusCode.NoContent)
}
}
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/CashoutDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/CashoutDAO.kt
@@ -39,6 +39,7 @@ enum class CashoutCreationResult {
/** Result status of cashout operation confirmation */
enum class CashoutConfirmationResult {
SUCCESS,
+ BAD_CONVERSION,
OP_NOT_FOUND,
BAD_TAN_CODE,
BALANCE_INSUFFICIENT,
@@ -158,6 +159,7 @@ class CashoutDAO(private val db: Database) {
val stmt = conn.prepareStatement("""
SELECT
out_no_op,
+ out_bad_conversion,
out_bad_code,
out_balance_insufficient,
out_aborted,
@@ -178,6 +180,7 @@ class CashoutDAO(private val db: Database) {
it.getBoolean("out_aborted") -> CashoutConfirmationResult.ABORTED
it.getBoolean("out_no_retry") -> CashoutConfirmationResult.NO_RETRY
it.getBoolean("out_no_cashout_payto") -> CashoutConfirmationResult.NO_CASHOUT_PAYTO
+ it.getBoolean("out_bad_conversion") -> CashoutConfirmationResult.BAD_CONVERSION
else -> CashoutConfirmationResult.SUCCESS
}
}
diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt
@@ -936,7 +936,7 @@ class CoreBankWithdrawalApiTest {
// POST /withdrawals/withdrawal_id/confirm
@Test
- fun confirm() = bankSetup { _ ->
+ fun confirm() = bankSetup { db ->
// Check confirm created
client.post("/accounts/merchant/withdrawals") {
basicAuth("merchant", "merchant-password")
@@ -1233,7 +1233,7 @@ class CoreBankCashoutApiTest {
// POST /accounts/{USERNAME}/cashouts/{CASHOUT_ID}/confirm
@Test
- fun confirm() = bankSetup { _ ->
+ fun confirm() = bankSetup { db ->
// TODO auth routine
client.patch("/accounts/customer") {
basicAuth("customer", "customer-password")
@@ -1261,7 +1261,7 @@ class CoreBankCashoutApiTest {
client.post("/accounts/customer/cashouts/$uuid/confirm") {
basicAuth("customer", "customer-password")
jsonBody { "tan" to "code" }
- }.assertConflict(TalerErrorCode.BANK_MISSING_TAN_INFO)
+ }.assertConflict(TalerErrorCode.BANK_CONFIRM_INCOMPLETE)
client.patch("/accounts/customer") {
basicAuth("customer", "customer-password")
jsonBody(json {
@@ -1292,12 +1292,46 @@ class CoreBankCashoutApiTest {
}.assertNoContent()
}
- // Check balance insufficient
+ // Check bad conversion
client.post("/accounts/customer/cashouts") {
basicAuth("customer", "customer-password")
jsonBody(json(req) { "request_uid" to randShortHashCode() })
}.assertOk().run {
val uuid = json<CashoutPending>().cashout_id
+
+ db.conversion.updateConfig(ConversionInfo(
+ buy_ratio = DecimalNumber("1"),
+ buy_fee = DecimalNumber("1"),
+ buy_tiny_amount = TalerAmount("KUDOS:0.0001"),
+ buy_rounding_mode = RoundingMode.nearest,
+ buy_min_amount = TalerAmount("FIAT:0.0001"),
+ sell_ratio = DecimalNumber("1"),
+ sell_fee = DecimalNumber("1"),
+ sell_tiny_amount = TalerAmount("FIAT:0.0001"),
+ sell_rounding_mode = RoundingMode.nearest,
+ sell_min_amount = TalerAmount("KUDOS:0.0001"),
+ ))
+
+ client.post("/accounts/customer/cashouts/$uuid/confirm"){
+ basicAuth("customer", "customer-password")
+ jsonBody { "tan" to smsCode("+99") }
+ }.assertConflict(TalerErrorCode.BANK_BAD_CONVERSION)
+
+ // Check can abort because not confirmed
+ client.post("/accounts/customer/cashouts/$uuid/abort") {
+ basicAuth("customer", "customer-password")
+ }.assertNoContent()
+ }
+
+ // Check balance insufficient
+ client.post("/accounts/customer/cashouts") {
+ basicAuth("customer", "customer-password")
+ jsonBody(json(req) {
+ "request_uid" to randShortHashCode()
+ "amount_credit" to convert("KUDOS:1")
+ })
+ }.assertOk().run {
+ val uuid = json<CashoutPending>().cashout_id
// Send too much money
client.post("/accounts/customer/transactions") {
basicAuth("customer", "customer-password")
diff --git a/database-versioning/libeufin-bank-procedures.sql b/database-versioning/libeufin-bank-procedures.sql
@@ -1101,6 +1101,7 @@ CREATE OR REPLACE FUNCTION cashout_confirm(
IN in_tan_code TEXT,
IN in_now_date BIGINT,
OUT out_no_op BOOLEAN,
+ OUT out_bad_conversion BOOLEAN,
OUT out_bad_code BOOLEAN,
OUT out_balance_insufficient BOOLEAN,
OUT out_aborted BOOLEAN,
@@ -1113,7 +1114,8 @@ DECLARE
admin_account_id BIGINT;
already_confirmed BOOLEAN;
subject_local TEXT;
- amount_local taler_amount;
+ amount_debit_local taler_amount;
+ amount_credit_local taler_amount;
challenge_id BIGINT;
tx_id BIGINT;
BEGIN
@@ -1123,14 +1125,16 @@ SELECT
aborted, subject,
bank_account, challenge,
(amount_debit).val, (amount_debit).frac,
+ (amount_credit).val, (amount_credit).frac,
cashout_payto IS NULL
INTO
already_confirmed,
out_aborted, subject_local,
wallet_account_id, challenge_id,
- amount_local.val, amount_local.frac,
+ amount_debit_local.val, amount_debit_local.frac,
+ amount_credit_local.val, amount_credit_local.frac,
out_no_cashout_payto
- FROM cashout_operations
+ FROM cashout_operations
JOIN bank_accounts ON bank_account_id=bank_account
JOIN customers ON customer_id=owning_customer_id
WHERE cashout_uuid=in_cashout_uuid;
@@ -1141,7 +1145,13 @@ ELSIF already_confirmed OR out_aborted OR out_no_cashout_payto THEN
RETURN;
END IF;
--- Check challenge
+-- check conversion
+SELECT too_small OR amount_credit_local!=to_amount INTO out_bad_conversion FROM conversion_to(amount_debit_local, 'sell'::text);
+IF out_bad_conversion THEN
+ RETURN;
+END IF;
+
+-- check challenge
SELECT NOT ok, no_retry
INTO out_bad_code, out_no_retry
FROM challenge_try(challenge_id, in_tan_code, in_now_date);
@@ -1164,7 +1174,7 @@ FROM bank_wire_transfer(
admin_account_id,
wallet_account_id,
subject_local,
- amount_local,
+ amount_debit_local,
in_now_date,
'not-used',
'not-used',