merchant

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

merchant_api_post-private-templates.c (15369B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2022 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU Lesser General Public License as
      7   published by the Free Software Foundation; either version 2.1,
      8   or (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful,
     11   but WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU Lesser General Public License for more details.
     14 
     15   You should have received a copy of the GNU Lesser General
     16   Public License along with TALER; see the file COPYING.LGPL.
     17   If not, see <http://www.gnu.org/licenses/>
     18 */
     19 /**
     20  * @file merchant_api_post-private-templates.c
     21  * @brief Implementation of the POST /templates request
     22  *        of the merchant's HTTP API
     23  * @author Priscilla HUANG
     24  */
     25 #include "taler/platform.h"
     26 #include <curl/curl.h>
     27 #include <jansson.h>
     28 #include <microhttpd.h> /* just for HTTP status codes */
     29 #include <gnunet/gnunet_util_lib.h>
     30 #include "taler/taler_merchant_service.h"
     31 #include "merchant_api_curl_defaults.h"
     32 #include "merchant_api_common.h"
     33 #include <taler/taler_json_lib.h>
     34 #include <taler/taler_curl_lib.h>
     35 #include "taler/taler_merchant_util.h"
     36 
     37 /* FIXME: Bohdan is to stupid to figure out how util can be used here */
     38 static enum TALER_MERCHANT_TemplateType
     39 template_type_from_string (const char *template_type)
     40 {
     41   if (NULL == template_type)
     42     return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER;
     43   if (0 == strcmp (template_type,
     44                    "fixed-order"))
     45     return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER;
     46   if (0 == strcmp (template_type,
     47                    "inventory-cart"))
     48     return TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART;
     49   if (0 == strcmp (template_type,
     50                    "paivana"))
     51     return TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA;
     52   return TALER_MERCHANT_TEMPLATE_TYPE_INVALID;
     53 }
     54 
     55 
     56 /**
     57  * Validate a fixed-order template contract.
     58  *
     59  * @param template_contract JSON template contract
     60  * @param summary summary pointer
     61  * @param amount amount storage
     62  * @param minimum_age minimum age storage
     63  * @param pay_duration pay duration storage
     64  * @return true if valid
     65  */
     66 static bool
     67 validate_template_contract_fixed (const json_t *template_contract,
     68                                   const char **summary,
     69                                   struct TALER_Amount *amount,
     70                                   uint32_t *minimum_age,
     71                                   struct GNUNET_TIME_Relative *pay_duration)
     72 {
     73   struct GNUNET_JSON_Specification spec[] = {
     74     GNUNET_JSON_spec_mark_optional (
     75       GNUNET_JSON_spec_string ("summary",
     76                                summary),
     77       NULL),
     78     GNUNET_JSON_spec_mark_optional (
     79       TALER_JSON_spec_amount_any ("amount",
     80                                   amount),
     81       NULL),
     82     GNUNET_JSON_spec_uint32 ("minimum_age",
     83                              minimum_age),
     84     GNUNET_JSON_spec_relative_time ("pay_duration",
     85                                     pay_duration),
     86     GNUNET_JSON_spec_end ()
     87   };
     88   const char *ename;
     89   unsigned int eline;
     90 
     91   if (GNUNET_OK !=
     92       GNUNET_JSON_parse (template_contract,
     93                          spec,
     94                          &ename,
     95                          &eline))
     96   {
     97     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     98                 "Invalid template_contract for field %s\n",
     99                 ename);
    100     return false;
    101   }
    102   return true;
    103 }
    104 
    105 
    106 /**
    107  * Validate an inventory-cart template contract.
    108  *
    109  * @param template_contract JSON template contract
    110  * @param summary summary pointer
    111  * @param pay_duration pay duration storage
    112  * @return true if valid
    113  */
    114 static bool
    115 validate_template_contract_inventory (const json_t *template_contract,
    116                                       const char **summary,
    117                                       struct GNUNET_TIME_Relative *pay_duration)
    118 {
    119   bool selected_all = false;
    120   bool choose_one = false;
    121   bool request_tip = false;
    122   const json_t *selected_categories = NULL;
    123   const json_t *selected_products = NULL;
    124   struct GNUNET_JSON_Specification ispec[] = {
    125     GNUNET_JSON_spec_mark_optional (
    126       GNUNET_JSON_spec_string ("summary",
    127                                summary),
    128       NULL),
    129     GNUNET_JSON_spec_mark_optional (
    130       GNUNET_JSON_spec_bool ("request_tip",
    131                              &request_tip),
    132       NULL),
    133     GNUNET_JSON_spec_relative_time ("pay_duration",
    134                                     pay_duration),
    135     GNUNET_JSON_spec_mark_optional (
    136       GNUNET_JSON_spec_bool ("selected_all",
    137                              &selected_all),
    138       NULL),
    139     GNUNET_JSON_spec_mark_optional (
    140       GNUNET_JSON_spec_array_const ("selected_categories",
    141                                     &selected_categories),
    142       NULL),
    143     GNUNET_JSON_spec_mark_optional (
    144       GNUNET_JSON_spec_array_const ("selected_products",
    145                                     &selected_products),
    146       NULL),
    147     GNUNET_JSON_spec_mark_optional (
    148       GNUNET_JSON_spec_bool ("choose_one",
    149                              &choose_one),
    150       NULL),
    151     GNUNET_JSON_spec_end ()
    152   };
    153   const char *ename;
    154   unsigned int eline;
    155 
    156   (void) request_tip;
    157   (void) selected_all;
    158   (void) choose_one;
    159   (void) selected_categories;
    160   (void) selected_products;
    161   if (GNUNET_OK !=
    162       GNUNET_JSON_parse (template_contract,
    163                          ispec,
    164                          &ename,
    165                          &eline))
    166   {
    167     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    168                 "Invalid template_contract for field %s\n",
    169                 ename);
    170     return false;
    171   }
    172   return true;
    173 }
    174 
    175 
    176 /**
    177  * Validate a paivana template contract.
    178  *
    179  * @param template_contract JSON template contract
    180  * @param summary summary pointer
    181  * @param amount amount storage
    182  * @param minimum_age minimum age storage
    183  * @param pay_duration pay duration storage
    184  * @return true if valid
    185  */
    186 static bool
    187 validate_template_contract_paivana (const json_t *template_contract,
    188                                     const char **summary,
    189                                     struct TALER_Amount *amount,
    190                                     uint32_t *minimum_age,
    191                                     struct GNUNET_TIME_Relative *pay_duration)
    192 {
    193   /* TODO: PAIVANA validate paivana-specific fields beyond presence. */
    194   const char *paivana_id;
    195   struct GNUNET_JSON_Specification pspec[] = {
    196     GNUNET_JSON_spec_string ("paivana_id",
    197                              &paivana_id),
    198     GNUNET_JSON_spec_end ()
    199   };
    200   const char *ename;
    201   unsigned int eline;
    202 
    203   if (GNUNET_OK !=
    204       GNUNET_JSON_parse (template_contract,
    205                          pspec,
    206                          &ename,
    207                          &eline))
    208   {
    209     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    210                 "Invalid paivana template_contract for field %s\n",
    211                 ename);
    212     return false;
    213   }
    214 
    215   if (! validate_template_contract_fixed (template_contract,
    216                                           summary,
    217                                           amount,
    218                                           minimum_age,
    219                                           pay_duration))
    220     return false;
    221 
    222   (void) paivana_id;
    223   return true;
    224 }
    225 
    226 
    227 /**
    228  * Handle for a POST /templates/$ID operation.
    229  */
    230 struct TALER_MERCHANT_TemplatesPostHandle
    231 {
    232 
    233   /**
    234    * The url for this request.
    235    */
    236   char *url;
    237 
    238   /**
    239    * Handle for the request.
    240    */
    241   struct GNUNET_CURL_Job *job;
    242 
    243   /**
    244    * Function to call with the result.
    245    */
    246   TALER_MERCHANT_TemplatesPostCallback cb;
    247 
    248   /**
    249    * Closure for @a cb.
    250    */
    251   void *cb_cls;
    252 
    253   /**
    254    * Reference to the execution context.
    255    */
    256   struct GNUNET_CURL_Context *ctx;
    257 
    258   /**
    259    * Minor context that holds body and headers.
    260    */
    261   struct TALER_CURL_PostContext post_ctx;
    262 };
    263 
    264 
    265 /**
    266  * Function called when we're done processing the
    267  * HTTP POST /templates request.
    268  *
    269  * @param cls the `struct TALER_MERCHANT_TemplatesPostHandle`
    270  * @param response_code HTTP response code, 0 on error
    271  * @param response response body, NULL if not in JSON
    272  */
    273 static void
    274 handle_post_templates_finished (void *cls,
    275                                 long response_code,
    276                                 const void *response)
    277 {
    278   struct TALER_MERCHANT_TemplatesPostHandle *tph = cls;
    279   const json_t *json = response;
    280   struct TALER_MERCHANT_HttpResponse hr = {
    281     .http_status = (unsigned int) response_code,
    282     .reply = json
    283   };
    284 
    285   tph->job = NULL;
    286   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    287               "POST /templates completed with response code %u\n",
    288               (unsigned int) response_code);
    289   switch (response_code)
    290   {
    291   case 0:
    292     hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    293     break;
    294   case MHD_HTTP_NO_CONTENT:
    295     break;
    296   case MHD_HTTP_BAD_REQUEST:
    297     hr.ec = TALER_JSON_get_error_code (json);
    298     hr.hint = TALER_JSON_get_error_hint (json);
    299     /* This should never happen, either us
    300      * or the merchant is buggy (or API version conflict);
    301      * just pass JSON reply to the application */
    302     break;
    303   case MHD_HTTP_UNAUTHORIZED:
    304     hr.ec = TALER_JSON_get_error_code (json);
    305     hr.hint = TALER_JSON_get_error_hint (json);
    306     /* Nothing really to verify, merchant says we need to authenticate. */
    307     break;
    308   case MHD_HTTP_FORBIDDEN:
    309     hr.ec = TALER_JSON_get_error_code (json);
    310     hr.hint = TALER_JSON_get_error_hint (json);
    311     /* Nothing really to verify, merchant says we tried to abort the payment
    312      * after it was successful. We should pass the JSON reply to the
    313      * application */
    314     break;
    315   case MHD_HTTP_NOT_FOUND:
    316     hr.ec = TALER_JSON_get_error_code (json);
    317     hr.hint = TALER_JSON_get_error_hint (json);
    318     /* Nothing really to verify, this should never
    319        happen, we should pass the JSON reply to the
    320        application */
    321     break;
    322   case MHD_HTTP_CONFLICT:
    323     hr.ec = TALER_JSON_get_error_code (json);
    324     hr.hint = TALER_JSON_get_error_hint (json);
    325     break;
    326   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    327     hr.ec = TALER_JSON_get_error_code (json);
    328     hr.hint = TALER_JSON_get_error_hint (json);
    329     /* Server had an internal issue; we should retry,
    330        but this API leaves this to the application */
    331     break;
    332   default:
    333     TALER_MERCHANT_parse_error_details_ (json,
    334                                          response_code,
    335                                          &hr);
    336     /* unexpected response code */
    337     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    338                 "Unexpected response code %u/%d\n",
    339                 (unsigned int) response_code,
    340                 (int) hr.ec);
    341     GNUNET_break_op (0);
    342     break;
    343   }
    344   tph->cb (tph->cb_cls,
    345            &hr);
    346   TALER_MERCHANT_templates_post_cancel (tph);
    347 }
    348 
    349 
    350 static bool
    351 test_template_contract_valid (const json_t *template_contract)
    352 {
    353   const char *template_type = NULL;
    354   enum TALER_MERCHANT_TemplateType template_type_enum;
    355   struct GNUNET_JSON_Specification type_spec[] = {
    356     GNUNET_JSON_spec_mark_optional (
    357       GNUNET_JSON_spec_string ("template_type",
    358                                &template_type),
    359       NULL),
    360     GNUNET_JSON_spec_end ()
    361   };
    362   const char *summary;
    363   struct TALER_Amount amount = { .value = 0};
    364   uint32_t minimum_age = 0;
    365   struct GNUNET_TIME_Relative pay_duration = { 0 };
    366   const char *ename;
    367   unsigned int eline;
    368 
    369   if (GNUNET_OK !=
    370       GNUNET_JSON_parse (template_contract,
    371                          type_spec,
    372                          &ename,
    373                          &eline))
    374   {
    375     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    376                 "Invalid template_contract for field %s\n",
    377                 ename);
    378     return false;
    379   }
    380 
    381   template_type_enum = template_type_from_string (template_type);
    382   if (TALER_MERCHANT_TEMPLATE_TYPE_INVALID == template_type_enum)
    383   {
    384     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    385                 "Invalid template_type '%s'\n",
    386                 template_type);
    387     return false;
    388   }
    389 
    390   /* FIXME: Bohdan understands that links have to be changed, but worried, that can crash something */
    391   switch (template_type_enum)
    392   {
    393   case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
    394     return validate_template_contract_inventory (template_contract,
    395                                                  &summary,
    396                                                  &pay_duration);
    397   case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
    398     return validate_template_contract_fixed (template_contract,
    399                                              &summary,
    400                                              &amount,
    401                                              &minimum_age,
    402                                              &pay_duration);
    403   case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
    404     return validate_template_contract_paivana (template_contract,
    405                                                &summary,
    406                                                &amount,
    407                                                &minimum_age,
    408                                                &pay_duration);
    409   case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
    410     break;
    411   }
    412   return false;
    413 }
    414 
    415 
    416 struct TALER_MERCHANT_TemplatesPostHandle *
    417 TALER_MERCHANT_templates_post (
    418   struct GNUNET_CURL_Context *ctx,
    419   const char *backend_url,
    420   const char *template_id,
    421   const char *template_description,
    422   const char *otp_id,
    423   const json_t *template_contract,
    424   TALER_MERCHANT_TemplatesPostCallback cb,
    425   void *cb_cls)
    426 {
    427   struct TALER_MERCHANT_TemplatesPostHandle *tph;
    428   json_t *req_obj;
    429 
    430   if (! test_template_contract_valid (template_contract))
    431   {
    432     GNUNET_break (0);
    433     return NULL;
    434   }
    435   req_obj = GNUNET_JSON_PACK (
    436     GNUNET_JSON_pack_string ("template_id",
    437                              template_id),
    438     GNUNET_JSON_pack_string ("template_description",
    439                              template_description),
    440     GNUNET_JSON_pack_allow_null (
    441       GNUNET_JSON_pack_string ("otp_id",
    442                                otp_id)),
    443     GNUNET_JSON_pack_object_incref ("template_contract",
    444                                     (json_t *) template_contract));
    445   tph = GNUNET_new (struct TALER_MERCHANT_TemplatesPostHandle);
    446   tph->ctx = ctx;
    447   tph->cb = cb;
    448   tph->cb_cls = cb_cls;
    449   tph->url = TALER_url_join (backend_url,
    450                              "private/templates",
    451                              NULL);
    452   if (NULL == tph->url)
    453   {
    454     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    455                 "Could not construct request URL.\n");
    456     json_decref (req_obj);
    457     GNUNET_free (tph);
    458     return NULL;
    459   }
    460   {
    461     CURL *eh;
    462 
    463     eh = TALER_MERCHANT_curl_easy_get_ (tph->url);
    464     GNUNET_assert (GNUNET_OK ==
    465                    TALER_curl_easy_post (&tph->post_ctx,
    466                                          eh,
    467                                          req_obj));
    468     json_decref (req_obj);
    469     tph->job = GNUNET_CURL_job_add2 (ctx,
    470                                      eh,
    471                                      tph->post_ctx.headers,
    472                                      &handle_post_templates_finished,
    473                                      tph);
    474     GNUNET_assert (NULL != tph->job);
    475   }
    476   return tph;
    477 }
    478 
    479 
    480 void
    481 TALER_MERCHANT_templates_post_cancel (
    482   struct TALER_MERCHANT_TemplatesPostHandle *tph)
    483 {
    484   if (NULL != tph->job)
    485   {
    486     GNUNET_CURL_job_cancel (tph->job);
    487     tph->job = NULL;
    488   }
    489   TALER_curl_easy_post_finished (&tph->post_ctx);
    490   GNUNET_free (tph->url);
    491   GNUNET_free (tph);
    492 }
    493 
    494 
    495 /* end of merchant_api_post_templates.c */