merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit 90fce64110ceee3be4232c96959e2460dca1e0fa
parent de1442ff02889a71651446bff27c0099ec80254a
Author: Christian Grothoff <christian@grothoff.org>
Date:   Thu, 29 Oct 2026 21:42:10 +0100

fix refund computation

Diffstat:
Msrc/backend/taler-merchant-httpd_get-private-orders-ORDER_ID.c | 113+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
1 file changed, 87 insertions(+), 26 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_get-private-orders-ORDER_ID.c b/src/backend/taler-merchant-httpd_get-private-orders-ORDER_ID.c @@ -92,6 +92,13 @@ struct TransferQuery struct TALER_Amount deposit_fee; /** + * Total amount refunded so far for this coin in this order. The + * exchange only returns @e deposit_fee to us once this reaches + * @e amount_with_fee. + */ + struct TALER_Amount refunded_total; + + /** * Public key of the coin this is about. */ struct TALER_CoinSpendPublicKeyP coin_pub; @@ -519,6 +526,27 @@ TMH_force_gorc_resume (void) /** + * Discard the list of deposits collected for @a gorc. + * + * @param[in,out] gorc order context to update + */ +static void +clear_transfer_queries (struct GetOrderRequestContext *gorc) +{ + struct TransferQuery *tq; + + while (NULL != (tq = gorc->tq_head)) + { + GNUNET_CONTAINER_DLL_remove (gorc->tq_head, + gorc->tq_tail, + tq); + GNUNET_free (tq->exchange_url); + GNUNET_free (tq); + } +} + + +/** * We have received a trigger from the database * that we should (possibly) resume the request. * @@ -559,17 +587,8 @@ static void gorc_cleanup (void *cls) { struct GetOrderRequestContext *gorc = cls; - struct TransferQuery *tq; - - while (NULL != (tq = gorc->tq_head)) - { - GNUNET_CONTAINER_DLL_remove (gorc->tq_head, - gorc->tq_tail, - tq); - GNUNET_free (tq->exchange_url); - GNUNET_free (tq); - } + clear_transfer_queries (gorc); if (NULL != gorc->contract_terms_json) { json_decref (gorc->contract_terms_json); @@ -1385,6 +1404,17 @@ deposit_cb ( "Checking deposit status for coin %s (over %s)\n", TALER_B2S (coin_pub), TALER_amount2s (amount_with_fee)); + if ( (GNUNET_OK != + TALER_amount_cmp_currency (&gorc->deposit_fees_total, + amount_with_fee)) || + (GNUNET_OK != + TALER_amount_cmp_currency (&gorc->deposit_fees_total, + deposit_fee)) ) + { + GNUNET_break (0); + gorc->deposit_currency_mismatch = true; + return; + } gorc->last_payment = GNUNET_TIME_timestamp_max (gorc->last_payment, deposit_timestamp); @@ -1399,6 +1429,13 @@ deposit_cb ( tq->h_wire = *h_wire; tq->amount_with_fee = *amount_with_fee; tq->deposit_fee = *deposit_fee; + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (amount_with_fee->currency, + &tq->refunded_total)); + GNUNET_assert (0 <= + TALER_amount_add (&gorc->deposit_fees_total, + &gorc->deposit_fees_total, + deposit_fee)); } @@ -1416,7 +1453,9 @@ phase_check_deposits (struct GetOrderRequestContext *gorc) /* amount must be always valid for paid orders */ GNUNET_assert (GNUNET_OK == TALER_amount_is_valid (&gorc->contract_amount)); - + /* We may be running again due to long-polling; + clear left-overs from previous iterations */ + clear_transfer_queries (gorc); GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (gorc->contract_amount.currency, &gorc->deposits_total)); @@ -1430,6 +1469,17 @@ phase_check_deposits (struct GetOrderRequestContext *gorc) gorc->order_serial, &deposit_cb, gorc); + if (gorc->deposit_currency_mismatch) + { + GNUNET_break (0); + phase_end (gorc, + TALER_MHD_reply_with_error ( + gorc->sc.con, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "deposits in different currency than original order price")); + return; + } gorc->phase++; } @@ -1480,8 +1530,8 @@ process_refunds_cb ( timestamp), GNUNET_JSON_pack_string ("reason", reason)))); - /* For refunded coins, we are not charged deposit fees, so subtract those - again */ + /* Add up the refunds per coin; #phase_check_refunds() checks if + the total is high enough to void the deposit fee. */ for (struct TransferQuery *tq = gorc->tq_head; NULL != tq; tq = tq->next) @@ -1496,17 +1546,17 @@ process_refunds_cb ( continue; if (GNUNET_OK != TALER_amount_cmp_currency ( - &gorc->deposit_fees_total, - &tq->deposit_fee)) + &tq->refunded_total, + refund_amount)) { gorc->refund_currency_mismatch = true; return; } GNUNET_assert ( 0 <= - TALER_amount_add (&gorc->deposit_fees_refunded_total, - &gorc->deposit_fees_refunded_total, - &tq->deposit_fee)); + TALER_amount_add (&tq->refunded_total, + &tq->refunded_total, + refund_amount)); } if (GNUNET_OK != TALER_amount_cmp_currency ( @@ -1616,6 +1666,21 @@ phase_check_refunds (struct GetOrderRequestContext *gorc) "refunds in different currency than original order price")); return; } + /* Exchange cancels deposit fees of fully refunded coins, check for that case */ + for (struct TransferQuery *tq = gorc->tq_head; + NULL != tq; + tq = tq->next) + { + if (0 != + TALER_amount_cmp (&tq->refunded_total, + &tq->amount_with_fee)) + continue; /* not refunded in full, we still owe the deposit fee */ + GNUNET_assert ( + 0 <= + TALER_amount_add (&gorc->deposit_fees_refunded_total, + &gorc->deposit_fees_refunded_total, + &tq->deposit_fee)); + } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Total refunds are %s\n", TALER_amount2s (&gorc->refund_amount)); @@ -1683,15 +1748,14 @@ process_transfer_details ( return; } - /* Compute total amount *wired* */ + /* Compute total amount *wired*. Note that @e deposit_fees_total is + deliberately not touched here: it covers every coin of the order, + while this callback only sees the coins whose wire transfer we + already know about. */ GNUNET_assert (0 <= TALER_amount_add (&gorc->deposits_total, &gorc->deposits_total, deposit_value)); - GNUNET_assert (0 <= - TALER_amount_add (&gorc->deposit_fees_total, - &gorc->deposit_fees_total, - deposit_fee)); GNUNET_assert (0 <= TALER_amount_subtract (&wired, deposit_value, deposit_fee)); @@ -1733,9 +1797,6 @@ phase_check_local_transfers (struct GetOrderRequestContext *gorc) GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (gorc->contract_amount.currency, &gorc->deposits_total)); - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (gorc->contract_amount.currency, - &gorc->deposit_fees_total)); GNUNET_assert (NULL != gorc->wire_details); /* We may be running again due to long-polling, clear state first */ json_array_clear (gorc->wire_details);