libeufin

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

commit d459c57867415b9f095ac4726aeef1096ad4c1e6
parent b3bb50692eaf339ba3989b83bb0fbf0f69f7e95a
Author: Antoine A <>
Date:   Wed, 16 Sep 2026 16:07:39 +0200

bank: fix prepared transfer initiated withdrawals not being accessible to the wallet app

Diffstat:
MCargo.lock | 29++++++++++++++++++++---------
Mdatabase-versioning/libeufin-bank-procedures.sql | 3++-
Mlibeufin-bank/src/api.rs | 4++--
Mlibeufin-bank/src/api/prepared.rs | 5+++++
Mlibeufin-bank/src/db/withdrawal.rs | 15++++++++-------
5 files changed, 37 insertions(+), 19 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -107,7 +107,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.119", - "synstructure", + "synstructure 0.13.2", ] [[package]] @@ -3501,6 +3501,17 @@ dependencies = [ ] [[package]] +name = "synstructure" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] name = "system-configuration" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -4597,14 +4608,14 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", - "synstructure", + "syn 3.0.5", + "synstructure 0.14.0", ] [[package]] @@ -4638,14 +4649,14 @@ dependencies = [ [[package]] name = "zerofrom-derive" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", - "synstructure", + "syn 3.0.5", + "synstructure 0.14.0", ] [[package]] diff --git a/database-versioning/libeufin-bank-procedures.sql b/database-versioning/libeufin-bank-procedures.sql @@ -221,7 +221,8 @@ WITH computed AS ( INTO out_max_amount.val, out_max_amount.frac FROM computed; -IF in_max_amount.val < out_max_amount.val +IF in_account_id IS NULL + OR in_max_amount.val < out_max_amount.val OR (in_max_amount.val = out_max_amount.val AND in_max_amount.frac < out_max_amount.frac) THEN out_max_amount = in_max_amount; END IF; diff --git a/libeufin-bank/src/api.rs b/libeufin-bank/src/api.rs @@ -328,7 +328,7 @@ pub mod test { use jiff::Timestamp; use rand::{random_range, seq::IndexedRandom}; use sqlx::PgPool; - use taler_api::db::BindHelper; + use taler_api::{api::TalerRouter, db::BindHelper}; use taler_common::{ api::{EddsaPublicKey, HashCode, ShortHashCode}, config::Config, @@ -668,7 +668,7 @@ pub mod test { drop(conn); let state = Arc::new(BankState::start(db.clone(), cfg).await); - let server = bank_api(state.clone()); + let server = bank_api(state.clone()).finalize(); let merchant_payto = db::account::create( &db, diff --git a/libeufin-bank/src/api/prepared.rs b/libeufin-bank/src/api/prepared.rs @@ -247,6 +247,11 @@ mod test { TransferSubject::Uri { uri, .. } => uri.as_str().rsplit('/').next().unwrap(), _ => unreachable!(), }; + + ctx.get(format!("/withdrawals/{uuid}")).await.assert_ok(); + ctx.get(format!("/taler-integration/withdrawal-operation/{uuid}")) + .await + .assert_ok(); ctx.posta(format!("/accounts/customer/withdrawals/{uuid}/confirm")) .json(json!({})) .await diff --git a/libeufin-bank/src/db/withdrawal.rs b/libeufin-bank/src/db/withdrawal.rs @@ -341,8 +341,8 @@ pub async fn poll_info( ,exchange_account.internal_payto as exchange_payto ,exchange_user.name as exchange_name FROM taler_withdrawal_operations - JOIN bank_accounts AS wallet_account ON wallet_bank_account=wallet_account.bank_account_id - JOIN customers AS wallet_user ON wallet_user.customer_id=wallet_account.owning_customer_id + LEFT JOIN bank_accounts AS wallet_account ON wallet_bank_account=wallet_account.bank_account_id + LEFT JOIN customers AS wallet_user ON wallet_user.customer_id=wallet_account.owning_customer_id LEFT JOIN bank_accounts AS exchange_account ON exchange_bank_account=exchange_account.bank_account_id LEFT JOIN customers AS exchange_user ON exchange_user.customer_id=exchange_account.owning_customer_id WHERE withdrawal_uuid=$1 @@ -353,7 +353,7 @@ pub async fn poll_info( amount: r.try_get_opt_amount("amount", currency)?, suggested_amount: r.try_get_opt_amount("suggested_amount", currency)?, no_amount_to_wallet: r.try_get("no_amount_to_wallet")?, - username: r.try_get("username")?, + username: r.try_get::<Option<CompactString>, _>("username")?.unwrap_or(CompactString::const_new("Uknonwn")), selected_reserve_pub: r.try_get("reserve_pub")?, selected_exchange_account: sql_opt_bank_payto(&r, ctx, "exchange_payto", "exchange_name")? })) @@ -403,8 +403,8 @@ pub async fn poll_status( ,max_amount ,no_amount_to_wallet FROM taler_withdrawal_operations - JOIN bank_accounts AS wallet_account ON wallet_bank_account=wallet_account.bank_account_id - JOIN customers AS wallet_user ON wallet_user.customer_id=wallet_account.owning_customer_id + LEFT JOIN bank_accounts AS wallet_account ON wallet_bank_account=wallet_account.bank_account_id + LEFT JOIN customers AS wallet_user ON wallet_user.customer_id=wallet_account.owning_customer_id LEFT JOIN bank_accounts AS exchange_account ON exchange_bank_account=exchange_account.bank_account_id LEFT JOIN customers AS exchange_user ON exchange_user.customer_id=exchange_account.owning_customer_id ,account_max_amount(wallet_account.bank_account_id, $1) AS max_amount @@ -413,7 +413,8 @@ pub async fn poll_status( ) .bind(max) .bind(uuid) - .try_map(|r: PgRow| Ok(BankWithdrawalOperationStatus { + .try_map(|r: PgRow| { + Ok(BankWithdrawalOperationStatus { status: r.try_get("status")?, amount: r.try_get_opt_amount("amount", currency)?, suggested_amount: r.try_get_opt_amount("suggested_amount", currency)?, @@ -432,7 +433,7 @@ pub async fn poll_status( sender_wire: sql_opt_bank_payto(&r, ctx, "internal_payto", "name")?, suggested_exchange: None, wire_types: vec![wire.to_compact_string()] - })) + })}) .fetch_optional(db)) , |init| init.as_ref().map(|it| it.status != params.status).unwrap_or(true),