merchant

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

taler-merchant-httpd_get-sessions-SESSION_ID.c (8730B)


      1 /*
      2   This file is part of TALER
      3   (C) 2025 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file src/backend/taler-merchant-httpd_get-sessions-SESSION_ID.c
     18  * @brief implement GET /session/$ID
     19  * @author Christian Grothoff
     20  */
     21 #include "platform.h"
     22 #include "taler-merchant-httpd_get-sessions-SESSION_ID.h"
     23 #include <taler/taler_json_lib.h>
     24 #include <taler/taler_dbevents.h>
     25 #include "merchant-database/lookup_order_by_fulfillment.h"
     26 #include "merchant-database/lookup_order_status.h"
     27 #include "merchant-database/event_listen.h"
     28 
     29 
     30 /**
     31  * Context for a get sessions request.
     32  */
     33 struct GetSessionContext
     34 {
     35   /**
     36    * Kept in a DLL.
     37    */
     38   struct GetSessionContext *next;
     39 
     40   /**
     41    * Kept in a DLL.
     42    */
     43   struct GetSessionContext *prev;
     44 
     45   /**
     46    * Request context.
     47    */
     48   struct TMH_HandlerContext *hc;
     49 
     50   /**
     51    * Entry in the #resume_timeout_heap for this check payment, if we are
     52    * suspended.
     53    */
     54   struct TMH_SuspendedConnection sc;
     55 
     56   /**
     57    * Fulfillment URL from the HTTP request.
     58    */
     59   const char *fulfillment_url;
     60 
     61   /**
     62    * Database event we are waiting on to be resuming on payment.
     63    */
     64   struct GNUNET_DB_EventHandler *eh;
     65 
     66   /**
     67    * Did we suspend @a connection and are thus in
     68    * the #gsc_head DLL (#GNUNET_YES). Set to
     69    * #GNUNET_NO if we are not suspended, and to
     70    * #GNUNET_SYSERR if we should close the connection
     71    * without a response due to shutdown.
     72    */
     73   enum GNUNET_GenericReturnValue suspended;
     74 };
     75 
     76 
     77 /**
     78  * Kept in a DLL.
     79  */
     80 static struct GetSessionContext *gsc_head;
     81 
     82 /**
     83  * Kept in a DLL.
     84  */
     85 static struct GetSessionContext *gsc_tail;
     86 
     87 
     88 void
     89 TMH_force_get_sessions_ID_resume (void)
     90 {
     91   struct GetSessionContext *gsc;
     92 
     93   while (NULL != (gsc = gsc_head))
     94   {
     95     GNUNET_CONTAINER_DLL_remove (gsc_head,
     96                                  gsc_tail,
     97                                  gsc);
     98     gsc->suspended = GNUNET_SYSERR;
     99     MHD_resume_connection (gsc->sc.con);
    100     TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
    101   }
    102 }
    103 
    104 
    105 /**
    106  * Cleanup helper for TMH_get_session_ID().
    107  *
    108  * @param cls must be a `struct GetSessionContext`
    109  */
    110 static void
    111 gsc_cleanup (void *cls)
    112 {
    113   struct GetSessionContext *gsc = cls;
    114 
    115   if (NULL != gsc->eh)
    116   {
    117     TALER_MERCHANTDB_event_listen_cancel (gsc->eh);
    118     gsc->eh = NULL;
    119   }
    120   GNUNET_free (gsc);
    121 }
    122 
    123 
    124 /**
    125  * We have received a trigger from the database
    126  * that we should (possibly) resume the request.
    127  *
    128  * @param cls a `struct GetOrderData` to resume
    129  * @param extra string encoding refund amount (or NULL)
    130  * @param extra_size number of bytes in @a extra
    131  */
    132 static void
    133 resume_by_event (void *cls,
    134                  const void *extra,
    135                  size_t extra_size)
    136 {
    137   struct GetSessionContext *gsc = cls;
    138 
    139   if (GNUNET_YES != gsc->suspended)
    140   {
    141     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    142                 "Not suspended, ignoring event\n");
    143     return; /* duplicate event is possible */
    144   }
    145   gsc->suspended = GNUNET_NO;
    146   GNUNET_CONTAINER_DLL_remove (gsc_head,
    147                                gsc_tail,
    148                                gsc);
    149   MHD_resume_connection (gsc->sc.con);
    150   TALER_MHD_daemon_trigger ();   /* we resumed, kick MHD */
    151 }
    152 
    153 
    154 enum MHD_Result
    155 TMH_get_sessions_ID (
    156   const struct TMH_RequestHandler *rh,
    157   struct MHD_Connection *connection,
    158   struct TMH_HandlerContext *hc)
    159 {
    160   struct GetSessionContext *gsc = hc->ctx;
    161   struct TMH_MerchantInstance *mi = hc->instance;
    162   char *order_id = NULL;
    163   bool paid = false;
    164   bool is_past;
    165 
    166   GNUNET_assert (NULL != mi);
    167   if (NULL == gsc)
    168   {
    169     gsc = GNUNET_new (struct GetSessionContext);
    170     gsc->hc = hc;
    171     hc->ctx = gsc;
    172     hc->cc = &gsc_cleanup;
    173     gsc->sc.con = connection;
    174     gsc->fulfillment_url = MHD_lookup_connection_value (
    175       connection,
    176       MHD_GET_ARGUMENT_KIND,
    177       "fulfillment_url");
    178     if (NULL == gsc->fulfillment_url)
    179     {
    180       GNUNET_break_op (0);
    181       return TALER_MHD_reply_with_error (connection,
    182                                          MHD_HTTP_BAD_REQUEST,
    183                                          TALER_EC_GENERIC_PARAMETER_MISSING,
    184                                          "fulfillment_url");
    185     }
    186     if (! TALER_is_web_url (gsc->fulfillment_url))
    187     {
    188       GNUNET_break_op (0);
    189       return TALER_MHD_reply_with_error (connection,
    190                                          MHD_HTTP_BAD_REQUEST,
    191                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
    192                                          "fulfillment_url");
    193     }
    194 
    195     TALER_MHD_parse_request_timeout (connection,
    196                                      &gsc->sc.long_poll_timeout);
    197 
    198     if (! GNUNET_TIME_absolute_is_past (gsc->sc.long_poll_timeout) )
    199     {
    200       struct TMH_SessionEventP session_eh = {
    201         .header.size = htons (sizeof (session_eh)),
    202         .header.type = htons (TALER_DBEVENT_MERCHANT_SESSION_CAPTURED),
    203         .merchant_pub = gsc->hc->instance->merchant_pub
    204       };
    205 
    206       GNUNET_CRYPTO_hash (hc->infix,
    207                           strlen (hc->infix),
    208                           &session_eh.h_session_id);
    209       GNUNET_CRYPTO_hash (gsc->fulfillment_url,
    210                           strlen (gsc->fulfillment_url),
    211                           &session_eh.h_fulfillment_url);
    212       gsc->eh
    213         = TALER_MERCHANTDB_event_listen (
    214             TMH_db,
    215             &session_eh.header,
    216             GNUNET_TIME_absolute_get_remaining (gsc->sc.long_poll_timeout),
    217             &resume_by_event,
    218             gsc);
    219     }
    220   } /* end first-time initialization (NULL == gsc) */
    221 
    222   if (GNUNET_SYSERR == gsc->suspended)
    223     return MHD_NO; /* close connection on service shutdown */
    224 
    225   is_past = GNUNET_TIME_absolute_is_past (gsc->sc.long_poll_timeout);
    226   /* figure out order_id */
    227   {
    228     enum GNUNET_DB_QueryStatus qs;
    229 
    230     qs = TALER_MERCHANTDB_lookup_order_by_fulfillment (TMH_db,
    231                                                        mi->settings.id,
    232                                                        gsc->fulfillment_url,
    233                                                        hc->infix,
    234                                                        false,
    235                                                        &order_id);
    236     if (0 > qs)
    237     {
    238       GNUNET_break (0);
    239       return TALER_MHD_reply_with_error (
    240         connection,
    241         MHD_HTTP_INTERNAL_SERVER_ERROR,
    242         TALER_EC_GENERIC_DB_FETCH_FAILED,
    243         "lookup_order_by_fulfillment");
    244     }
    245     if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) &&
    246          is_past)
    247     {
    248       return TALER_MHD_reply_with_error (
    249         connection,
    250         MHD_HTTP_NOT_FOUND,
    251         TALER_EC_MERCHANT_GENERIC_SESSION_UNKNOWN,
    252         hc->infix);
    253     }
    254   }
    255 
    256   /* Check if paid */
    257   if (NULL != order_id)
    258   {
    259     enum GNUNET_DB_QueryStatus qs;
    260     struct TALER_PrivateContractHashP h_contract_terms;
    261 
    262     qs = TALER_MERCHANTDB_lookup_order_status (TMH_db,
    263                                                mi->settings.id,
    264                                                order_id,
    265                                                &h_contract_terms,
    266                                                &paid);
    267     if (0 >= qs)
    268     {
    269       GNUNET_break (0);
    270       return TALER_MHD_reply_with_error (
    271         connection,
    272         MHD_HTTP_INTERNAL_SERVER_ERROR,
    273         TALER_EC_GENERIC_DB_FETCH_FAILED,
    274         "lookup_order_status");
    275     }
    276   }
    277 
    278   if (paid)
    279   {
    280     enum MHD_Result ret;
    281 
    282     ret = TALER_MHD_REPLY_JSON_PACK (
    283       connection,
    284       MHD_HTTP_OK,
    285       GNUNET_JSON_pack_string ("order_id",
    286                                order_id));
    287     GNUNET_free (order_id);
    288     return ret;
    289   }
    290 
    291   if (is_past)
    292   {
    293     enum MHD_Result ret;
    294 
    295     GNUNET_assert (NULL != order_id);
    296     ret = TALER_MHD_REPLY_JSON_PACK (
    297       connection,
    298       MHD_HTTP_ACCEPTED,
    299       GNUNET_JSON_pack_string ("order_id",
    300                                order_id));
    301     GNUNET_free (order_id);
    302     return ret;
    303   }
    304 
    305   GNUNET_free (order_id);
    306   GNUNET_CONTAINER_DLL_insert (gsc_head,
    307                                gsc_tail,
    308                                gsc);
    309   gsc->suspended = GNUNET_YES;
    310   MHD_suspend_connection (gsc->sc.con);
    311   return MHD_YES;
    312 }
    313 
    314 
    315 /* end of taler-merchant-httpd_get-templates-TEMPLATE_ID.c */