merchant

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

commit b476f8aef4b07f25b362a24d1419398feff30a3f
parent e027e72930686263e0cbdc236f408225426ec247
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun,  4 Jan 2026 22:59:04 +0100

implement GET /sessions/ID for future vPAIVANA API

Diffstat:
Msrc/backend/Makefile.am | 2++
Msrc/backend/taler-merchant-httpd.c | 2++
Msrc/backend/taler-merchant-httpd_dispatcher.c | 9+++++++++
Asrc/backend/taler-merchant-httpd_get-sessions-ID.c | 312+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/taler-merchant-httpd_get-sessions-ID.h | 48++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 373 insertions(+), 0 deletions(-)

diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am @@ -85,6 +85,8 @@ taler_merchant_httpd_SOURCES = \ taler-merchant-httpd_exchanges.c taler-merchant-httpd_exchanges.h \ taler-merchant-httpd_get-orders-ID.c \ taler-merchant-httpd_get-orders-ID.h \ + taler-merchant-httpd_get-sessions-ID.c \ + taler-merchant-httpd_get-sessions-ID.h \ taler-merchant-httpd_get-products-image.c \ taler-merchant-httpd_get-products-image.h \ taler-merchant-httpd_get-templates-ID.c \ diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c @@ -38,6 +38,7 @@ #include "taler-merchant-httpd_post-orders-ID-abort.h" #include "taler-merchant-httpd_post-challenge-ID.h" #include "taler-merchant-httpd_get-orders-ID.h" +#include "taler-merchant-httpd_get-sessions-ID.h" #include "taler-merchant-httpd_exchanges.h" #include "taler-merchant-httpd_spa.h" #include "taler-merchant-httpd_terms.h" @@ -211,6 +212,7 @@ do_shutdown (void *cls) TALER_MHD_daemons_halt (); TMH_handler_statistic_report_transactions_cleanup (); TMH_force_orders_resume (); + TMH_force_get_sessions_ID_resume (); TMH_force_ac_resume (); TMH_force_pc_resume (); TMH_force_kyc_resume (); diff --git a/src/backend/taler-merchant-httpd_dispatcher.c b/src/backend/taler-merchant-httpd_dispatcher.c @@ -23,6 +23,7 @@ #include "taler-merchant-httpd_dispatcher.h" #include "taler-merchant-httpd_exchanges.h" #include "taler-merchant-httpd_get-orders-ID.h" +#include "taler-merchant-httpd_get-sessions-ID.h" #include "taler-merchant-httpd_get-products-image.h" #include "taler-merchant-httpd_get-templates-ID.h" #include "taler-merchant-httpd_mhd.h" @@ -1175,6 +1176,14 @@ determine_handler_group (const char **urlp, .have_id_segment = true, .handler = &TMH_get_orders_ID }, + /* GET /sessions/$ID: */ + { + .url_prefix = "/sessions/", + .method = MHD_HTTP_METHOD_GET, + .allow_deleted_instance = true, + .have_id_segment = true, + .handler = &TMH_get_sessions_ID + }, /* GET /static/ *: */ { .url_prefix = "/static/", diff --git a/src/backend/taler-merchant-httpd_get-sessions-ID.c b/src/backend/taler-merchant-httpd_get-sessions-ID.c @@ -0,0 +1,312 @@ +/* + This file is part of TALER + (C) 2025 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-merchant-httpd_private-get-sessions-ID.c + * @brief implement GET /session/$ID + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler-merchant-httpd_get-sessions-ID.h" +#include <taler/taler_json_lib.h> +#include <taler/taler_dbevents.h> + + +/** + * Context for a get sessions request. + */ +struct GetSessionContext +{ + /** + * Kept in a DLL. + */ + struct GetSessionContext *next; + + /** + * Kept in a DLL. + */ + struct GetSessionContext *prev; + + /** + * Request context. + */ + struct TMH_HandlerContext *hc; + + /** + * Entry in the #resume_timeout_heap for this check payment, if we are + * suspended. + */ + struct TMH_SuspendedConnection sc; + + /** + * Fulfillment URL from the HTTP request. + */ + const char *fulfillment_url; + + /** + * Database event we are waiting on to be resuming on payment. + */ + struct GNUNET_DB_EventHandler *eh; + + /** + * Did we suspend @a connection and are thus in + * the #gsc_head DLL (#GNUNET_YES). Set to + * #GNUNET_NO if we are not suspended, and to + * #GNUNET_SYSERR if we should close the connection + * without a response due to shutdown. + */ + enum GNUNET_GenericReturnValue suspended; +}; + + +/** + * Kept in a DLL. + */ +static struct GetSessionContext *gsc_head; + +/** + * Kept in a DLL. + */ +static struct GetSessionContext *gsc_tail; + + +void +TMH_force_get_sessions_ID_resume (void) +{ + struct GetSessionContext *gsc; + + while (NULL != (gsc = gsc_head)) + { + GNUNET_CONTAINER_DLL_remove (gsc_head, + gsc_tail, + gsc); + gsc->suspended = GNUNET_SYSERR; + MHD_resume_connection (gsc->sc.con); + TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ + } +} + + +/** + * Cleanup helper for TMH_get_session_ID(). + * + * @param cls must be a `struct GetSessionContext` + */ +static void +gsc_cleanup (void *cls) +{ + struct GetSessionContext *gsc = cls; + + if (NULL != gsc->eh) + { + TMH_db->event_listen_cancel (gsc->eh); + gsc->eh = NULL; + } + GNUNET_free (gsc); +} + + +/** + * We have received a trigger from the database + * that we should (possibly) resume the request. + * + * @param cls a `struct GetOrderData` to resume + * @param extra string encoding refund amount (or NULL) + * @param extra_size number of bytes in @a extra + */ +static void +resume_by_event (void *cls, + const void *extra, + size_t extra_size) +{ + struct GetSessionContext *gsc = cls; + + if (GNUNET_YES != gsc->suspended) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Not suspended, ignoring event\n"); + return; /* duplicate event is possible */ + } + gsc->suspended = GNUNET_NO; + GNUNET_CONTAINER_DLL_remove (gsc_head, + gsc_tail, + gsc); + MHD_resume_connection (gsc->sc.con); + TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ +} + + +MHD_RESULT +TMH_get_sessions_ID ( + const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc) +{ + struct GetSessionContext *gsc = hc->ctx; + struct TMH_MerchantInstance *mi = hc->instance; + char *order_id = NULL; + bool paid = false; + bool is_past; + + GNUNET_assert (NULL != mi); + if (NULL == gsc) + { + gsc = GNUNET_new (struct GetSessionContext); + gsc->hc = hc; + hc->ctx = gsc; + hc->cc = &gsc_cleanup; + gsc->sc.con = connection; + gsc->fulfillment_url = MHD_lookup_connection_value ( + connection, + MHD_GET_ARGUMENT_KIND, + "fulfillment_url"); + if (NULL == gsc->fulfillment_url) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MISSING, + "fulfillment_url"); + } + if (! TALER_is_web_url (gsc->fulfillment_url)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "fulfillment_url"); + } + + TALER_MHD_parse_request_timeout (connection, + &gsc->sc.long_poll_timeout); + + if (! GNUNET_TIME_absolute_is_past (gsc->sc.long_poll_timeout) ) + { + struct TMH_SessionEventP session_eh = { + .header.size = htons (sizeof (session_eh)), + .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED), + .merchant_pub = gsc->hc->instance->merchant_pub + }; + + GNUNET_CRYPTO_hash (hc->infix, + strlen (hc->infix), + &session_eh.h_session_id); + GNUNET_CRYPTO_hash (gsc->fulfillment_url, + strlen (gsc->fulfillment_url), + &session_eh.h_fulfillment_url); + gsc->eh + = TMH_db->event_listen ( + TMH_db->cls, + &session_eh.header, + GNUNET_TIME_absolute_get_remaining (gsc->sc.long_poll_timeout), + &resume_by_event, + gsc); + } + } /* end first-time initialization (NULL == gsc) */ + + if (GNUNET_SYSERR == gsc->suspended) + return MHD_NO; /* close connection on service shutdown */ + + is_past = GNUNET_TIME_absolute_is_past (gsc->sc.long_poll_timeout); + /* figure out order_id */ + { + enum GNUNET_DB_QueryStatus qs; + + qs = TMH_db->lookup_order_by_fulfillment (TMH_db->cls, + mi->settings.id, + gsc->fulfillment_url, + hc->infix, + false, + &order_id); + if (0 > qs) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "lookup_order_by_fulfillment"); + } + if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) && + is_past) + { + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_MERCHANT_GENERIC_SESSION_UNKNOWN, + hc->infix); + } + } + + /* Check if paid */ + if (NULL != order_id) + { + enum GNUNET_DB_QueryStatus qs; + struct TALER_PrivateContractHashP h_contract_terms; + + qs = TMH_db->lookup_order_status (TMH_db->cls, + mi->settings.id, + order_id, + &h_contract_terms, + &paid); + if (0 >= qs) + { + GNUNET_break (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "lookup_order_status"); + } + } + + if (paid) + { + MHD_RESULT ret; + + ret = TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_OK, + GNUNET_JSON_pack_string ("order_id", + order_id)); + GNUNET_free (order_id); + return ret; + } + + if (is_past) + { + MHD_RESULT ret; + + GNUNET_assert (NULL != order_id); + ret = TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_ACCEPTED, + GNUNET_JSON_pack_string ("order_id", + order_id)); + GNUNET_free (order_id); + return ret; + } + + GNUNET_free (order_id); + GNUNET_CONTAINER_DLL_insert (gsc_head, + gsc_tail, + gsc); + gsc->suspended = GNUNET_YES; + MHD_suspend_connection (gsc->sc.con); + return MHD_YES; +} + + +/* end of taler-merchant-httpd_get-templates-ID.c */ diff --git a/src/backend/taler-merchant-httpd_get-sessions-ID.h b/src/backend/taler-merchant-httpd_get-sessions-ID.h @@ -0,0 +1,48 @@ +/* + This file is part of TALER + (C) 2025 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-merchant-httpd_get-sessions-ID.h + * @brief implementation of GET /orders/$ID + * @author Christian Grothoff + */ +#ifndef TALER_MERCHANT_HTTPD_GET_SESSIONS_ID_H +#define TALER_MERCHANT_HTTPD_GET_SESSIONS_ID_H + +#include <microhttpd.h> +#include "taler-merchant-httpd.h" + +/** + * Force resuming all suspended session lookups, needed during shutdown. + */ +void +TMH_force_get_sessions_ID_resume (void); + + +/** + * Handle a GET "/sessions/$ID" request. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] hc context with further information about the request + * @return MHD result code + */ +MHD_RESULT +TMH_get_sessions_ID ( + const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc); + +#endif