merchant

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

taler-merchant-httpd_post-private-donau.c (10355B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2024, 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_post-private-donau.c
     18  * @brief implementation of POST /donau
     19  * @author Bohdan Potuzhnyi
     20  * @author Vlada Svirsh
     21  * @author Christian Grothoff
     22  */
     23 #include "platform.h"
     24 #include <jansson.h>
     25 #include "donau/donau_service.h"
     26 #include <taler/taler_json_lib.h>
     27 #include <taler/taler_dbevents.h>
     28 #include "taler/taler_merchant_service.h"
     29 #include "taler-merchant-httpd_post-private-donau.h"
     30 #include "merchant-database/check_donau_instance.h"
     31 #include "merchant-database/insert_donau_instance.h"
     32 #include "merchant-database/event_listen.h"
     33 #include "merchant-database/event_notify.h"
     34 
     35 /**
     36  * Context for the POST /donau request handler.
     37  */
     38 struct PostDonauCtx
     39 {
     40   /**
     41    * Stored in a DLL.
     42    */
     43   struct PostDonauCtx *next;
     44 
     45   /**
     46    * Stored in a DLL.
     47    */
     48   struct PostDonauCtx *prev;
     49 
     50   /**
     51    * Connection to the MHD server
     52    */
     53   struct MHD_Connection *connection;
     54 
     55   /**
     56    * Context of the request handler.
     57    */
     58   struct TMH_HandlerContext *hc;
     59 
     60   /**
     61    * URL of the DONAU service
     62    * to which the charity belongs.
     63    */
     64   const char *donau_url;
     65 
     66   /**
     67    * ID of the charity in the DONAU service.
     68    */
     69   uint64_t charity_id;
     70 
     71   /**
     72    * Handle returned by DONAU_charities_get(); needed to cancel on
     73    * connection abort, etc.
     74    */
     75   struct DONAU_CharityGetHandle *get_handle;
     76 
     77   /**
     78    * Response to return.
     79    */
     80   struct MHD_Response *response;
     81 
     82   /**
     83    * HTTP status for @e response.
     84    */
     85   unsigned int http_status;
     86 
     87   /**
     88    * #GNUNET_YES if we are suspended,
     89    * #GNUNET_NO if not,
     90    * #GNUNET_SYSERR on shutdown
     91    */
     92   enum GNUNET_GenericReturnValue suspended;
     93 };
     94 
     95 
     96 /**
     97  * Head of active pay context DLL.
     98  */
     99 static struct PostDonauCtx *pdc_head;
    100 
    101 /**
    102  * Tail of active pay context DLL.
    103  */
    104 static struct PostDonauCtx *pdc_tail;
    105 
    106 
    107 void
    108 TMH_force_post_donau_resume ()
    109 {
    110   for (struct PostDonauCtx *pdc = pdc_head;
    111        NULL != pdc;
    112        pdc = pdc->next)
    113   {
    114     if (GNUNET_YES == pdc->suspended)
    115     {
    116       pdc->suspended = GNUNET_SYSERR;
    117       MHD_resume_connection (pdc->connection);
    118     }
    119   }
    120 }
    121 
    122 
    123 /**
    124  * Callback for DONAU_charities_get() to handle the response.
    125  *
    126  * @param cls closure with PostDonauCtx
    127  * @param gcr response from Donau
    128  */
    129 static void
    130 donau_charity_get_cb (void *cls,
    131                       const struct DONAU_GetCharityResponse *gcr)
    132 {
    133   struct PostDonauCtx *pdc = cls;
    134   enum GNUNET_DB_QueryStatus qs;
    135 
    136   pdc->get_handle = NULL;
    137   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    138               "Processing DONAU charity get response");
    139   /* Anything but 200 => propagate Donau’s response. */
    140   if (MHD_HTTP_OK != gcr->hr.http_status)
    141   {
    142     pdc->http_status = MHD_HTTP_BAD_GATEWAY;
    143     pdc->response = TALER_MHD_MAKE_JSON_PACK (
    144       TALER_MHD_PACK_EC (gcr->hr.ec),
    145       GNUNET_JSON_pack_uint64 ("donau_http_status",
    146                                gcr->hr.http_status));
    147     pdc->suspended = GNUNET_NO;
    148     MHD_resume_connection (pdc->connection);
    149     TALER_MHD_daemon_trigger ();
    150     return;
    151   }
    152 
    153   if (0 !=
    154       GNUNET_memcmp (&gcr->details.ok.charity.charity_pub.eddsa_pub,
    155                      &pdc->hc->instance->merchant_pub.eddsa_pub))
    156   {
    157     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    158                 "Charity key at donau does not match our merchant key\n");
    159     pdc->http_status = MHD_HTTP_CONFLICT;
    160     pdc->response = TALER_MHD_make_error (
    161       TALER_EC_GENERIC_PARAMETER_MALFORMED,
    162       "charity_pub != merchant_pub");
    163     MHD_resume_connection (pdc->connection);
    164     TALER_MHD_daemon_trigger ();
    165     return;
    166   }
    167 
    168   qs = TALER_MERCHANTDB_insert_donau_instance (TMH_db,
    169                                                pdc->donau_url,
    170                                                &gcr->details.ok.charity,
    171                                                pdc->charity_id);
    172   switch (qs)
    173   {
    174   case GNUNET_DB_STATUS_HARD_ERROR:
    175     GNUNET_break (0);
    176     pdc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
    177     pdc->response = TALER_MHD_make_error (
    178       TALER_EC_GENERIC_DB_STORE_FAILED,
    179       "insert_donau_instance");
    180     break;
    181   case GNUNET_DB_STATUS_SOFT_ERROR:
    182     GNUNET_break (0);
    183     pdc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
    184     pdc->response = TALER_MHD_make_error (
    185       TALER_EC_GENERIC_DB_STORE_FAILED,
    186       "insert_donau_instance");
    187     break;
    188   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    189     /* presumably idempotent + concurrent, no need to notify, but still respond */
    190     pdc->http_status = MHD_HTTP_NO_CONTENT;
    191     pdc->response = MHD_create_response_from_buffer_static (0,
    192                                                             NULL);
    193     TALER_MHD_add_global_headers (pdc->response,
    194                                   false);
    195     break;
    196   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    197     {
    198       struct GNUNET_DB_EventHeaderP es = {
    199         .size = htons (sizeof (es)),
    200         .type = htons (TALER_DBEVENT_MERCHANT_DONAU_KEYS)
    201       };
    202 
    203       TALER_MERCHANTDB_event_notify (TMH_db,
    204                                      &es,
    205                                      pdc->donau_url,
    206                                      strlen (pdc->donau_url) + 1);
    207       pdc->http_status = MHD_HTTP_NO_CONTENT;
    208       pdc->response = MHD_create_response_from_buffer_static (0,
    209                                                               NULL);
    210       TALER_MHD_add_global_headers (pdc->response,
    211                                     false);
    212       break;
    213     }
    214   }
    215   pdc->suspended = GNUNET_NO;
    216   MHD_resume_connection (pdc->connection);
    217   TALER_MHD_daemon_trigger ();
    218 }
    219 
    220 
    221 /**
    222  * Cleanup function for the PostDonauCtx.
    223  *
    224  * @param cls closure with PostDonauCtx
    225  */
    226 static void
    227 post_donau_cleanup (void *cls)
    228 {
    229   struct PostDonauCtx *pdc = cls;
    230 
    231   if (pdc->get_handle)
    232   {
    233     DONAU_charity_get_cancel (pdc->get_handle);
    234     pdc->get_handle = NULL;
    235   }
    236   GNUNET_CONTAINER_DLL_remove (pdc_head,
    237                                pdc_tail,
    238                                pdc);
    239   GNUNET_free (pdc);
    240 }
    241 
    242 
    243 /**
    244  * Handle a POST "/donau" request.
    245  *
    246  * @param rh context of the handler
    247  * @param connection the MHD connection to handle
    248  * @param[in,out] hc context with further information about the request
    249  * @return MHD result code
    250  */
    251 enum MHD_Result
    252 TMH_private_post_donau_instance (const struct TMH_RequestHandler *rh,
    253                                  struct MHD_Connection *connection,
    254                                  struct TMH_HandlerContext *hc)
    255 {
    256   struct PostDonauCtx *pdc = hc->ctx;
    257 
    258   if (NULL == pdc)
    259   {
    260     enum GNUNET_DB_QueryStatus qs;
    261 
    262     pdc = GNUNET_new (struct PostDonauCtx);
    263     pdc->connection = connection;
    264     pdc->hc = hc;
    265     hc->ctx = pdc;
    266     hc->cc = &post_donau_cleanup;
    267     GNUNET_CONTAINER_DLL_insert (pdc_head,
    268                                  pdc_tail,
    269                                  pdc);
    270     {
    271       struct GNUNET_JSON_Specification spec[] = {
    272         GNUNET_JSON_spec_string ("donau_url",
    273                                  &pdc->donau_url),
    274         GNUNET_JSON_spec_uint64 ("charity_id",
    275                                  &pdc->charity_id),
    276         GNUNET_JSON_spec_end ()
    277       };
    278 
    279       if (GNUNET_OK !=
    280           TALER_MHD_parse_json_data (connection,
    281                                      hc->request_body,
    282                                      spec))
    283       {
    284         GNUNET_break_op (0);
    285         return MHD_NO;
    286       }
    287     }
    288     qs = TALER_MERCHANTDB_check_donau_instance (TMH_db,
    289                                                 &hc->instance->merchant_pub,
    290                                                 pdc->donau_url,
    291                                                 pdc->charity_id);
    292     switch (qs)
    293     {
    294     case GNUNET_DB_STATUS_HARD_ERROR:
    295     case GNUNET_DB_STATUS_SOFT_ERROR:
    296       GNUNET_break (0);
    297       pdc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
    298       return TALER_MHD_reply_with_error (
    299         connection,
    300         MHD_HTTP_INTERNAL_SERVER_ERROR,
    301         TALER_EC_GENERIC_DB_FETCH_FAILED,
    302         "check_donau_instance");
    303       break;
    304     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    305       pdc->http_status = MHD_HTTP_NO_CONTENT;
    306       pdc->response = MHD_create_response_from_buffer_static (0,
    307                                                               NULL);
    308       TALER_MHD_add_global_headers (pdc->response,
    309                                     false);
    310       goto respond;
    311     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    312       /* normal case, continue below */
    313       break;
    314     }
    315 
    316     {
    317       struct DONAU_CharityPrivateKeyP cp;
    318 
    319       /* Merchant private key IS our charity private key */
    320       cp.eddsa_priv = hc->instance->merchant_priv.eddsa_priv;
    321       pdc->get_handle =
    322         DONAU_charity_get (TMH_curl_ctx,
    323                            pdc->donau_url,
    324                            pdc->charity_id,
    325                            &cp,
    326                            &donau_charity_get_cb,
    327                            pdc);
    328     }
    329     if (NULL == pdc->get_handle)
    330     {
    331       GNUNET_break (0);
    332       GNUNET_free (pdc);
    333       return TALER_MHD_reply_with_error (connection,
    334                                          MHD_HTTP_SERVICE_UNAVAILABLE,
    335                                          TALER_EC_GENERIC_ALLOCATION_FAILURE,
    336                                          "Failed to initiate Donau lookup");
    337     }
    338     pdc->suspended = GNUNET_YES;
    339     MHD_suspend_connection (connection);
    340     return MHD_YES;
    341   }
    342 respond:
    343   if (NULL != pdc->response)
    344   {
    345     enum MHD_Result res;
    346 
    347     GNUNET_break (GNUNET_NO == pdc->suspended);
    348     res = MHD_queue_response (pdc->connection,
    349                               pdc->http_status,
    350                               pdc->response);
    351     MHD_destroy_response (pdc->response);
    352     return res;
    353   }
    354   GNUNET_break (GNUNET_SYSERR == pdc->suspended);
    355   return MHD_NO;
    356 }