merchant

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

contract_parse.c (12602B)


      1 /*
      2   This file is part of TALER
      3   (C) 2024, 2025, 2026 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/contract_parse.c
     18  * @brief shared logic for contract terms 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  * Parse v0-specific fields of @a input JSON into @a contract.
     35  *
     36  * @param[in] input the JSON contract terms
     37  * @param[out] contract where to write the data
     38  * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
     39  */
     40 static enum GNUNET_GenericReturnValue
     41 parse_contract_v0 (
     42   json_t *input,
     43   struct TALER_MERCHANT_ProtoContract *contract)
     44 {
     45   struct GNUNET_JSON_Specification espec[] = {
     46     TALER_JSON_spec_amount_any ("amount",
     47                                 &contract->details.v0.brutto),
     48     GNUNET_JSON_spec_mark_optional (
     49       TALER_JSON_spec_amount_any ("tip",
     50                                   &contract->details.v0.tip),
     51       &contract->details.v0.no_tip),
     52     TALER_JSON_spec_amount_any ("max_fee",
     53                                 &contract->details.v0.max_fee),
     54     GNUNET_JSON_spec_end ()
     55   };
     56   enum GNUNET_GenericReturnValue res;
     57   const char *ename;
     58   unsigned int eline;
     59 
     60   res = GNUNET_JSON_parse (input,
     61                            espec,
     62                            &ename,
     63                            &eline);
     64   if (GNUNET_OK != res)
     65   {
     66     GNUNET_break (0);
     67     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     68                 "Failed to parse contract v0 at field %s\n",
     69                 ename);
     70     return GNUNET_SYSERR;
     71   }
     72 
     73   if (GNUNET_OK !=
     74       TALER_amount_cmp_currency (&contract->details.v0.max_fee,
     75                                  &contract->details.v0.brutto))
     76   {
     77     GNUNET_break (0);
     78     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     79                 "'max_fee' does not match currency of contract price");
     80     return GNUNET_SYSERR;
     81   }
     82   if ( (! contract->details.v0.no_tip) &&
     83        (GNUNET_OK !=
     84         TALER_amount_cmp_currency (&contract->details.v0.tip,
     85                                    &contract->details.v0.brutto)) )
     86   {
     87     GNUNET_break (0);
     88     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     89                 "'tip' does not match currency of contract price");
     90     return GNUNET_SYSERR;
     91   }
     92   if (NULL != contract->base->amount_external)
     93   {
     94     if (! TALER_MERCHANT_amount_external_valid (
     95           contract->base->amount_external))
     96     {
     97       GNUNET_break_op (0);
     98       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     99                   "'amount_external' is not a valid set of external payments");
    100       return GNUNET_SYSERR;
    101     }
    102     if (! TALER_MERCHANT_amount_external_currency_valid (
    103           contract->base->amount_external,
    104           &contract->details.v0.brutto))
    105     {
    106       GNUNET_break_op (0);
    107       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    108                   "'amount_external' does not match currency of contract price");
    109       return GNUNET_SYSERR;
    110     }
    111     if (! TALER_MERCHANT_amount_external_total_valid (
    112           contract->base->amount_external,
    113           &contract->details.v0.brutto))
    114     {
    115       GNUNET_break_op (0);
    116       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    117                   "total of 'amount_external' and contract price is too large");
    118       return GNUNET_SYSERR;
    119     }
    120   }
    121 
    122   return res;
    123 }
    124 
    125 
    126 /**
    127  * Parse v1-specific fields of @a input JSON into @a pc.
    128  *
    129  * @param[in] input the JSON contract terms
    130  * @param[out] pc where to write the data
    131  * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
    132  */
    133 static enum GNUNET_GenericReturnValue
    134 parse_contract_v1 (
    135   json_t *input,
    136   struct TALER_MERCHANT_ProtoContract *pc)
    137 {
    138   struct GNUNET_JSON_Specification espec[] = {
    139     TALER_MERCHANT_spec_contract_choices (
    140       "choices",
    141       &pc->details.v1.choices,
    142       &pc->details.v1.choices_len),
    143     TALER_MERCHANT_spec_token_families (
    144       "token_families",
    145       &pc->details.v1.token_authorities,
    146       &pc->details.v1.token_authorities_len),
    147     GNUNET_JSON_spec_end ()
    148   };
    149 
    150   enum GNUNET_GenericReturnValue res;
    151   const char *ename;
    152   unsigned int eline;
    153 
    154   res = GNUNET_JSON_parse (input,
    155                            espec,
    156                            &ename,
    157                            &eline);
    158   if (GNUNET_OK != res)
    159   {
    160     GNUNET_break (0);
    161     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    162                 "Failed to parse contract v1 at field %s\n",
    163                 ename);
    164     return GNUNET_SYSERR;
    165   }
    166   if (NULL != pc->base->amount_external)
    167   {
    168     if (! TALER_MERCHANT_amount_external_valid (pc->base->amount_external))
    169     {
    170       GNUNET_break_op (0);
    171       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    172                   "'amount_external' is not a valid set of external payments\n");
    173       return GNUNET_SYSERR;
    174     }
    175     for (unsigned int i = 0; i<pc->details.v1.choices_len; i++)
    176     {
    177       if (! TALER_MERCHANT_amount_external_currency_valid (
    178             pc->base->amount_external,
    179             &pc->details.v1.choices[i].amount))
    180       {
    181         GNUNET_break_op (0);
    182         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    183                     "'amount_external' does not match currency of choice #%u\n",
    184                     i);
    185         return GNUNET_SYSERR;
    186       }
    187       if (! TALER_MERCHANT_amount_external_total_valid (
    188             pc->base->amount_external,
    189             &pc->details.v1.choices[i].amount))
    190       {
    191         GNUNET_break_op (0);
    192         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    193                     "total of 'amount_external' and choice #%u is too large\n",
    194                     i);
    195         return GNUNET_SYSERR;
    196       }
    197     }
    198   }
    199 
    200   return res;
    201 }
    202 
    203 
    204 struct TALER_MERCHANT_ProtoContract *
    205 TALER_MERCHANT_proto_contract_parse (
    206   json_t *input)
    207 {
    208   struct TALER_MERCHANT_ContractBaseTerms *base;
    209   struct TALER_MERCHANT_ProtoContract *pc;
    210   enum GNUNET_GenericReturnValue res;
    211 
    212   base = TALER_MERCHANT_base_terms_parse (input);
    213   if (NULL == base)
    214   {
    215     GNUNET_break_op (0);
    216     return NULL;
    217   }
    218   pc = GNUNET_new (struct TALER_MERCHANT_ProtoContract);
    219   pc->base = base;
    220   {
    221     const json_t *products = NULL;
    222     struct GNUNET_JSON_Specification espec[] = {
    223       TALER_JSON_spec_slug_copy ("order_id",
    224                                  &pc->order_id),
    225       GNUNET_JSON_spec_mark_optional (
    226         GNUNET_JSON_spec_array_const ("products",
    227                                       &products),
    228         NULL),
    229       GNUNET_JSON_spec_timestamp ("timestamp",
    230                                   &pc->timestamp),
    231       GNUNET_JSON_spec_timestamp ("refund_deadline",
    232                                   &pc->refund_deadline),
    233       GNUNET_JSON_spec_timestamp ("pay_deadline",
    234                                   &pc->pay_deadline),
    235       GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
    236                                   &pc->wire_deadline),
    237       GNUNET_JSON_spec_fixed_auto ("merchant_pub",
    238                                    &pc->merchant_pub),
    239       GNUNET_JSON_spec_fixed_auto ("h_wire",
    240                                    &pc->h_wire),
    241       TALER_JSON_spec_web_url_copy ("merchant_base_url",
    242                                     &pc->merchant_base_url),
    243       TALER_MERCHANT_spec_merchant_details ("merchant",
    244                                             &pc->merchant),
    245       GNUNET_JSON_spec_string_copy ("wire_method",
    246                                     &pc->wire_method),
    247       GNUNET_JSON_spec_array_copy ("exchanges",
    248                                    &pc->exchanges),
    249 
    250       GNUNET_JSON_spec_end ()
    251     };
    252     const char *ename;
    253     unsigned int eline;
    254 
    255     res = GNUNET_JSON_parse (input,
    256                              espec,
    257                              &ename,
    258                              &eline);
    259     if (GNUNET_OK != res)
    260     {
    261       GNUNET_break_op (0);
    262       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    263                   "Failed to parse proto contract at field %s\n",
    264                   ename);
    265       goto cleanup;
    266     }
    267     if (NULL != products)
    268     {
    269       pc->products_len = json_array_size (products);
    270       if (0 != pc->products_len)
    271       {
    272         size_t i;
    273         json_t *p;
    274 
    275         pc->products = GNUNET_new_array (
    276           pc->products_len,
    277           struct TALER_MERCHANT_ProductSold);
    278         json_array_foreach (products, i, p)
    279         {
    280           if (GNUNET_OK !=
    281               TALER_MERCHANT_parse_product_sold (p,
    282                                                  &pc->products[i],
    283                                                  false))
    284           {
    285             GNUNET_break (0);
    286             GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    287                         "Failed to parse product at offset %u\n",
    288                         (unsigned int) i);
    289             goto cleanup;
    290           }
    291         }
    292       }
    293     }
    294   }
    295   switch (base->version)
    296   {
    297   case TALER_MERCHANT_CONTRACT_VERSION_0:
    298     if (GNUNET_OK ==
    299         parse_contract_v0 (input,
    300                            pc))
    301       return pc;
    302     GNUNET_break_op (0);
    303     break;
    304   case TALER_MERCHANT_CONTRACT_VERSION_1:
    305     if (GNUNET_OK ==
    306         parse_contract_v1 (input,
    307                            pc))
    308       return pc;
    309     GNUNET_break_op (0);
    310     break;
    311   }
    312 cleanup:
    313   TALER_MERCHANT_proto_contract_free (pc);
    314   return NULL;
    315 }
    316 
    317 
    318 /**
    319  * Free the proto-contract at @a pc
    320  *
    321  * @param[in] pc proto-contract to free
    322  */
    323 void
    324 TALER_MERCHANT_proto_contract_free (
    325   struct TALER_MERCHANT_ProtoContract *pc)
    326 {
    327   if (NULL != pc->products)
    328   {
    329     for (size_t i = 0; i<pc->products_len; i++)
    330       TALER_MERCHANT_product_sold_free (&pc->products[i]);
    331     GNUNET_free (pc->products);
    332   }
    333   if (NULL != pc->base)
    334   {
    335     switch (pc->base->version)
    336     {
    337     case TALER_MERCHANT_CONTRACT_VERSION_0:
    338       break;
    339     case TALER_MERCHANT_CONTRACT_VERSION_1:
    340       for (unsigned int i = 0;
    341            i < pc->details.v1.choices_len;
    342            i++)
    343         TALER_MERCHANT_contract_choice_free (
    344           &pc->details.v1.choices[i]);
    345       GNUNET_free (pc->details.v1.choices);
    346       for (unsigned int i = 0;
    347            i < pc->details.v1.token_authorities_len;
    348            i++)
    349         TALER_MERCHANT_contract_token_family_free (
    350           &pc->details.v1.token_authorities[i]);
    351       GNUNET_free (pc->details.v1.token_authorities);
    352       break;
    353     }
    354     TALER_MERCHANT_base_terms_free (pc->base);
    355     pc->base = NULL;
    356   }
    357   GNUNET_free (pc->wire_method);
    358   GNUNET_free (pc->merchant_base_url);
    359   TALER_MERCHANT_metadata_free (&pc->merchant);
    360   if (NULL != pc->exchanges)
    361   {
    362     json_decref (pc->exchanges);
    363     pc->exchanges = NULL;
    364   }
    365   GNUNET_free (pc->order_id);
    366   GNUNET_free (pc);
    367 }
    368 
    369 
    370 struct TALER_MERCHANT_Contract *
    371 TALER_MERCHANT_contract_parse (json_t *input)
    372 {
    373   struct TALER_MERCHANT_Contract *contract
    374     = GNUNET_new (struct TALER_MERCHANT_Contract);
    375   struct GNUNET_JSON_Specification espec[] = {
    376     GNUNET_JSON_spec_string_copy ("nonce",
    377                                   &contract->nonce),
    378     GNUNET_JSON_spec_end ()
    379   };
    380   enum GNUNET_GenericReturnValue res;
    381   const char *ename;
    382   unsigned int eline;
    383 
    384   GNUNET_assert (NULL != input);
    385   contract->pc = TALER_MERCHANT_proto_contract_parse (input);
    386   if (NULL == contract->pc)
    387   {
    388     GNUNET_break_op (0);
    389     GNUNET_free (contract);
    390     return NULL;
    391   }
    392   res = GNUNET_JSON_parse (input,
    393                            espec,
    394                            &ename,
    395                            &eline);
    396   if (GNUNET_OK != res)
    397   {
    398     GNUNET_break_op (0);
    399     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    400                 "Failed to parse contract at field %s\n",
    401                 ename);
    402     goto cleanup;
    403   }
    404   return contract;
    405 
    406 cleanup:
    407   TALER_MERCHANT_contract_free (contract);
    408   return NULL;
    409 }
    410 
    411 
    412 void
    413 TALER_MERCHANT_contract_free (
    414   struct TALER_MERCHANT_Contract *contract)
    415 {
    416   if (NULL == contract)
    417     return;
    418   if (NULL != contract->pc)
    419   {
    420     TALER_MERCHANT_proto_contract_free (contract->pc);
    421     contract->pc = NULL;
    422   }
    423   GNUNET_free (contract->nonce);
    424   GNUNET_free (contract);
    425 }