merchant

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

merchant_api_get-templates-TEMPLATE_ID.c (13396B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2022-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 src/lib/merchant_api_get-templates-TEMPLATE_ID.c
     19  * @brief Implementation of the GET /templates/$TEMPLATE_ID request (wallet-facing)
     20  * @author Christian Grothoff
     21  */
     22 #include "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/merchant/get-templates-TEMPLATE_ID.h>
     29 #include "merchant_api_curl_defaults.h"
     30 #include <taler/taler_json_lib.h>
     31 
     32 
     33 /**
     34  * Maximum number of exchange candidates or wire methods accepted in a reply.
     35  */
     36 #define MAX_CANDIDATE_ENTRIES 1024
     37 
     38 
     39 /**
     40  * Free merchant metadata parsed for a callback.
     41  *
     42  * @param merchant metadata to free
     43  */
     44 static void
     45 free_merchant_metadata (struct TALER_MERCHANT_MetaData *merchant)
     46 {
     47   GNUNET_free (merchant->name);
     48   GNUNET_free (merchant->website);
     49   GNUNET_free (merchant->email);
     50   GNUNET_free (merchant->logo);
     51   if (NULL != merchant->address)
     52     json_decref (merchant->address);
     53   if (NULL != merchant->jurisdiction)
     54     json_decref (merchant->jurisdiction);
     55 }
     56 
     57 
     58 /**
     59  * Parse merchant metadata.
     60  *
     61  * @param object merchant JSON object
     62  * @param[out] merchant parsed metadata
     63  * @return #GNUNET_OK on success
     64  */
     65 static enum GNUNET_GenericReturnValue
     66 parse_merchant_metadata (const json_t *object,
     67                          struct TALER_MERCHANT_MetaData *merchant)
     68 {
     69   struct GNUNET_JSON_Specification spec[] = {
     70     GNUNET_JSON_spec_string_copy ("name",
     71                                   &merchant->name),
     72     GNUNET_JSON_spec_mark_optional (
     73       TALER_JSON_spec_web_url_copy ("website",
     74                                     &merchant->website),
     75       NULL),
     76     GNUNET_JSON_spec_mark_optional (
     77       GNUNET_JSON_spec_string_copy ("email",
     78                                     &merchant->email),
     79       NULL),
     80     GNUNET_JSON_spec_mark_optional (
     81       GNUNET_JSON_spec_string_copy ("logo",
     82                                     &merchant->logo),
     83       NULL),
     84     GNUNET_JSON_spec_mark_optional (
     85       GNUNET_JSON_spec_object_copy ("address",
     86                                     &merchant->address),
     87       NULL),
     88     GNUNET_JSON_spec_mark_optional (
     89       GNUNET_JSON_spec_object_copy ("jurisdiction",
     90                                     &merchant->jurisdiction),
     91       NULL),
     92     GNUNET_JSON_spec_end ()
     93   };
     94 
     95   return GNUNET_JSON_parse (object,
     96                             spec,
     97                             NULL,
     98                             NULL);
     99 }
    100 
    101 
    102 /**
    103  * Free parsed exchange candidates.
    104  *
    105  * @param num_candidates number of candidates
    106  * @param candidates candidate array
    107  */
    108 static void
    109 free_exchange_candidates (
    110   unsigned int num_candidates,
    111   struct TALER_MERCHANT_TemplateExchangeCandidate *candidates)
    112 {
    113   for (unsigned int i = 0; i<num_candidates; i++)
    114     GNUNET_free (candidates[i].wire_methods);
    115   GNUNET_free (candidates);
    116 }
    117 
    118 
    119 /**
    120  * Parse exchange candidates from a template response.
    121  *
    122  * @param array candidate JSON array
    123  * @param[out] num_candidates number of parsed entries
    124  * @param[out] candidates parsed entries
    125  * @return #GNUNET_OK on success
    126  */
    127 static enum GNUNET_GenericReturnValue
    128 parse_exchange_candidates (
    129   const json_t *array,
    130   unsigned int *num_candidates,
    131   struct TALER_MERCHANT_TemplateExchangeCandidate **candidates)
    132 {
    133   size_t array_size = json_array_size (array);
    134   struct TALER_MERCHANT_TemplateExchangeCandidate *result;
    135 
    136   if ( (array_size > MAX_CANDIDATE_ENTRIES) ||
    137        (array_size != (unsigned int) array_size) )
    138     return GNUNET_SYSERR;
    139   result = GNUNET_new_array (
    140     array_size,
    141     struct TALER_MERCHANT_TemplateExchangeCandidate);
    142   for (unsigned int i = 0; i<array_size; i++)
    143   {
    144     struct TALER_MERCHANT_TemplateExchangeCandidate *candidate = &result[i];
    145     const json_t *wire_methods;
    146     size_t num_methods;
    147     const char **methods;
    148     struct GNUNET_JSON_Specification spec[] = {
    149       TALER_JSON_spec_web_url ("base_url",
    150                                &candidate->base_url),
    151       GNUNET_JSON_spec_string ("currency",
    152                                &candidate->currency),
    153       GNUNET_JSON_spec_fixed_auto ("master_pub",
    154                                    &candidate->master_pub),
    155       GNUNET_JSON_spec_array_const ("wire_methods",
    156                                     &wire_methods),
    157       GNUNET_JSON_spec_end ()
    158     };
    159 
    160     if (GNUNET_OK !=
    161         GNUNET_JSON_parse (json_array_get (array,
    162                                            i),
    163                            spec,
    164                            NULL,
    165                            NULL))
    166       goto fail;
    167     num_methods = json_array_size (wire_methods);
    168     if ( (0 == num_methods) ||
    169          (num_methods > MAX_CANDIDATE_ENTRIES) ||
    170          (num_methods != (unsigned int) num_methods) )
    171       goto fail;
    172     methods = GNUNET_new_array (num_methods,
    173                                 const char *);
    174     for (unsigned int j = 0; j<num_methods; j++)
    175     {
    176       const json_t *method = json_array_get (wire_methods,
    177                                              j);
    178 
    179       if (! json_is_string (method))
    180       {
    181         GNUNET_free (methods);
    182         goto fail;
    183       }
    184       methods[j] = json_string_value (method);
    185     }
    186     candidate->num_wire_methods = (unsigned int) num_methods;
    187     candidate->wire_methods = methods;
    188   }
    189   *num_candidates = (unsigned int) array_size;
    190   *candidates = result;
    191   return GNUNET_OK;
    192 
    193 fail:
    194   free_exchange_candidates ((unsigned int) array_size,
    195                             result);
    196   return GNUNET_SYSERR;
    197 }
    198 
    199 
    200 /**
    201  * Handle for a GET /templates/$TEMPLATE_ID operation (wallet-facing).
    202  */
    203 struct TALER_MERCHANT_GetTemplatesHandle
    204 {
    205   /**
    206    * Base URL of the merchant backend.
    207    */
    208   char *base_url;
    209 
    210   /**
    211    * The full URL for this request.
    212    */
    213   char *url;
    214 
    215   /**
    216    * Handle for the request.
    217    */
    218   struct GNUNET_CURL_Job *job;
    219 
    220   /**
    221    * Function to call with the result.
    222    */
    223   TALER_MERCHANT_GetTemplatesCallback cb;
    224 
    225   /**
    226    * Closure for @a cb.
    227    */
    228   TALER_MERCHANT_GET_TEMPLATES_RESULT_CLOSURE *cb_cls;
    229 
    230   /**
    231    * Reference to the execution context.
    232    */
    233   struct GNUNET_CURL_Context *ctx;
    234 
    235   /**
    236    * Template identifier.
    237    */
    238   char *template_id;
    239 };
    240 
    241 
    242 /**
    243  * Function called when we're done processing the
    244  * HTTP GET /templates/$TEMPLATE_ID request.
    245  *
    246  * @param cls the `struct TALER_MERCHANT_GetTemplatesHandle`
    247  * @param response_code HTTP response code, 0 on error
    248  * @param response response body, NULL if not in JSON
    249  */
    250 static void
    251 handle_get_template_finished (void *cls,
    252                               long response_code,
    253                               const void *response)
    254 {
    255   struct TALER_MERCHANT_GetTemplatesHandle *gth = cls;
    256   const json_t *json = response;
    257   struct TALER_MERCHANT_MetaData merchant = { 0 };
    258   struct TALER_MerchantPublicKeyP merchant_pub;
    259   struct TALER_MERCHANT_TemplateExchangeCandidate *candidates = NULL;
    260   unsigned int num_candidates = 0;
    261   struct TALER_MERCHANT_GetTemplatesResponse wtgr = {
    262     .hr.http_status = (unsigned int) response_code,
    263     .hr.reply = json
    264   };
    265 
    266   gth->job = NULL;
    267   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    268               "Got /templates/$TEMPLATE_ID response with status code %u\n",
    269               (unsigned int) response_code);
    270   switch (response_code)
    271   {
    272   case MHD_HTTP_OK:
    273     {
    274       const json_t *exchange_candidates = NULL;
    275       const json_t *merchant_object = NULL;
    276       bool exchange_candidates_missing;
    277       bool merchant_missing;
    278       bool merchant_pub_missing;
    279       struct GNUNET_JSON_Specification spec[] = {
    280         GNUNET_JSON_spec_object_const ("template_contract",
    281                                        &wtgr.details.ok.template_contract),
    282         GNUNET_JSON_spec_mark_optional (
    283           GNUNET_JSON_spec_object_const (
    284             "editable_defaults",
    285             &wtgr.details.ok.editable_defaults),
    286           NULL),
    287         GNUNET_JSON_spec_mark_optional (
    288           GNUNET_JSON_spec_string (
    289             "required_currency",
    290             &wtgr.details.ok.required_currency),
    291           NULL),
    292         GNUNET_JSON_spec_mark_optional (
    293           GNUNET_JSON_spec_object_const ("merchant",
    294                                          &merchant_object),
    295           &merchant_missing),
    296         GNUNET_JSON_spec_mark_optional (
    297           GNUNET_JSON_spec_fixed_auto ("merchant_pub",
    298                                        &merchant_pub),
    299           &merchant_pub_missing),
    300         GNUNET_JSON_spec_mark_optional (
    301           GNUNET_JSON_spec_array_const ("exchange_candidates",
    302                                         &exchange_candidates),
    303           &exchange_candidates_missing),
    304         GNUNET_JSON_spec_end ()
    305       };
    306 
    307       if (GNUNET_OK !=
    308           GNUNET_JSON_parse (json,
    309                              spec,
    310                              NULL, NULL))
    311       {
    312         wtgr.hr.http_status = 0;
    313         wtgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    314         break;
    315       }
    316       {
    317         if (merchant_missing != merchant_pub_missing)
    318         {
    319           wtgr.hr.http_status = 0;
    320           wtgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    321           break;
    322         }
    323         if (! merchant_missing)
    324         {
    325           if (GNUNET_OK !=
    326               parse_merchant_metadata (merchant_object,
    327                                        &merchant))
    328           {
    329             wtgr.hr.http_status = 0;
    330             wtgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    331             break;
    332           }
    333           wtgr.details.ok.merchant = &merchant;
    334           wtgr.details.ok.merchant_pub = &merchant_pub;
    335         }
    336       }
    337       if (! exchange_candidates_missing)
    338       {
    339         if (GNUNET_OK !=
    340             parse_exchange_candidates (exchange_candidates,
    341                                        &num_candidates,
    342                                        &candidates))
    343         {
    344           wtgr.hr.http_status = 0;
    345           wtgr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    346           break;
    347         }
    348         wtgr.details.ok.exchange_candidates_provided = true;
    349         wtgr.details.ok.num_exchange_candidates = num_candidates;
    350         wtgr.details.ok.exchange_candidates = candidates;
    351       }
    352       break;
    353     }
    354   case MHD_HTTP_UNAUTHORIZED:
    355     wtgr.hr.ec = TALER_JSON_get_error_code (json);
    356     wtgr.hr.hint = TALER_JSON_get_error_hint (json);
    357     break;
    358   case MHD_HTTP_NOT_FOUND:
    359     wtgr.hr.ec = TALER_JSON_get_error_code (json);
    360     wtgr.hr.hint = TALER_JSON_get_error_hint (json);
    361     break;
    362   default:
    363     wtgr.hr.ec = TALER_JSON_get_error_code (json);
    364     wtgr.hr.hint = TALER_JSON_get_error_hint (json);
    365     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    366                 "Unexpected response code %u/%d\n",
    367                 (unsigned int) response_code,
    368                 (int) wtgr.hr.ec);
    369     break;
    370   }
    371   gth->cb (gth->cb_cls,
    372            &wtgr);
    373   free_exchange_candidates (num_candidates,
    374                             candidates);
    375   free_merchant_metadata (&merchant);
    376   TALER_MERCHANT_get_templates_cancel (gth);
    377 }
    378 
    379 
    380 struct TALER_MERCHANT_GetTemplatesHandle *
    381 TALER_MERCHANT_get_templates_create (
    382   struct GNUNET_CURL_Context *ctx,
    383   const char *url,
    384   const char *template_id)
    385 {
    386   struct TALER_MERCHANT_GetTemplatesHandle *gth;
    387 
    388   gth = GNUNET_new (struct TALER_MERCHANT_GetTemplatesHandle);
    389   gth->ctx = ctx;
    390   gth->base_url = GNUNET_strdup (url);
    391   gth->template_id = GNUNET_strdup (template_id);
    392   return gth;
    393 }
    394 
    395 
    396 enum TALER_ErrorCode
    397 TALER_MERCHANT_get_templates_start (
    398   struct TALER_MERCHANT_GetTemplatesHandle *gth,
    399   TALER_MERCHANT_GetTemplatesCallback cb,
    400   TALER_MERCHANT_GET_TEMPLATES_RESULT_CLOSURE *cb_cls)
    401 {
    402   CURL *eh;
    403 
    404   gth->cb = cb;
    405   gth->cb_cls = cb_cls;
    406   {
    407     char *path;
    408 
    409     GNUNET_asprintf (&path,
    410                      "templates/%s",
    411                      gth->template_id);
    412     gth->url = TALER_url_join (gth->base_url,
    413                                path,
    414                                NULL);
    415     GNUNET_free (path);
    416   }
    417   if (NULL == gth->url)
    418     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    419   eh = TALER_MERCHANT_curl_easy_get_ (gth->url);
    420   if (NULL == eh)
    421     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    422   gth->job = GNUNET_CURL_job_add (gth->ctx,
    423                                   eh,
    424                                   &handle_get_template_finished,
    425                                   gth);
    426   if (NULL == gth->job)
    427     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    428   return TALER_EC_NONE;
    429 }
    430 
    431 
    432 void
    433 TALER_MERCHANT_get_templates_cancel (
    434   struct TALER_MERCHANT_GetTemplatesHandle *gth)
    435 {
    436   if (NULL != gth->job)
    437   {
    438     GNUNET_CURL_job_cancel (gth->job);
    439     gth->job = NULL;
    440   }
    441   GNUNET_free (gth->url);
    442   GNUNET_free (gth->base_url);
    443   GNUNET_free (gth->template_id);
    444   GNUNET_free (gth);
    445 }
    446 
    447 
    448 /* end of merchant_api_get-templates-TEMPLATE_ID-new.c */