merchant

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

merchant_api_get-config.c (12121B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Lesser General Public License as published by the Free Software
      7   Foundation; either version 2.1, 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 Lesser General Public License for more details.
     12 
     13   You should have received a copy of the GNU Lesser General Public License along with
     14   TALER; see the file COPYING.LGPL.  If not, see
     15   <http://www.gnu.org/licenses/>
     16 */
     17 /**
     18  * @file merchant_api_get-config-new.c
     19  * @brief Implementation of the GET /config request
     20  * @author Christian Grothoff
     21  */
     22 #include "taler/platform.h"
     23 #include <curl/curl.h>
     24 #include <jansson.h>
     25 #include <microhttpd.h> /* just for HTTP status codes */
     26 #include <gnunet/gnunet_util_lib.h>
     27 #include <gnunet/gnunet_curl_lib.h>
     28 #include <taler/taler-merchant/get-config.h>
     29 #include "merchant_api_curl_defaults.h"
     30 #include <taler/taler_json_lib.h>
     31 #include <taler/taler_signatures.h>
     32 
     33 /**
     34  * Which version of the Taler protocol is implemented
     35  * by this library?  Used to determine compatibility.
     36  */
     37 #define MERCHANT_PROTOCOL_CURRENT 27
     38 
     39 /**
     40  * How many configs are we backwards-compatible with?
     41  */
     42 #define MERCHANT_PROTOCOL_AGE 3
     43 
     44 /**
     45  * How many exchanges do we allow at most per merchant?
     46  */
     47 #define MAX_EXCHANGES 1024
     48 
     49 /**
     50  * How many currency specs do we allow at most per merchant?
     51  */
     52 #define MAX_CURRENCIES 1024
     53 
     54 
     55 /**
     56  * Handle for a GET /config operation.
     57  */
     58 struct TALER_MERCHANT_GetConfigHandle
     59 {
     60   /**
     61    * Base URL of the merchant backend.
     62    */
     63   char *base_url;
     64 
     65   /**
     66    * The full URL for this request.
     67    */
     68   char *url;
     69 
     70   /**
     71    * Handle for the request.
     72    */
     73   struct GNUNET_CURL_Job *job;
     74 
     75   /**
     76    * Function to call with the result.
     77    */
     78   TALER_MERCHANT_GetConfigCallback cb;
     79 
     80   /**
     81    * Closure for @a cb.
     82    */
     83   TALER_MERCHANT_GET_CONFIG_RESULT_CLOSURE *cb_cls;
     84 
     85   /**
     86    * Reference to the execution context.
     87    */
     88   struct GNUNET_CURL_Context *ctx;
     89 };
     90 
     91 
     92 /**
     93  * Function called when we're done processing the
     94  * HTTP GET /config request.
     95  *
     96  * @param cls the `struct TALER_MERCHANT_GetConfigHandle`
     97  * @param response_code HTTP response code, 0 on error
     98  * @param response response body, NULL if not in JSON
     99  */
    100 static void
    101 handle_get_config_finished (void *cls,
    102                             long response_code,
    103                             const void *response)
    104 {
    105   struct TALER_MERCHANT_GetConfigHandle *gch = cls;
    106   const json_t *json = response;
    107   struct TALER_MERCHANT_GetConfigResponse cr = {
    108     .hr.http_status = (unsigned int) response_code,
    109     .hr.reply = json
    110   };
    111 
    112   gch->job = NULL;
    113   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    114               "Got /config response with status code %u\n",
    115               (unsigned int) response_code);
    116   switch (response_code)
    117   {
    118   case MHD_HTTP_OK:
    119     {
    120       const json_t *jcs;
    121       const json_t *exchanges = NULL;
    122       struct TALER_MERCHANT_GetConfigExchangeInfo *eci = NULL;
    123       unsigned int num_eci = 0;
    124       unsigned int nspec;
    125       struct TALER_JSON_ProtocolVersion pv;
    126       struct GNUNET_JSON_Specification spec[] = {
    127         GNUNET_JSON_spec_object_const ("currencies",
    128                                        &jcs),
    129         GNUNET_JSON_spec_array_const ("exchanges",
    130                                       &exchanges),
    131         GNUNET_JSON_spec_string ("currency",
    132                                  &cr.details.ok.ci.currency),
    133         TALER_JSON_spec_version ("version",
    134                                  &pv),
    135         GNUNET_JSON_spec_string ("version",
    136                                  &cr.details.ok.ci.version),
    137         GNUNET_JSON_spec_mark_optional (
    138           GNUNET_JSON_spec_string ("implementation",
    139                                    &cr.details.ok.implementation),
    140           NULL),
    141         GNUNET_JSON_spec_bool ("have_self_provisioning",
    142                                &cr.details.ok.have_self_provisioning),
    143         GNUNET_JSON_spec_bool ("have_donau",
    144                                &cr.details.ok.have_donau),
    145         GNUNET_JSON_spec_string ("payment_target_types",
    146                                  &cr.details.ok.payment_target_types),
    147         GNUNET_JSON_spec_mark_optional (
    148           GNUNET_JSON_spec_string ("payment_target_regex",
    149                                    &cr.details.ok.payment_target_regex),
    150           NULL),
    151         GNUNET_JSON_spec_mark_optional (
    152           GNUNET_JSON_spec_array_const ("mandatory_tan_channels",
    153                                         &cr.details.ok.mandatory_tan_channels),
    154           NULL),
    155         GNUNET_JSON_spec_relative_time ("default_pay_delay",
    156                                         &cr.details.ok.default_pay_delay),
    157         GNUNET_JSON_spec_relative_time ("default_refund_delay",
    158                                         &cr.details.ok.default_refund_delay),
    159         GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay",
    160                                         &cr.details.ok.default_wire_transfer_delay),
    161         GNUNET_JSON_spec_string ("default_persona",
    162                                  &cr.details.ok.default_persona),
    163         GNUNET_JSON_spec_mark_optional (
    164           GNUNET_JSON_spec_array_const ("report_generators",
    165                                         &cr.details.ok.report_generators),
    166           NULL),
    167         GNUNET_JSON_spec_mark_optional (
    168           GNUNET_JSON_spec_string ("phone_regex",
    169                                    &cr.details.ok.phone_regex),
    170           NULL),
    171         GNUNET_JSON_spec_mark_optional (
    172           GNUNET_JSON_spec_object_const ("spa_options",
    173                                          &cr.details.ok.spa_options),
    174           NULL),
    175         GNUNET_JSON_spec_end ()
    176       };
    177 
    178       cr.details.ok.compat
    179         = TALER_MERCHANT_GET_CONFIG_VC_PROTOCOL_ERROR;
    180       if (GNUNET_OK !=
    181           GNUNET_JSON_parse (json,
    182                              spec,
    183                              NULL, NULL))
    184       {
    185         GNUNET_break_op (0);
    186         cr.hr.http_status = 0;
    187         cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    188         break;
    189       }
    190       cr.details.ok.compat = TALER_MERCHANT_GET_CONFIG_VC_MATCH;
    191       if (MERCHANT_PROTOCOL_CURRENT < pv.current)
    192       {
    193         cr.details.ok.compat |= TALER_MERCHANT_GET_CONFIG_VC_NEWER;
    194         if (MERCHANT_PROTOCOL_CURRENT < pv.current - pv.age)
    195           cr.details.ok.compat
    196             |= TALER_MERCHANT_GET_CONFIG_VC_INCOMPATIBLE;
    197       }
    198       if (MERCHANT_PROTOCOL_CURRENT > pv.current)
    199       {
    200         cr.details.ok.compat |= TALER_MERCHANT_GET_CONFIG_VC_OLDER;
    201         if (MERCHANT_PROTOCOL_CURRENT - MERCHANT_PROTOCOL_AGE > pv.current)
    202           cr.details.ok.compat
    203             |= TALER_MERCHANT_GET_CONFIG_VC_INCOMPATIBLE;
    204       }
    205 
    206       nspec = (unsigned int) json_object_size (jcs);
    207       if ( (nspec > MAX_CURRENCIES) ||
    208            (json_object_size (jcs) != (size_t) nspec) )
    209       {
    210         GNUNET_break_op (0);
    211         cr.hr.http_status = 0;
    212         cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    213         break;
    214       }
    215       if (NULL != exchanges)
    216       {
    217         num_eci = (unsigned int) json_array_size (exchanges);
    218         if ( (num_eci > MAX_EXCHANGES) ||
    219              (json_array_size (exchanges) != (size_t) num_eci) )
    220         {
    221           GNUNET_break_op (0);
    222           cr.hr.http_status = 0;
    223           cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    224           break;
    225         }
    226         eci = GNUNET_new_array (num_eci,
    227                                 struct TALER_MERCHANT_GetConfigExchangeInfo);
    228         for (unsigned int i = 0; i<num_eci; i++)
    229         {
    230           struct TALER_MERCHANT_GetConfigExchangeInfo *ei = &eci[i];
    231           const json_t *ej = json_array_get (exchanges,
    232                                              i);
    233           struct GNUNET_JSON_Specification ispec[] = {
    234             GNUNET_JSON_spec_string ("currency",
    235                                      &ei->currency),
    236             GNUNET_JSON_spec_string ("base_url",
    237                                      &ei->base_url),
    238             GNUNET_JSON_spec_fixed_auto ("master_pub",
    239                                          &ei->master_pub),
    240             GNUNET_JSON_spec_end ()
    241           };
    242 
    243           if (GNUNET_OK !=
    244               GNUNET_JSON_parse (ej,
    245                                  ispec,
    246                                  NULL, NULL))
    247           {
    248             GNUNET_break_op (0);
    249             cr.hr.http_status = 0;
    250             cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    251             GNUNET_free (eci);
    252             break;
    253           }
    254         }
    255       }
    256       {
    257         struct TALER_CurrencySpecification *cspecs;
    258         unsigned int off = 0;
    259         json_t *obj;
    260         const char *curr;
    261 
    262         cspecs = GNUNET_new_array (nspec,
    263                                    struct TALER_CurrencySpecification);
    264         cr.details.ok.num_cspecs = nspec;
    265         cr.details.ok.cspecs = cspecs;
    266         cr.details.ok.num_exchanges = (unsigned int) num_eci;
    267         cr.details.ok.exchanges = eci;
    268         json_object_foreach ((json_t *) jcs, curr, obj)
    269         {
    270           struct TALER_CurrencySpecification *cs = &cspecs[off++];
    271           struct GNUNET_JSON_Specification cspec[] = {
    272             TALER_JSON_spec_currency_specification (curr,
    273                                                     curr,
    274                                                     cs),
    275             GNUNET_JSON_spec_end ()
    276           };
    277 
    278           if (GNUNET_OK !=
    279               GNUNET_JSON_parse (jcs,
    280                                  cspec,
    281                                  NULL, NULL))
    282           {
    283             GNUNET_break_op (0);
    284             cr.hr.http_status = 0;
    285             cr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    286             GNUNET_free (eci);
    287             TALER_CONFIG_free_currencies (off - 1,
    288                                           cspecs);
    289             break;
    290           }
    291         }
    292         gch->cb (gch->cb_cls,
    293                  &cr);
    294         GNUNET_free (eci);
    295         TALER_CONFIG_free_currencies (nspec,
    296                                       cspecs);
    297       }
    298       TALER_MERCHANT_get_config_cancel (gch);
    299       return;
    300     }
    301   default:
    302     cr.hr.ec = TALER_JSON_get_error_code (json);
    303     cr.hr.hint = TALER_JSON_get_error_hint (json);
    304     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    305                 "Unexpected response code %u/%d\n",
    306                 (unsigned int) response_code,
    307                 (int) cr.hr.ec);
    308     break;
    309   }
    310   gch->cb (gch->cb_cls,
    311            &cr);
    312   TALER_MERCHANT_get_config_cancel (gch);
    313 }
    314 
    315 
    316 struct TALER_MERCHANT_GetConfigHandle *
    317 TALER_MERCHANT_get_config_create (
    318   struct GNUNET_CURL_Context *ctx,
    319   const char *url)
    320 {
    321   struct TALER_MERCHANT_GetConfigHandle *gch;
    322 
    323   gch = GNUNET_new (struct TALER_MERCHANT_GetConfigHandle);
    324   gch->ctx = ctx;
    325   gch->base_url = GNUNET_strdup (url);
    326   return gch;
    327 }
    328 
    329 
    330 enum TALER_ErrorCode
    331 TALER_MERCHANT_get_config_start (
    332   struct TALER_MERCHANT_GetConfigHandle *gch,
    333   TALER_MERCHANT_GetConfigCallback cb,
    334   TALER_MERCHANT_GET_CONFIG_RESULT_CLOSURE *cb_cls)
    335 {
    336   CURL *eh;
    337 
    338   gch->cb = cb;
    339   gch->cb_cls = cb_cls;
    340   gch->url = TALER_url_join (gch->base_url,
    341                              "config",
    342                              NULL);
    343   if (NULL == gch->url)
    344     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    345   eh = TALER_MERCHANT_curl_easy_get_ (gch->url);
    346   if (NULL == eh)
    347     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    348   gch->job = GNUNET_CURL_job_add (gch->ctx,
    349                                   eh,
    350                                   &handle_get_config_finished,
    351                                   gch);
    352   if (NULL == gch->job)
    353     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    354   return TALER_EC_NONE;
    355 }
    356 
    357 
    358 void
    359 TALER_MERCHANT_get_config_cancel (
    360   struct TALER_MERCHANT_GetConfigHandle *gch)
    361 {
    362   if (NULL != gch->job)
    363   {
    364     GNUNET_CURL_job_cancel (gch->job);
    365     gch->job = NULL;
    366   }
    367   GNUNET_free (gch->url);
    368   GNUNET_free (gch->base_url);
    369   GNUNET_free (gch);
    370 }
    371 
    372 
    373 /* end of merchant_api_get-config-new.c */