merchant

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

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:
Msrc/backend/taler-merchant-httpd_post-orders-ORDER_ID-pay.c | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++------
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 (!) */