merchant

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

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


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