libeufin

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

commit 77ea0eb003a2157172350d737573ed587d556a6d
parent 1f67cce74dbb6cd297cde4f7ac8e7da86a42b541
Author: Antoine A <>
Date:   Sun, 23 Jun 2024 19:10:34 +0200

bank: improve max_amount and add more tests

Diffstat:
Mbank/src/main/kotlin/tech/libeufin/bank/api/BankIntegrationApi.kt | 10+++++++---
Mbank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt | 17++++++++++++++---
Mbank/src/test/kotlin/BankIntegrationApiTest.kt | 3++-
Mbank/src/test/kotlin/CoreBankApiTest.kt | 29+++++++++++++++++++++++++++++
Mdatabase-versioning/libeufin-bank-procedures.sql | 20++++++++++++++++++++
5 files changed, 72 insertions(+), 7 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/api/BankIntegrationApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/api/BankIntegrationApi.kt @@ -44,10 +44,14 @@ fun Routing.bankIntegrationApi(db: Database, ctx: BankConfig) { get("/taler-integration/withdrawal-operation/{wopid}") { val uuid = call.uuidPath("wopid") val params = StatusParams.extract(call.request.queryParameters) - val op = db.withdrawal.pollStatus(uuid, params, ctx.wireMethod)?.copy( + val op = db.withdrawal.pollStatus( + uuid, + params, + ctx.wireMethod, + ctx.maxAmount + )?.copy( card_fees = ctx.wireTransferFees, - min_amount = ctx.minAmount, - max_amount = ctx.maxAmount + min_amount = ctx.minAmount ) ?: throw notFound( "Withdrawal operation '$uuid' not found", TalerErrorCode.BANK_TRANSACTION_NOT_FOUND diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/WithdrawalDAO.kt @@ -337,7 +337,12 @@ class WithdrawalDAO(private val db: Database) { } /** Pool public status of operation [uuid] */ - suspend fun pollStatus(uuid: UUID, params: StatusParams, wire: WireMethod): BankWithdrawalOperationStatus? = + suspend fun pollStatus( + uuid: UUID, + params: StatusParams, + wire: WireMethod, + maxAmount: TalerAmount + ): BankWithdrawalOperationStatus? = poll(uuid, params, status = { it.status }) { db.serializableRead( """ @@ -357,18 +362,24 @@ class WithdrawalDAO(private val db: Database) { ,confirmation_done ,internal_payto_uri ,reserve_pub - ,selected_exchange_payto + ,selected_exchange_payto + ,(max_amount).val as max_amount_val + ,(max_amount).frac as max_amount_frac FROM taler_withdrawal_operations JOIN bank_accounts ON (wallet_bank_account=bank_account_id) + ,account_max_amount(bank_account_id, (?, ?)::taler_amount) AS max_amount WHERE withdrawal_uuid=? """ ) { - setObject(1, uuid) + setLong(1, maxAmount.value) + setInt(2, maxAmount.frac) + setObject(3, uuid) oneOrNull { BankWithdrawalOperationStatus( status = WithdrawalStatus.valueOf(it.getString("status")), amount = it.getOptAmount("amount", db.bankCurrency), suggested_amount = it.getOptAmount("suggested_amount", db.bankCurrency), + max_amount = it.getAmount("max_amount", db.bankCurrency), selection_done = it.getBoolean("selection_done"), transfer_done = it.getBoolean("confirmation_done"), aborted = it.getBoolean("aborted"), diff --git a/bank/src/test/kotlin/BankIntegrationApiTest.kt b/bank/src/test/kotlin/BankIntegrationApiTest.kt @@ -60,7 +60,7 @@ class BankIntegrationApiTest { assert(!it.transfer_done) assertEquals(it.card_fees, TalerAmount.zero("KUDOS")) assertEquals(it.min_amount, TalerAmount.zero("KUDOS")) - assertEquals(it.max_amount, TalerAmount.max("KUDOS")) + assertEquals(it.max_amount, TalerAmount("KUDOS:10")) assertEquals(amount, it.amount) assertEquals(suggested, it.suggested_amount) assertEquals(listOf("iban"), it.wire_types) @@ -201,6 +201,7 @@ class BankIntegrationApiTest { client.get("/taler-integration/withdrawal-operation/$uuid") .assertOkJson<BankWithdrawalOperationStatus> { assertEquals(TalerAmount("KUDOS:1.1"), it.amount) + assertEquals(TalerAmount("KUDOS:10"), it.max_amount) } } } diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt @@ -26,6 +26,7 @@ import org.junit.Test import tech.libeufin.bank.* import tech.libeufin.bank.auth.* import tech.libeufin.common.* +import tech.libeufin.common.db.* import java.time.Duration import java.time.Instant import java.util.* @@ -1444,6 +1445,34 @@ class CoreBankWithdrawalApiTest { }.assertNoContent() } } + + @Test + fun confirmWithFee() = bankSetup(conf = "test_with_fees.conf") { db -> + suspend fun run(amount: TalerAmount): HttpResponse { + val uuid = UUID.randomUUID() + // Create a selected withdrawal directly in the database to bypass checks + db.serializableWrite(""" + INSERT INTO taler_withdrawal_operations(withdrawal_uuid,amount,selected_exchange_payto,selection_done,wallet_bank_account,creation_date) + VALUES (?, (?, ?)::taler_amount, ?, true, 3, 0) + """) { + setObject(1, uuid) + setLong(2, amount.value) + setInt(3, amount.frac) + setString(4, exchangePayto.canonical) + execute() + } + + return client.postA("/accounts/customer/withdrawals/$uuid/confirm") + } + + // Check insufficient fund + for (amount in listOf("KUDOS:11", "KUDOS:10", "KUDOS:0", "KUDOS:150")) { + run(TalerAmount(amount)).assertConflict(TalerErrorCode.BANK_UNALLOWED_DEBIT) + } + + // Check OK + run(TalerAmount("KUDOS:9.9")) + } } class CoreBankCashoutApiTest { diff --git a/database-versioning/libeufin-bank-procedures.sql b/database-versioning/libeufin-bank-procedures.sql @@ -163,6 +163,26 @@ END IF; END $$; COMMENT ON FUNCTION account_balance_is_sufficient IS 'Check if an account have enough fund to transfer an amount.'; +CREATE FUNCTION account_max_amount( + IN in_account_id INT8, + IN in_max_amount taler_amount, + OUT out_max_amount taler_amount +) +LANGUAGE plpgsql AS $$ +BEGIN +-- add balance and max_debt +WITH added AS ( + SELECT amount_add(balance, max_debt) as sum FROM bank_accounts + WHERE bank_account_id=in_account_id +) SELECT (sum).val, (sum).frac + INTO out_max_amount.val, out_max_amount.frac + FROM added; + +IF in_max_amount.val < out_max_amount.val + OR (in_max_amount.val = out_max_amount.val OR in_max_amount.frac < out_max_amount.frac) THEN + out_max_amount = in_max_amount; +END IF; +END $$; CREATE FUNCTION bank_wire_transfer( IN in_creditor_account_id INT8,