merchant

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

order_parse.c (11419B)


      1 /*
      2   This file is part of TALER
      3   (C) 2024, 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/order_parse.c
     18  * @brief shared logic for order parsing
     19  * @author Iván Ávalos
     20  * @author Christian Grothoff
     21  */
     22 #include "platform.h"
     23 #include <gnunet/gnunet_common.h>
     24 #include <gnunet/gnunet_json_lib.h>
     25 #include <jansson.h>
     26 #include <stdbool.h>
     27 #include <stdint.h>
     28 #include <taler/taler_json_lib.h>
     29 #include <taler/taler_util.h>
     30 #include "taler/taler_merchant_util.h"
     31 
     32 
     33 /**
     34  * Maximum number of entries we accept in the @e products array of an
     35  * order.  The array is allocated from its JSON length before any entry
     36  * is parsed, and phase_merge_inventory() of POST /private/orders puts
     37  * @e products_len + 1 words on the stack, so the length must be bounded
     38  * independently of the maximum upload size.  Same limit as the
     39  * MAX_PRODUCTS that bounds the inventory_products of the same request.
     40  */
     41 #define MAX_PRODUCTS 1024
     42 
     43 
     44 /**
     45  * Parse v0-specific fields of @a input JSON into @a order.
     46  *
     47  * @param[in] input the JSON order terms
     48  * @param[out] order where to write the data
     49  * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
     50  */
     51 static enum GNUNET_GenericReturnValue
     52 parse_order_v0 (
     53   json_t *input,
     54   struct TALER_MERCHANT_Order *order)
     55 {
     56   struct GNUNET_JSON_Specification espec[] = {
     57     TALER_JSON_spec_amount_any ("amount",
     58                                 &order->details.v0.brutto),
     59     GNUNET_JSON_spec_mark_optional (
     60       TALER_JSON_spec_amount_any ("tip",
     61                                   &order->details.v0.tip),
     62       &order->details.v0.no_tip),
     63     GNUNET_JSON_spec_mark_optional (
     64       TALER_JSON_spec_amount_any ("max_fee",
     65                                   &order->details.v0.max_fee),
     66       &order->details.v0.no_max_fee),
     67     GNUNET_JSON_spec_end ()
     68   };
     69   enum GNUNET_GenericReturnValue res;
     70   const char *ename;
     71   unsigned int eline;
     72 
     73   res = GNUNET_JSON_parse (input,
     74                            espec,
     75                            &ename,
     76                            &eline);
     77   if (GNUNET_OK != res)
     78   {
     79     GNUNET_break (0);
     80     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     81                 "Failed to parse order v0 at field %s\n",
     82                 ename);
     83     return GNUNET_SYSERR;
     84   }
     85 
     86   if ( (! order->details.v0.no_max_fee) &&
     87        (GNUNET_OK !=
     88         TALER_amount_cmp_currency (&order->details.v0.max_fee,
     89                                    &order->details.v0.brutto)) )
     90   {
     91     GNUNET_break (0);
     92     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     93                 "'max_fee' does not match currency of order price");
     94     return GNUNET_SYSERR;
     95   }
     96   if ( (! order->details.v0.no_tip) &&
     97        (GNUNET_OK !=
     98         TALER_amount_cmp_currency (&order->details.v0.tip,
     99                                    &order->details.v0.brutto)) )
    100   {
    101     GNUNET_break (0);
    102     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    103                 "'tip' does not match currency of order price");
    104     return GNUNET_SYSERR;
    105   }
    106   if (NULL != order->base->amount_external)
    107   {
    108     if (! TALER_MERCHANT_amount_external_valid (
    109           order->base->amount_external))
    110     {
    111       GNUNET_break_op (0);
    112       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    113                   "'amount_external' is not a valid set of external payments");
    114       return GNUNET_SYSERR;
    115     }
    116     if (! TALER_MERCHANT_amount_external_currency_valid (
    117           order->base->amount_external,
    118           &order->details.v0.brutto))
    119     {
    120       GNUNET_break_op (0);
    121       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    122                   "'amount_external' does not match currency of order price");
    123       return GNUNET_SYSERR;
    124     }
    125     if (! TALER_MERCHANT_amount_external_total_valid (
    126           order->base->amount_external,
    127           &order->details.v0.brutto))
    128     {
    129       GNUNET_break_op (0);
    130       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    131                   "total of 'amount_external' and order price is too large");
    132       return GNUNET_SYSERR;
    133     }
    134   }
    135 
    136   return res;
    137 }
    138 
    139 
    140 /**
    141  * Parse v1-specific fields of @a input JSON into @a order.
    142  *
    143  * @param[in] input the JSON order terms
    144  * @param[out] order where to write the data
    145  * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
    146  */
    147 static enum GNUNET_GenericReturnValue
    148 parse_order_v1 (
    149   json_t *input,
    150   struct TALER_MERCHANT_Order *order)
    151 {
    152   struct GNUNET_JSON_Specification espec[] = {
    153     TALER_MERCHANT_spec_order_choices (
    154       "choices",
    155       &order->details.v1.choices,
    156       &order->details.v1.choices_len),
    157     GNUNET_JSON_spec_end ()
    158   };
    159 
    160   enum GNUNET_GenericReturnValue res;
    161   const char *ename;
    162   unsigned int eline;
    163 
    164   res = GNUNET_JSON_parse (input,
    165                            espec,
    166                            &ename,
    167                            &eline);
    168   if (GNUNET_OK != res)
    169   {
    170     GNUNET_break (0);
    171     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    172                 "Failed to parse order v1 at field %s\n",
    173                 ename);
    174     return GNUNET_SYSERR;
    175   }
    176   if (NULL != order->base->amount_external)
    177   {
    178     if (! TALER_MERCHANT_amount_external_valid (order->base->amount_external))
    179     {
    180       GNUNET_break_op (0);
    181       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    182                   "'amount_external' is not a valid set of external payments\n");
    183       return GNUNET_SYSERR;
    184     }
    185     for (unsigned int i = 0; i<order->details.v1.choices_len; i++)
    186     {
    187       if (! TALER_MERCHANT_amount_external_currency_valid (
    188             order->base->amount_external,
    189             &order->details.v1.choices[i].amount))
    190       {
    191         GNUNET_break_op (0);
    192         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    193                     "'amount_external' does not match currency of choice #%u\n",
    194                     i);
    195         return GNUNET_SYSERR;
    196       }
    197       if (! TALER_MERCHANT_amount_external_total_valid (
    198             order->base->amount_external,
    199             &order->details.v1.choices[i].amount))
    200       {
    201         GNUNET_break_op (0);
    202         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    203                     "total of 'amount_external' and choice #%u is too large\n",
    204                     i);
    205         return GNUNET_SYSERR;
    206       }
    207     }
    208   }
    209 
    210   return res;
    211 }
    212 
    213 
    214 struct TALER_MERCHANT_Order *
    215 TALER_MERCHANT_order_parse (json_t *input)
    216 {
    217   struct TALER_MERCHANT_Order *order
    218     = GNUNET_new (struct TALER_MERCHANT_Order);
    219   enum GNUNET_GenericReturnValue res
    220     = GNUNET_SYSERR;
    221 
    222   GNUNET_assert (NULL != input);
    223   order->base = TALER_MERCHANT_base_terms_parse (input);
    224   if (NULL == order->base)
    225   {
    226     GNUNET_break (0);
    227     GNUNET_free (order);
    228     return NULL;
    229   }
    230   order->refund_deadline = GNUNET_TIME_UNIT_FOREVER_TS;
    231   order->wire_transfer_deadline = GNUNET_TIME_UNIT_FOREVER_TS;
    232   {
    233     const json_t *products;
    234     struct GNUNET_JSON_Specification espec[] = {
    235       GNUNET_JSON_spec_mark_optional (
    236         GNUNET_JSON_spec_array_const ("products",
    237                                       &products),
    238         NULL),
    239       GNUNET_JSON_spec_mark_optional (
    240         TALER_JSON_spec_slug_copy ("order_id",
    241                                    &order->order_id),
    242         NULL),
    243       GNUNET_JSON_spec_mark_optional (
    244         GNUNET_JSON_spec_timestamp ("timestamp",
    245                                     &order->timestamp),
    246         NULL),
    247       GNUNET_JSON_spec_mark_optional (
    248         GNUNET_JSON_spec_timestamp ("refund_deadline",
    249                                     &order->refund_deadline),
    250         NULL),
    251       GNUNET_JSON_spec_mark_optional (
    252         GNUNET_JSON_spec_timestamp ("pay_deadline",
    253                                     &order->pay_deadline),
    254         NULL),
    255       GNUNET_JSON_spec_mark_optional (
    256         GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
    257                                     &order->wire_transfer_deadline),
    258         NULL),
    259       GNUNET_JSON_spec_end ()
    260     };
    261     const char *ename;
    262     unsigned int eline;
    263 
    264     res = GNUNET_JSON_parse (input,
    265                              espec,
    266                              &ename,
    267                              &eline);
    268     if (GNUNET_OK != res)
    269     {
    270       GNUNET_break_op (0);
    271       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    272                   "Failed to parse proto contract at field %s\n",
    273                   ename);
    274       goto cleanup;
    275     }
    276     if (NULL != products)
    277     {
    278       order->products_len = json_array_size (products);
    279       if (MAX_PRODUCTS < order->products_len)
    280       {
    281         /* 'products' is untrusted input; reject rather than abort in
    282            GNUNET_xmalloc_() (which asserts above 40 MiB). */
    283         GNUNET_break_op (0);
    284         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    285                     "Too many products in order (%u, at most %u allowed)\n",
    286                     (unsigned int) order->products_len,
    287                     (unsigned int) MAX_PRODUCTS);
    288         goto cleanup;
    289       }
    290       if (0 != order->products_len)
    291       {
    292         size_t i;
    293         json_t *p;
    294 
    295         order->products = GNUNET_new_array (
    296           order->products_len,
    297           struct TALER_MERCHANT_ProductSold);
    298         json_array_foreach (products, i, p)
    299         {
    300           if (GNUNET_OK !=
    301               TALER_MERCHANT_parse_product_sold (p,
    302                                                  &order->products[i],
    303                                                  true))
    304           {
    305             GNUNET_break (0);
    306             GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    307                         "Failed to parse product at offset %u\n",
    308                         (unsigned int) i);
    309             goto cleanup;
    310           }
    311         }
    312       }
    313     }
    314   }
    315   switch (order->base->version)
    316   {
    317   case TALER_MERCHANT_CONTRACT_VERSION_0:
    318     res = parse_order_v0 (input,
    319                           order);
    320     break;
    321   case TALER_MERCHANT_CONTRACT_VERSION_1:
    322     res = parse_order_v1 (input,
    323                           order);
    324     break;
    325   }
    326 
    327   if (GNUNET_OK != res)
    328   {
    329     GNUNET_break (0);
    330     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    331                 "Failed to parse order\n");
    332     goto cleanup;
    333   }
    334   return order;
    335 
    336 cleanup:
    337   TALER_MERCHANT_order_free (order);
    338   return NULL;
    339 }
    340 
    341 
    342 void
    343 TALER_MERCHANT_order_free (
    344   struct TALER_MERCHANT_Order *order)
    345 {
    346   if (NULL == order)
    347     return;
    348   if (NULL != order->products)
    349   {
    350     for (size_t i = 0; i<order->products_len; i++)
    351       TALER_MERCHANT_product_sold_free (&order->products[i]);
    352     GNUNET_free (order->products);
    353     order->products_len = 0;
    354   }
    355   GNUNET_free (order->order_id);
    356   if (NULL != order->base)
    357   {
    358     switch (order->base->version)
    359     {
    360     case TALER_MERCHANT_CONTRACT_VERSION_0:
    361       break;
    362     case TALER_MERCHANT_CONTRACT_VERSION_1:
    363       for (unsigned int i = 0;
    364            i < order->details.v1.choices_len;
    365            i++)
    366         TALER_MERCHANT_order_choice_free (&order->details.v1.choices[i]);
    367       GNUNET_free (order->details.v1.choices);
    368       break;
    369     }
    370     TALER_MERCHANT_base_terms_free (order->base);
    371     order->base = NULL;
    372   }
    373   GNUNET_free (order);
    374 }