merchant

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

template_parse.c (14871B)


      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 /**
    251  * Check that the (optional) @e min_amount and @e max_amount of @a tc are
    252  * usable: they must be denominated in the same currency, which must be
    253  * the currency of the template (which then must be given, as otherwise we
    254  * could not tell in which currency to enforce the limits), and they must
    255  * not exclude each other.
    256  *
    257  * @param tc template contract to check
    258  * @param[out] error_name error description
    259  * @return #GNUNET_OK on success, #GNUNET_SYSERR on validation failure
    260  */
    261 static enum GNUNET_GenericReturnValue
    262 check_amount_limits (const struct TALER_MERCHANT_TemplateContract *tc,
    263                      const char **error_name)
    264 {
    265   if (tc->no_min_amount &&
    266       tc->no_max_amount)
    267     return GNUNET_OK;
    268   if ( (! tc->no_min_amount) &&
    269        (! tc->no_max_amount) )
    270   {
    271     /* Must be checked before the two are compared, as comparing
    272        amounts of different currencies fails an assertion. Note that
    273        we deliberately do not rely on both having been checked against
    274        the currency of the template here. */
    275     if (GNUNET_YES !=
    276         TALER_amount_cmp_currency (&tc->min_amount,
    277                                    &tc->max_amount))
    278     {
    279       GNUNET_break_op (0);
    280       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    281                   "min_amount and max_amount must use the same currency\n");
    282       if (NULL != error_name)
    283         *error_name = "max_amount";
    284       return GNUNET_SYSERR;
    285     }
    286     if (0 < TALER_amount_cmp (&tc->min_amount,
    287                               &tc->max_amount))
    288     {
    289       GNUNET_break_op (0);
    290       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    291                   "min_amount exceeds max_amount\n");
    292       if (NULL != error_name)
    293         *error_name = "min_amount";
    294       return GNUNET_SYSERR;
    295     }
    296   }
    297   if (NULL == tc->currency)
    298   {
    299     GNUNET_break_op (0);
    300     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    301                 "Template with min_amount or max_amount must specify a currency\n");
    302     if (NULL != error_name)
    303       *error_name = "currency";
    304     return GNUNET_SYSERR;
    305   }
    306   if ( (! tc->no_min_amount) &&
    307        (0 != strcasecmp (tc->currency,
    308                          tc->min_amount.currency)) )
    309   {
    310     GNUNET_break_op (0);
    311     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    312                 "min_amount must use the currency of the template (%s)\n",
    313                 tc->currency);
    314     if (NULL != error_name)
    315       *error_name = "min_amount";
    316     return GNUNET_SYSERR;
    317   }
    318   if ( (! tc->no_max_amount) &&
    319        (0 != strcasecmp (tc->currency,
    320                          tc->max_amount.currency)) )
    321   {
    322     GNUNET_break_op (0);
    323     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    324                 "max_amount must use the currency of the template (%s)\n",
    325                 tc->currency);
    326     if (NULL != error_name)
    327       *error_name = "max_amount";
    328     return GNUNET_SYSERR;
    329   }
    330   return GNUNET_OK;
    331 }
    332 
    333 
    334 enum GNUNET_GenericReturnValue
    335 TALER_MERCHANT_template_contract_parse (
    336   const json_t *template_contract,
    337   struct TALER_MERCHANT_TemplateContract *out,
    338   const char **error_name)
    339 {
    340   const char *template_type_str = NULL;
    341   struct GNUNET_JSON_Specification spec[] = {
    342     GNUNET_JSON_spec_mark_optional (
    343       GNUNET_JSON_spec_string ("template_type",
    344                                &template_type_str),
    345       NULL),
    346     GNUNET_JSON_spec_mark_optional (
    347       GNUNET_JSON_spec_string ("summary",
    348                                &out->summary),
    349       NULL),
    350     GNUNET_JSON_spec_mark_optional (
    351       GNUNET_JSON_spec_string ("currency",
    352                                &out->currency),
    353       NULL),
    354     GNUNET_JSON_spec_mark_optional (
    355       TALER_JSON_spec_amount_any ("amount",
    356                                   &out->amount),
    357       &out->no_amount),
    358     GNUNET_JSON_spec_mark_optional (
    359       TALER_JSON_spec_amount_any ("min_amount",
    360                                   &out->min_amount),
    361       &out->no_min_amount),
    362     GNUNET_JSON_spec_mark_optional (
    363       TALER_JSON_spec_amount_any ("max_amount",
    364                                   &out->max_amount),
    365       &out->no_max_amount),
    366     GNUNET_JSON_spec_mark_optional (
    367       GNUNET_JSON_spec_uint32 ("minimum_age",
    368                                &out->minimum_age),
    369       NULL),
    370     GNUNET_JSON_spec_mark_optional (
    371       GNUNET_JSON_spec_relative_time ("pay_duration",
    372                                       &out->pay_duration),
    373       NULL),
    374     GNUNET_JSON_spec_mark_optional (
    375       GNUNET_JSON_spec_relative_time ("max_pickup_duration",
    376                                       &out->max_pickup_duration),
    377       NULL),
    378     GNUNET_JSON_spec_mark_optional (
    379       GNUNET_JSON_spec_bool ("request_tip",
    380                              &out->request_tip),
    381       NULL),
    382     GNUNET_JSON_spec_end ()
    383   };
    384   const char *en;
    385 
    386   if (NULL == template_contract)
    387   {
    388     if (NULL != error_name)
    389       *error_name = "template_contract is NULL";
    390     return GNUNET_SYSERR;
    391   }
    392   out->max_pickup_duration = GNUNET_TIME_UNIT_FOREVER_REL;
    393   out->no_min_amount = true;
    394   out->no_max_amount = true;
    395   if (GNUNET_OK !=
    396       GNUNET_JSON_parse ((json_t *) template_contract,
    397                          spec,
    398                          &en,
    399                          NULL))
    400   {
    401     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    402                 "Invalid input for field %s\n",
    403                 en);
    404     if (NULL != error_name)
    405       *error_name = en;
    406     return GNUNET_SYSERR;
    407   }
    408   if (GNUNET_OK !=
    409       check_amount_limits (out,
    410                            error_name))
    411     return GNUNET_SYSERR;
    412 
    413   out->type = TALER_MERCHANT_template_type_from_string (template_type_str);
    414   if (TALER_MERCHANT_TEMPLATE_TYPE_INVALID == out->type)
    415   {
    416     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    417                 "Invalid template_type used '%s'\n",
    418                 template_type_str);
    419     if (NULL != error_name)
    420       *error_name = "Invalid template_type used";
    421     return GNUNET_SYSERR;
    422   }
    423 
    424   /* Parse additional fields for each specific type */
    425   switch (out->type)
    426   {
    427   case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
    428     return GNUNET_OK;
    429   case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
    430     return parse_template_inventory (template_contract,
    431                                      out,
    432                                      error_name);
    433   case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
    434     return parse_template_paivana (template_contract,
    435                                    out,
    436                                    error_name);
    437   case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
    438     break;
    439   }
    440 
    441   /* I think we are never supposed to reach it */
    442   GNUNET_break_op (0);
    443   if (NULL != error_name)
    444     *error_name = "template_type";
    445   return GNUNET_SYSERR;
    446 }
    447 
    448 
    449 void
    450 TALER_MERCHANT_template_contract_free (
    451   struct TALER_MERCHANT_TemplateContract *tc)
    452 {
    453   switch (tc->type)
    454   {
    455   case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
    456     return;
    457   case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
    458     return;
    459   case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
    460     for (unsigned int i = 0; i<tc->details.paivana.choices_len; i++)
    461       TALER_MERCHANT_order_choice_free (&tc->details.paivana.choices[i]);
    462     GNUNET_array_grow (tc->details.paivana.choices,
    463                        tc->details.paivana.choices_len,
    464                        0);
    465     return;
    466   case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
    467     return;
    468   }
    469 }
    470 
    471 
    472 bool
    473 TALER_MERCHANT_template_contract_valid (const json_t *template_contract)
    474 {
    475   struct TALER_MERCHANT_TemplateContract tmp;
    476   bool ret;
    477 
    478   memset (&tmp,
    479           0,
    480           sizeof (tmp));
    481   ret = (GNUNET_OK ==
    482          TALER_MERCHANT_template_contract_parse (template_contract,
    483                                                  &tmp,
    484                                                  NULL));
    485   TALER_MERCHANT_template_contract_free (&tmp);
    486   return ret;
    487 }