commit 91729ce58f8579d7470c8c599a97c13dfbcc7b55
parent a2beb276c7230c189bef3e3c5e47e32054b22617
Author: Florian Dold <dold@taler.net>
Date: Sun, 6 Sep 2026 12:41:06 +0200
payments: retain pending deposit responses after an exchange error
Wait for outstanding batch responses before returning an exchange error.
A cancelled request may already have deposited coins, so record
successful responses to preserve the refund path for an incomplete
payment.
Diffstat:
1 file changed, 53 insertions(+), 6 deletions(-)
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
@@ -925,6 +925,9 @@ resume_pay_with_response (struct PayContext *pc,
unsigned int response_code,
struct MHD_Response *response)
{
+ if ( (NULL != pc->response) &&
+ (pc->response != response) )
+ MHD_destroy_response (pc->response);
pc->response_code = response_code;
pc->response = response;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -1307,6 +1310,35 @@ do_batch_deposits (struct ExchangeGroup *eg);
/**
+ * Retain the first batch error until outstanding deposits have been recorded.
+ * Cancelling another exchange's request cannot undo its accepted deposit.
+ *
+ * @param pc payment context
+ * @param response_code HTTP status to return
+ * @param response response to retain
+ */
+static void
+defer_batch_deposit_error (struct PayContext *pc,
+ unsigned int response_code,
+ struct MHD_Response *response)
+{
+ if (NULL == pc->response)
+ {
+ pc->response = response;
+ pc->response_code = response_code;
+ }
+ else
+ {
+ MHD_destroy_response (response);
+ }
+ if (0 == pc->batch_deposits.pending_at_eg)
+ resume_pay_with_response (pc,
+ pc->response_code,
+ pc->response);
+}
+
+
+/**
* Callback to handle a batch deposit permission's response.
*
* @param cls a `struct ExchangeGroup`
@@ -1361,8 +1393,15 @@ batch_deposit_cb (
}
if (0 == pc->batch_deposits.pending_at_eg)
{
- pc->phase = PP_COMPUTE_MONEY_POTS;
- pay_resume (pc);
+ if (NULL != pc->response)
+ resume_pay_with_response (pc,
+ pc->response_code,
+ pc->response);
+ else
+ {
+ pc->phase = PP_COMPUTE_MONEY_POTS;
+ pay_resume (pc);
+ }
}
return;
default:
@@ -1383,7 +1422,7 @@ batch_deposit_cb (
if (5 == dr->hr.http_status / 100)
{
/* internal server error at exchange */
- resume_pay_with_response (pc,
+ defer_batch_deposit_error (pc,
MHD_HTTP_BAD_GATEWAY,
TALER_MHD_MAKE_JSON_PACK (
TALER_JSON_pack_ec (
@@ -1394,7 +1433,7 @@ batch_deposit_cb (
if (NULL == dr->hr.reply)
{
/* We can't do anything meaningful here, the exchange did something wrong */
- resume_pay_with_response (
+ defer_batch_deposit_error (
pc,
MHD_HTTP_BAD_GATEWAY,
TALER_MHD_MAKE_JSON_PACK (
@@ -1408,7 +1447,7 @@ batch_deposit_cb (
error was being generated */
if (TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS == dr->hr.ec)
{
- resume_pay_with_response (
+ defer_batch_deposit_error (
pc,
MHD_HTTP_CONFLICT,
TALER_MHD_MAKE_JSON_PACK (
@@ -1419,7 +1458,7 @@ batch_deposit_cb (
eg->exchange_url)));
return;
}
- resume_pay_with_response (
+ defer_batch_deposit_error (
pc,
MHD_HTTP_BAD_GATEWAY,
TALER_MHD_MAKE_JSON_PACK (
@@ -1439,6 +1478,14 @@ do_batch_deposits (struct ExchangeGroup *eg)
struct PayContext *pc = eg->pc;
struct TMH_HandlerContext *hc = pc->hc;
unsigned int group_size = 0;
+ if (NULL != pc->response)
+ {
+ if (0 == pc->batch_deposits.pending_at_eg)
+ resume_pay_with_response (pc,
+ pc->response_code,
+ pc->response);
+ return;
+ }
/* Initiate /batch-deposit operation for all coins of
the current exchange (!) */