merchant

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

commit be36d19b930ada87ed724ba5452f8fd3131377ac
parent 5779a18b87620c4d62e3841c82879c42e099c697
Author: Florian Dold <dold@taler.net>
Date:   Thu, 27 Aug 2026 14:08:23 +0200

merchant: release order claims and wake status long-polls

Diffstat:
Msrc/backend/taler-merchant-httpd_get-orders-ORDER_ID.c | 9+++++++--
Msrc/backenddb/insert_unclaim_signature.c | 34+++++++++++++++++++---------------
Msrc/backenddb/insert_unclaim_signature.sql | 62++++++++++++++++++++++++++++++++++++++++++++------------------
3 files changed, 70 insertions(+), 35 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_get-orders-ORDER_ID.c b/src/backend/taler-merchant-httpd_get-orders-ORDER_ID.c @@ -547,6 +547,11 @@ resume_by_event (void *cls, : "not waiting for refund", (int) extra_size, (const char *) extra); + /* Return the current status after a relevant event. In particular, a + claim changes an order from unclaimed to claimed without changing its + unpaid HTTP response, so re-entering the long poll here would hide the + state transition until the original timeout. */ + god->sc.long_poll_timeout = GNUNET_TIME_absolute_get (); god->suspended = GNUNET_NO; GNUNET_CONTAINER_DLL_remove (god_head, god_tail, @@ -602,12 +607,12 @@ phase_init (struct GetOrderData *god) { struct TMH_OrderPayEventP pay_eh = { .header.size = htons (sizeof (pay_eh)), - .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_PAID), + .header.type = htons (TALER_DBEVENT_MERCHANT_ORDER_STATUS_CHANGED), .merchant_pub = god->hc->instance->merchant_pub }; GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Subscribing to payments on %s\n", + "Subscribing to order status changes on %s\n", god->order_id); GNUNET_CRYPTO_hash (god->order_id, strlen (god->order_id), diff --git a/src/backenddb/insert_unclaim_signature.c b/src/backenddb/insert_unclaim_signature.c @@ -63,16 +63,7 @@ TALER_MERCHANTDB_insert_unclaim_signature ( { char nonce_str[sizeof (*nonce) * 2]; char *end; - char *notify_str = get_notify_str (order_id, - &pg->current_merchant_pub); - struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (order_id), - GNUNET_PQ_query_param_string (nonce_str), - GNUNET_PQ_query_param_string (notify_str), - GNUNET_PQ_query_param_auto_from_type (h_contract), - GNUNET_PQ_query_param_auto_from_type (nsig), - GNUNET_PQ_query_param_end - }; + char *notify_str; bool found; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_bool ("out_found", @@ -90,16 +81,29 @@ TALER_MERCHANTDB_insert_unclaim_signature ( sizeof (nonce_str)); GNUNET_assert (NULL != end); *end = '\0'; + notify_str = get_notify_str (order_id, + &pg->current_merchant_pub); TMH_PQ_prepare_anon (pg, "SELECT" " out_found" " FROM merchant_do_insert_unclaim_signature" "($1, $2, $3, $4, $5);"); - qs = GNUNET_PQ_eval_prepared_singleton_select ( - pg->conn, - "", - params, - rs); + { + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (order_id), + GNUNET_PQ_query_param_string (nonce_str), + GNUNET_PQ_query_param_string (notify_str), + GNUNET_PQ_query_param_auto_from_type (h_contract), + GNUNET_PQ_query_param_auto_from_type (nsig), + GNUNET_PQ_query_param_end + }; + + qs = GNUNET_PQ_eval_prepared_singleton_select ( + pg->conn, + "", + params, + rs); + } GNUNET_free (notify_str); if (qs <= 0) return qs; diff --git a/src/backenddb/insert_unclaim_signature.sql b/src/backenddb/insert_unclaim_signature.sql @@ -27,19 +27,47 @@ LANGUAGE plpgsql AS $$ DECLARE my_expiration_time INT8; + my_order_serial INT8; BEGIN SELECT pay_deadline + ,order_serial INTO my_expiration_time - FROM merchant_contract_terms - WHERE order_id=in_order_id - AND contract_terms->>'nonce' = in_nonce_str; + ,my_order_serial + FROM merchant_contract_terms AS mct + WHERE mct.order_id=in_order_id + AND mct.contract_terms->>'nonce' = in_nonce_str + AND mct.h_contract_terms=in_h_contract_terms + AND NOT mct.paid + AND NOT EXISTS + (SELECT 1 + FROM merchant_deposit_confirmations AS mdc + WHERE mdc.order_serial=mct.order_serial) + AND NOT EXISTS + (SELECT 1 + FROM merchant_refunds AS mr + WHERE mr.order_serial=mct.order_serial) + AND NOT EXISTS + (SELECT 1 + FROM merchant_order_token_blinded_sigs AS mots + WHERE mots.order_serial=mct.order_serial) + FOR UPDATE; IF NOT FOUND THEN - -- FIXME: distinguish better between - -- different "not found" cases (contract, unclaim sig exists) - out_found = FALSE; + -- A repeated request after the contract row was removed is successful, + -- but an old proof must not release a contract claimed with a new nonce. + PERFORM FROM merchant_contract_terms + WHERE order_id = in_order_id; + IF FOUND + THEN + out_found = FALSE; + RETURN; + END IF; + PERFORM FROM merchant_unclaim_signatures + WHERE h_contract_terms = in_h_contract_terms + AND unclaim_sig = in_nonce_sig; + out_found = FOUND; RETURN; END IF; @@ -53,21 +81,19 @@ BEGIN ,my_expiration_time) ON CONFLICT DO NOTHING; - IF FOUND + DELETE FROM merchant_contract_terms + WHERE order_serial=my_order_serial; + IF NOT FOUND THEN - out_found = TRUE; - - -- order status change notification - EXECUTE FORMAT ( - 'NOTIFY %s' - ,in_notify_str); - + out_found = FALSE; RETURN; END IF; - PERFORM FROM merchant_unclaim_signatures - WHERE h_contract_terms = in_h_contract_terms - AND unclaim_sig = in_nonce_sig; - out_found = FOUND; + out_found = TRUE; + + -- order status change notification + EXECUTE FORMAT ( + 'NOTIFY %s' + ,in_notify_str); END $$;