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:
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);