merchant

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

commit 361f28341a7585acf6c4eb5c7e9db8b116993ae1
parent b5260e975b225731b50787c2aca986a5898b8f14
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun,  2 Aug 2026 20:46:04 +0200

fix GET /private/orders/ long-polling

Diffstat:
Msrc/backend/taler-merchant-httpd_get-private-orders-ORDER_ID.c | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
1 file changed, 59 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 @@ -430,6 +430,18 @@ struct GetOrderRequestContext bool paid_session_matches; /** + * When we install the @e session_eh listener, we already checked + * the order status. We could not do it earlier, because the + * fulfillment URL was unavailable. However, this leaves a chance + * for a payment event to be missed if it happens before the first + * order status check and us installing the event listener. Thus, + * if we afterwards suspend, we should once *immediately* check the + * order status a 2nd time before we really suspend. This flag is + * set to true to handle this case. + */ + bool instant_retry; + + /** * True if the exchange wired the money to the merchant. */ bool wired; @@ -641,32 +653,6 @@ phase_init (struct GetOrderRequestContext *gorc) GNUNET_TIME_absolute_get_remaining (gorc->sc.long_poll_timeout), &resume_by_event, gorc); - if ( (NULL != gorc->session_id) && - (NULL != gorc->ct->fulfillment_url) ) - { - struct TMH_SessionEventP session_eh = { - .header.size = htons (sizeof (session_eh)), - .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED), - .merchant_pub = hc->instance->merchant_pub - }; - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Subscribing to session triggers for %p\n", - gorc); - GNUNET_CRYPTO_hash (gorc->session_id, - strlen (gorc->session_id), - &session_eh.h_session_id); - GNUNET_CRYPTO_hash (gorc->ct->fulfillment_url, - strlen (gorc->ct->fulfillment_url), - &session_eh.h_fulfillment_url); - gorc->session_eh - = TALER_MERCHANTDB_event_listen ( - TMH_db, - &session_eh.header, - GNUNET_TIME_absolute_get_remaining (gorc->sc.long_poll_timeout), - &resume_by_event, - gorc); - } gorc->phase++; } @@ -856,6 +842,40 @@ phase_parse_contract (struct GetOrderRequestContext *gorc) } } + /* Now that the contract terms (and thus the fulfillment URL) are + available, subscribe to session-capture triggers if requested. + The @e session_eh guard makes this idempotent across the phase + re-runs of the long-polling loop; @e gorc->session_eh is cancelled + in gorc_cleanup(). */ + if ( (NULL != gorc->session_id) && + (NULL != gorc->ct->fulfillment_url) && + (NULL == gorc->session_eh) ) + { + struct TMH_SessionEventP session_eh = { + .header.size = htons (sizeof (session_eh)), + .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED), + .merchant_pub = hc->instance->merchant_pub + }; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Subscribing to session triggers for %p\n", + gorc); + GNUNET_CRYPTO_hash (gorc->session_id, + strlen (gorc->session_id), + &session_eh.h_session_id); + GNUNET_CRYPTO_hash (gorc->ct->fulfillment_url, + strlen (gorc->ct->fulfillment_url), + &session_eh.h_fulfillment_url); + gorc->session_eh + = TALER_MERCHANTDB_event_listen ( + TMH_db, + &session_eh.header, + GNUNET_TIME_absolute_get_remaining (gorc->sc.long_poll_timeout), + &resume_by_event, + gorc); + gorc->instant_retry = true; + } + switch (gorc->ct->version) { case TALER_MERCHANT_CONTRACT_VERSION_0: @@ -1008,6 +1028,13 @@ check_reply (struct GetOrderRequestContext *gorc, GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Status unchanged, not returning response yet\n"); GNUNET_assert (GNUNET_NO == gorc->suspended); + if (gorc->instant_retry) + { + gorc->instant_retry = false; + gorc->phase = GOP_FETCH_CONTRACT; + GNUNET_free (can); + return; + } /* note: not necessarily actually unpaid ... */ GNUNET_CONTAINER_DLL_insert (gorc_head, gorc_tail, @@ -1211,6 +1238,12 @@ phase_unpaid_finish (struct GetOrderRequestContext *gorc) if (GNUNET_TIME_absolute_is_future (gorc->sc.long_poll_timeout) && (! gorc->have_lp_not_etag) ) { + if (gorc->instant_retry) + { + gorc->instant_retry = false; + gorc->phase = GOP_FETCH_CONTRACT; + return; + } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Suspending GET /private/orders/%s\n", hc->infix);