merchant

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

template_parse.c (11528B)


      1 /*
      2   This file is part of TALER
      3   (C) 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 Lesser 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/util/template_parse.c
     18  * @brief shared logic for template contract parsing
     19  * @author Bohdan Potuzhnyi
     20  */
     21 #include "platform.h"
     22 #include <gnunet/gnunet_common.h>
     23 #include <gnunet/gnunet_json_lib.h>
     24 #include <jansson.h>
     25 #include <string.h>
     26 #include <taler/taler_json_lib.h>
     27 #include <taler/taler_util.h>
     28 #include "taler/taler_merchant_util.h"
     29 #include <regex.h>
     30 
     31 
     32 enum TALER_MERCHANT_TemplateType
     33 TALER_MERCHANT_template_type_from_contract (const json_t *template_contract)
     34 {
     35   const json_t *type_val;
     36 
     37   if (NULL == template_contract)
     38     return TALER_MERCHANT_TEMPLATE_TYPE_INVALID;
     39 
     40   type_val = json_object_get (template_contract,
     41                               "template_type");
     42 
     43   if (NULL == type_val)
     44   {
     45     /* missing => fixed order */
     46     return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER;
     47   }
     48   if (! json_is_string (type_val))
     49   {
     50     GNUNET_break_op (0);
     51     return TALER_MERCHANT_TEMPLATE_TYPE_INVALID;
     52   }
     53 
     54   return TALER_MERCHANT_template_type_from_string (
     55     json_string_value (type_val));
     56 }
     57 
     58 
     59 enum TALER_MERCHANT_TemplateType
     60 TALER_MERCHANT_template_type_from_string (const char *template_type)
     61 {
     62   if (NULL == template_type)
     63     return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER;
     64   if (0 == strcmp (template_type,
     65                    "fixed-order"))
     66     return TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER;
     67   if (0 == strcmp (template_type,
     68                    "inventory-cart"))
     69     return TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART;
     70   if (0 == strcmp (template_type,
     71                    "paivana"))
     72     return TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA;
     73   return TALER_MERCHANT_TEMPLATE_TYPE_INVALID;
     74 }
     75 
     76 
     77 const char *
     78 TALER_MERCHANT_template_type_to_string (
     79   enum TALER_MERCHANT_TemplateType template_type)
     80 {
     81   switch (template_type)
     82   {
     83   case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
     84     return "fixed-order";
     85   case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
     86     return "inventory-cart";
     87   case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
     88     return "paivana";
     89   case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
     90     break;
     91   }
     92   return NULL;
     93 }
     94 
     95 
     96 /**
     97  * Parse inventory-specific fields from a template contract.
     98  *
     99  * @param template_contract json
    100  * @param[out] out where to write parsed fields
    101  * @param[out] error_name error description
    102  * @return #GNUNET_OK on success, #GNUNET_SYSERR on parse/validation failure
    103  */
    104 static enum GNUNET_GenericReturnValue
    105 parse_template_inventory (const json_t *template_contract,
    106                           struct TALER_MERCHANT_TemplateContract *out,
    107                           const char **error_name)
    108 {
    109   struct GNUNET_JSON_Specification spec[] = {
    110     GNUNET_JSON_spec_mark_optional (
    111       GNUNET_JSON_spec_bool (
    112         "selected_all",
    113         &out->details.inventory.selected_all),
    114       NULL),
    115     GNUNET_JSON_spec_mark_optional (
    116       GNUNET_JSON_spec_array_const (
    117         "selected_categories",
    118         &out->details.inventory.selected_categories)
    119       ,
    120       NULL),
    121     GNUNET_JSON_spec_mark_optional (
    122       GNUNET_JSON_spec_array_const (
    123         "selected_products",
    124         &out->details.inventory.selected_products),
    125       NULL),
    126     GNUNET_JSON_spec_mark_optional (
    127       GNUNET_JSON_spec_bool (
    128         "choose_one",
    129         &out->details.inventory.choose_one),
    130       NULL),
    131     GNUNET_JSON_spec_end ()
    132   };
    133   const char *en;
    134 
    135   if (GNUNET_OK !=
    136       GNUNET_JSON_parse ((json_t *) template_contract,
    137                          spec,
    138                          &en,
    139                          NULL))
    140   {
    141     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    142                 "Invalid inventory template_contract for field %s\n",
    143                 en);
    144     if (NULL != error_name)
    145       *error_name = en;
    146     return GNUNET_SYSERR;
    147   }
    148 
    149   if (NULL != out->details.inventory.selected_categories)
    150   {
    151     const json_t *entry;
    152     size_t idx;
    153 
    154     json_array_foreach ((json_t *) out->details.inventory.selected_categories,
    155                         idx,
    156                         entry)
    157     {
    158       if ( (! json_is_integer (entry)) ||
    159            (0 > json_integer_value (entry)) )
    160       {
    161         GNUNET_break_op (0);
    162         if (NULL != error_name)
    163           *error_name = "selected_categories";
    164         return GNUNET_SYSERR;
    165       }
    166     }
    167   }
    168 
    169   if (NULL != out->details.inventory.selected_products)
    170   {
    171     const json_t *entry;
    172     size_t idx;
    173 
    174     json_array_foreach ((json_t *) out->details.inventory.selected_products,
    175                         idx,
    176                         entry)
    177     {
    178       if (! json_is_string (entry))
    179       {
    180         GNUNET_break_op (0);
    181         if (NULL != error_name)
    182           *error_name = "selected_products";
    183         return GNUNET_SYSERR;
    184       }
    185     }
    186   }
    187   return GNUNET_OK;
    188 }
    189 
    190 
    191 /**
    192  * Parse paivana-specific fields from a template contract.
    193  *
    194  * @param template_contract json
    195  * @param[out] out where to write parsed fields
    196  * @param[out] error_name error description
    197  * @return #GNUNET_OK on success, #GNUNET_SYSERR on parse/validation failure
    198  */
    199 static enum GNUNET_GenericReturnValue
    200 parse_template_paivana (const json_t *template_contract,
    201                         struct TALER_MERCHANT_TemplateContract *out,
    202                         const char **error_name)
    203 {
    204   struct GNUNET_JSON_Specification spec[] = {
    205     GNUNET_JSON_spec_mark_optional (
    206       GNUNET_JSON_spec_string ("website_regex",
    207                                &out->details.paivana.website_regex),
    208       NULL),
    209     TALER_MERCHANT_spec_order_choices ("choices",
    210                                        &out->details.paivana.choices,
    211                                        &out->details.paivana.choices_len),
    212     GNUNET_JSON_spec_end ()
    213   };
    214   const char *en;
    215 
    216   if (GNUNET_OK !=
    217       GNUNET_JSON_parse ((json_t *) template_contract,
    218                          spec,
    219                          &en,
    220                          NULL))
    221   {
    222     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    223                 "Invalid paivana template_contract for field %s\n",
    224                 en);
    225     if (NULL != error_name)
    226       *error_name = en;
    227     return GNUNET_SYSERR;
    228   }
    229   if (NULL != out->details.paivana.website_regex)
    230   {
    231     regex_t ex;
    232 
    233     if (0 != regcomp (&ex,
    234                       out->details.paivana.website_regex,
    235                       REG_NOSUB | REG_EXTENDED))
    236     {
    237       GNUNET_break_op (0);
    238       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    239                   "Invalid paivana website_regex given\n");
    240       if (NULL != error_name)
    241         *error_name = "Invalid website_regex given";
    242       return GNUNET_SYSERR;
    243     }
    244     regfree (&ex);
    245   }
    246   return GNUNET_OK;
    247 }
    248 
    249 
    250 enum GNUNET_GenericReturnValue
    251 TALER_MERCHANT_template_contract_parse (
    252   const json_t *template_contract,
    253   struct TALER_MERCHANT_TemplateContract *out,
    254   const char **error_name)
    255 {
    256   const char *template_type_str = NULL;
    257   struct GNUNET_JSON_Specification spec[] = {
    258     GNUNET_JSON_spec_mark_optional (
    259       GNUNET_JSON_spec_string ("template_type",
    260                                &template_type_str),
    261       NULL),
    262     GNUNET_JSON_spec_mark_optional (
    263       GNUNET_JSON_spec_string ("summary",
    264                                &out->summary),
    265       NULL),
    266     GNUNET_JSON_spec_mark_optional (
    267       GNUNET_JSON_spec_string ("currency",
    268                                &out->currency),
    269       NULL),
    270     GNUNET_JSON_spec_mark_optional (
    271       TALER_JSON_spec_amount_any ("amount",
    272                                   &out->amount),
    273       &out->no_amount),
    274     GNUNET_JSON_spec_mark_optional (
    275       GNUNET_JSON_spec_uint32 ("minimum_age",
    276                                &out->minimum_age),
    277       NULL),
    278     GNUNET_JSON_spec_mark_optional (
    279       GNUNET_JSON_spec_relative_time ("pay_duration",
    280                                       &out->pay_duration),
    281       NULL),
    282     GNUNET_JSON_spec_mark_optional (
    283       GNUNET_JSON_spec_relative_time ("max_pickup_duration",
    284                                       &out->max_pickup_duration),
    285       NULL),
    286     GNUNET_JSON_spec_mark_optional (
    287       GNUNET_JSON_spec_bool ("request_tip",
    288                              &out->request_tip),
    289       NULL),
    290     GNUNET_JSON_spec_end ()
    291   };
    292   const char *en;
    293 
    294   if (NULL == template_contract)
    295   {
    296     if (NULL != error_name)
    297       *error_name = "template_contract is NULL";
    298     return GNUNET_SYSERR;
    299   }
    300   out->max_pickup_duration = GNUNET_TIME_UNIT_FOREVER_REL;
    301   if (GNUNET_OK !=
    302       GNUNET_JSON_parse ((json_t *) template_contract,
    303                          spec,
    304                          &en,
    305                          NULL))
    306   {
    307     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    308                 "Invalid input for field %s\n",
    309                 en);
    310     if (NULL != error_name)
    311       *error_name = en;
    312     return GNUNET_SYSERR;
    313   }
    314 
    315   out->type = TALER_MERCHANT_template_type_from_string (template_type_str);
    316   if (TALER_MERCHANT_TEMPLATE_TYPE_INVALID == out->type)
    317   {
    318     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    319                 "Invalid template_type used '%s'\n",
    320                 template_type_str);
    321     if (NULL != error_name)
    322       *error_name = "Invalid template_type used";
    323     return GNUNET_SYSERR;
    324   }
    325 
    326   /* Parse additional fields for each specific type */
    327   switch (out->type)
    328   {
    329   case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
    330     return GNUNET_OK;
    331   case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
    332     return parse_template_inventory (template_contract,
    333                                      out,
    334                                      error_name);
    335   case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
    336     return parse_template_paivana (template_contract,
    337                                    out,
    338                                    error_name);
    339   case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
    340     break;
    341   }
    342 
    343   /* I think we are never supposed to reach it */
    344   GNUNET_break_op (0);
    345   if (NULL != error_name)
    346     *error_name = "template_type";
    347   return GNUNET_SYSERR;
    348 }
    349 
    350 
    351 void
    352 TALER_MERCHANT_template_contract_free (
    353   struct TALER_MERCHANT_TemplateContract *tc)
    354 {
    355   switch (tc->type)
    356   {
    357   case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
    358     return;
    359   case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
    360     return;
    361   case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
    362     for (unsigned int i = 0; i<tc->details.paivana.choices_len; i++)
    363       TALER_MERCHANT_order_choice_free (&tc->details.paivana.choices[i]);
    364     GNUNET_array_grow (tc->details.paivana.choices,
    365                        tc->details.paivana.choices_len,
    366                        0);
    367     return;
    368   case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
    369     return;
    370   }
    371 }
    372 
    373 
    374 bool
    375 TALER_MERCHANT_template_contract_valid (const json_t *template_contract)
    376 {
    377   struct TALER_MERCHANT_TemplateContract tmp;
    378   bool ret;
    379 
    380   memset (&tmp,
    381           0,
    382           sizeof (tmp));
    383   ret = (GNUNET_OK ==
    384          TALER_MERCHANT_template_contract_parse (template_contract,
    385                                                  &tmp,
    386                                                  NULL));
    387   TALER_MERCHANT_template_contract_free (&tmp);
    388   return ret;
    389 }