commit d0aa19952bfc26a8cd9923a1a2799160191faa70
parent f405f6f043d5e32b3365b4b40df7a73e503c6e3d
Author: Christian Grothoff <grothoff@gnunet.org>
Date: Fri, 10 Jul 2026 11:31:00 +0200
fix /pay idempotency issue with uninitialized wire fee
Diffstat:
6 files changed, 63 insertions(+), 12 deletions(-)
diff --git a/src/backend/taler-merchant-httpd_post-orders-ORDER_ID-abort.c b/src/backend/taler-merchant-httpd_post-orders-ORDER_ID-abort.c
@@ -659,6 +659,7 @@ find_next_exchange (struct AbortContext *ac)
* @param amount_with_fee amount the exchange will deposit for this coin
* @param deposit_fee fee the exchange will charge for this coin
* @param refund_fee fee the exchange will charge for refunding this coin
+ * @param wire_fee fee to be paid for the wire transfer
*/
static void
refund_coins (void *cls,
@@ -666,13 +667,15 @@ refund_coins (void *cls,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_Amount *amount_with_fee,
const struct TALER_Amount *deposit_fee,
- const struct TALER_Amount *refund_fee)
+ const struct TALER_Amount *refund_fee,
+ const struct TALER_Amount *wire_fee)
{
struct AbortContext *ac = cls;
struct GNUNET_TIME_Timestamp now;
(void) deposit_fee;
(void) refund_fee;
+ (void) wire_fee;
now = GNUNET_TIME_timestamp_get ();
for (size_t i = 0; i<ac->coins_cnt; i++)
{
diff --git a/src/backend/taler-merchant-httpd_post-orders-ORDER_ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ORDER_ID-pay.c
@@ -230,6 +230,11 @@ struct DepositConfirmation
struct TALER_Amount refund_fee;
/**
+ * Fee charged by the exchange for the wire transfer.
+ */
+ struct TALER_Amount wire_fee;
+
+ /**
* If a minimum age was required (i. e. pc->minimum_age is large enough),
* this is the signature of the minimum age (as a single uint8_t), using the
* private key to the corresponding age group. Might be all zeroes for no
@@ -1091,6 +1096,7 @@ batch_deposit_transaction (
continue;
if (! dc->in_batch)
continue;
+ dc->wire_fee = eg->wire_fee;
/* FIXME-#9457: We might want to check if the order was fully paid concurrently
by some other wallet here, and if so, issue an auto-refund. Right now,
it is possible to over-pay if two wallets literally make a concurrent
@@ -2526,6 +2532,7 @@ phase_compute_money_pots (struct PayContext *pc)
* @param amount_with_fee amount the exchange will deposit for this coin
* @param deposit_fee fee the exchange will charge for this coin
* @param refund_fee fee the exchange will charge for refunding this coin
+ * @param wire_fee fee the exchange will charge for wiring this coin
*/
static void
check_coin_paid (void *cls,
@@ -2533,7 +2540,8 @@ check_coin_paid (void *cls,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_Amount *amount_with_fee,
const struct TALER_Amount *deposit_fee,
- const struct TALER_Amount *refund_fee)
+ const struct TALER_Amount *refund_fee,
+ const struct TALER_Amount *wire_fee)
{
struct PayContext *pc = cls;
@@ -2579,6 +2587,7 @@ check_coin_paid (void *cls,
deposit_fee));
dc->deposit_fee = *deposit_fee;
dc->refund_fee = *refund_fee;
+ dc->wire_fee = *wire_fee;
dc->cdd.amount = *amount_with_fee;
dc->found_in_db = true;
pc->pay_transaction.pending--;
@@ -2659,9 +2668,36 @@ check_payment_sufficient (struct PayContext *pc)
&total_wire_fee));
for (unsigned int i = 0; i < pc->parse_pay.num_exchanges; i++)
{
+ const struct ExchangeGroup *egsi = pc->parse_pay.egs[i];
+ const struct TALER_Amount *wire_fee = NULL;
+
+ /* Note: we cannot just use egsi->wire_fee here, as that field
+ MAY not be initialized if the deposit for that exchange was
+ done earlier this is an idempotent request, for example
+ to deposit coins of another exchange or just because the
+ previous answer was lost; thus, we must get the fee from
+ the "dc" as that is guaranteed to be set! */
+ for (unsigned int j = 0; j < pc->parse_pay.num_exchanges; j++)
+ {
+ const struct DepositConfirmation *dc = &pc->parse_pay.dc[j];
+
+ if (0 == strcmp (dc->exchange_url,
+ egsi->exchange_url))
+ {
+ wire_fee = &dc->wire_fee;
+ break;
+ }
+ }
+ if (NULL == wire_fee)
+ {
+ /* Exchange group without a single deposit? Strange! */
+ GNUNET_break (0);
+ continue;
+ }
+
if (GNUNET_OK !=
TALER_amount_cmp_currency (&total_wire_fee,
- &pc->parse_pay.egs[i]->wire_fee))
+ wire_fee))
{
GNUNET_break_op (0);
pay_end (pc,
@@ -2674,7 +2710,7 @@ check_payment_sufficient (struct PayContext *pc)
if (0 >
TALER_amount_add (&total_wire_fee,
&total_wire_fee,
- &pc->parse_pay.egs[i]->wire_fee))
+ wire_fee))
{
GNUNET_break (0);
pay_end (pc,
@@ -3017,10 +3053,11 @@ phase_execute_pay_transaction (struct PayContext *pc)
/* Always report on hard error as well to enable diagnostics */
GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
pay_end (pc,
- TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup deposits"));
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup deposits"));
return;
}
if (pc->pay_transaction.deposit_currency_mismatch)
diff --git a/src/backenddb/lookup_deposits.c b/src/backenddb/lookup_deposits.c
@@ -71,6 +71,7 @@ lookup_deposits_cb (void *cls,
struct TALER_Amount amount_with_fee;
struct TALER_Amount deposit_fee;
struct TALER_Amount refund_fee;
+ struct TALER_Amount wire_fee;
char *exchange_url;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_string ("exchange_url",
@@ -83,6 +84,8 @@ lookup_deposits_cb (void *cls,
&deposit_fee),
TALER_PQ_result_spec_amount_with_currency ("refund_fee",
&refund_fee),
+ TALER_PQ_result_spec_amount_with_currency ("wire_fee",
+ &wire_fee),
GNUNET_PQ_result_spec_end
};
@@ -100,7 +103,8 @@ lookup_deposits_cb (void *cls,
&coin_pub,
&amount_with_fee,
&deposit_fee,
- &refund_fee);
+ &refund_fee,
+ &wire_fee);
GNUNET_PQ_cleanup_result (rs);
}
ldc->qs = num_results;
@@ -136,6 +140,7 @@ TALER_MERCHANTDB_lookup_deposits (
TMH_PQ_prepare_anon (pg,
"SELECT"
" dcom.exchange_url"
+ ",dcom.wire_fee"
",dep.coin_pub"
",dep.amount_with_fee"
",dep.deposit_fee"
diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c
@@ -2993,6 +2993,7 @@ struct TestLookupDeposits_Closure
* @param amount_with_fee amount of the deposit with fees.
* @param deposit_fee fee charged for the deposit.
* @param refund_fee fee charged in case of a refund.
+ * @param wire_fee wire transfer fee to be paid
*/
static void
lookup_deposits_cb (void *cls,
@@ -3000,9 +3001,11 @@ lookup_deposits_cb (void *cls,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_Amount *amount_with_fee,
const struct TALER_Amount *deposit_fee,
- const struct TALER_Amount *refund_fee)
+ const struct TALER_Amount *refund_fee,
+ const struct TALER_Amount *wire_fee)
{
struct TestLookupDeposits_Closure *cmp = cls;
+
if (NULL == cmp)
return;
cmp->results_length += 1;
diff --git a/src/include/merchant-database/lookup_deposits.h b/src/include/merchant-database/lookup_deposits.h
@@ -37,6 +37,7 @@ struct TALER_MERCHANTDB_PostgresContext;
* @param amount_with_fee amount the exchange will deposit for this coin
* @param deposit_fee fee the exchange will charge for this coin
* @param refund_fee fee the exchange will charge for refunding this coin
+ * @param wire_fee fee the exchange will charge for the wire transfer
*/
typedef void
(*TALER_MERCHANTDB_DepositsCallback)(
@@ -45,7 +46,8 @@ typedef void
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_Amount *amount_with_fee,
const struct TALER_Amount *deposit_fee,
- const struct TALER_Amount *refund_fee);
+ const struct TALER_Amount *refund_fee,
+ const struct TALER_Amount *wire_fee);
/**
* Lookup information about coins that were successfully deposited for a
diff --git a/src/testing/testing_api_cmd_post_transfers.c b/src/testing/testing_api_cmd_post_transfers.c
@@ -485,7 +485,8 @@ TALER_TESTING_cmd_merchant_post_transfer_set_serial (
{
struct PostTransfersState *pts = cmd->cls;
- GNUNET_assert (cmd->run == &post_transfers_run);
+ GNUNET_assert ( (cmd->run == &post_transfers_run) ||
+ (cmd->run == &post_transfers_run2) );
pts->serial = serial;
}