merchant

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

template_parse.c (15381B)


      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 (out->minimum_age > UINT8_MAX)
    409   {
    410     /* Orders only support ages up to UINT8_MAX, see the "minimum_age"
    411        field of the contract base terms. */
    412     GNUNET_break_op (0);
    413     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    414                 "Invalid minimum_age %u in template\n",
    415                 (unsigned int) out->minimum_age);
    416     if (NULL != error_name)
    417       *error_name = "minimum_age";
    418     return GNUNET_SYSERR;
    419   }
    420   if (GNUNET_OK !=
    421       check_amount_limits (out,
    422                            error_name))
    423     return GNUNET_SYSERR;
    424 
    425   out->type = TALER_MERCHANT_template_type_from_string (template_type_str);
    426   if (TALER_MERCHANT_TEMPLATE_TYPE_INVALID == out->type)
    427   {
    428     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    429                 "Invalid template_type used '%s'\n",
    430                 template_type_str);
    431     if (NULL != error_name)
    432       *error_name = "Invalid template_type used";
    433     return GNUNET_SYSERR;
    434   }
    435 
    436   /* Parse additional fields for each specific type */
    437   switch (out->type)
    438   {
    439   case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
    440     return GNUNET_OK;
    441   case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
    442     return parse_template_inventory (template_contract,
    443                                      out,
    444                                      error_name);
    445   case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
    446     return parse_template_paivana (template_contract,
    447                                    out,
    448                                    error_name);
    449   case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
    450     break;
    451   }
    452 
    453   /* I think we are never supposed to reach it */
    454   GNUNET_break_op (0);
    455   if (NULL != error_name)
    456     *error_name = "template_type";
    457   return GNUNET_SYSERR;
    458 }
    459 
    460 
    461 void
    462 TALER_MERCHANT_template_contract_free (
    463   struct TALER_MERCHANT_TemplateContract *tc)
    464 {
    465   switch (tc->type)
    466   {
    467   case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
    468     return;
    469   case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
    470     return;
    471   case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
    472     for (unsigned int i = 0; i<tc->details.paivana.choices_len; i++)
    473       TALER_MERCHANT_order_choice_free (&tc->details.paivana.choices[i]);
    474     GNUNET_array_grow (tc->details.paivana.choices,
    475                        tc->details.paivana.choices_len,
    476                        0);
    477     return;
    478   case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
    479     return;
    480   }
    481 }
    482 
    483 
    484 bool
    485 TALER_MERCHANT_template_contract_valid (const json_t *template_contract)
    486 {
    487   struct TALER_MERCHANT_TemplateContract tmp;
    488   bool ret;
    489 
    490   memset (&tmp,
    491           0,
    492           sizeof (tmp));
    493   ret = (GNUNET_OK ==
    494          TALER_MERCHANT_template_contract_parse (template_contract,
    495                                                  &tmp,
    496                                                  NULL));
    497   if (ret &&
    498       GNUNET_TIME_relative_is_forever (tmp.pay_duration))
    499     ret = false;
    500   TALER_MERCHANT_template_contract_free (&tmp);
    501   return ret;
    502 }