merchant

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

contract_parse.c (10473B)


      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 
     93   return res;
     94 }
     95 
     96 
     97 /**
     98  * Parse v1-specific fields of @a input JSON into @a pc.
     99  *
    100  * @param[in] input the JSON contract terms
    101  * @param[out] pc where to write the data
    102  * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
    103  */
    104 static enum GNUNET_GenericReturnValue
    105 parse_contract_v1 (
    106   json_t *input,
    107   struct TALER_MERCHANT_ProtoContract *pc)
    108 {
    109   struct GNUNET_JSON_Specification espec[] = {
    110     TALER_MERCHANT_spec_contract_choices (
    111       "choices",
    112       &pc->details.v1.choices,
    113       &pc->details.v1.choices_len),
    114     TALER_MERCHANT_spec_token_families (
    115       "token_families",
    116       &pc->details.v1.token_authorities,
    117       &pc->details.v1.token_authorities_len),
    118     GNUNET_JSON_spec_end ()
    119   };
    120 
    121   enum GNUNET_GenericReturnValue res;
    122   const char *ename;
    123   unsigned int eline;
    124 
    125   res = GNUNET_JSON_parse (input,
    126                            espec,
    127                            &ename,
    128                            &eline);
    129   if (GNUNET_OK != res)
    130   {
    131     GNUNET_break (0);
    132     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    133                 "Failed to parse contract v1 at field %s\n",
    134                 ename);
    135     return GNUNET_SYSERR;
    136   }
    137 
    138   return res;
    139 }
    140 
    141 
    142 struct TALER_MERCHANT_ProtoContract *
    143 TALER_MERCHANT_proto_contract_parse (
    144   json_t *input)
    145 {
    146   struct TALER_MERCHANT_ContractBaseTerms *base;
    147   struct TALER_MERCHANT_ProtoContract *pc;
    148   enum GNUNET_GenericReturnValue res;
    149 
    150   base = TALER_MERCHANT_base_terms_parse (input);
    151   if (NULL == base)
    152   {
    153     GNUNET_break_op (0);
    154     return NULL;
    155   }
    156   pc = GNUNET_new (struct TALER_MERCHANT_ProtoContract);
    157   pc->base = base;
    158   {
    159     const json_t *products = NULL;
    160     struct GNUNET_JSON_Specification espec[] = {
    161       GNUNET_JSON_spec_string_copy ("order_id",
    162                                     &pc->order_id),
    163       GNUNET_JSON_spec_mark_optional (
    164         GNUNET_JSON_spec_array_const ("products",
    165                                       &products),
    166         NULL),
    167       GNUNET_JSON_spec_timestamp ("timestamp",
    168                                   &pc->timestamp),
    169       GNUNET_JSON_spec_timestamp ("refund_deadline",
    170                                   &pc->refund_deadline),
    171       GNUNET_JSON_spec_timestamp ("pay_deadline",
    172                                   &pc->pay_deadline),
    173       GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
    174                                   &pc->wire_deadline),
    175       GNUNET_JSON_spec_fixed_auto ("merchant_pub",
    176                                    &pc->merchant_pub),
    177       GNUNET_JSON_spec_fixed_auto ("h_wire",
    178                                    &pc->h_wire),
    179       GNUNET_JSON_spec_string_copy ("merchant_base_url",
    180                                     &pc->merchant_base_url),
    181       TALER_MERCHANT_spec_merchant_details ("merchant",
    182                                             &pc->merchant),
    183       GNUNET_JSON_spec_string_copy ("wire_method",
    184                                     &pc->wire_method),
    185       GNUNET_JSON_spec_array_copy ("exchanges",
    186                                    &pc->exchanges),
    187 
    188       GNUNET_JSON_spec_end ()
    189     };
    190     const char *ename;
    191     unsigned int eline;
    192 
    193     res = GNUNET_JSON_parse (input,
    194                              espec,
    195                              &ename,
    196                              &eline);
    197     if (GNUNET_OK != res)
    198     {
    199       GNUNET_break_op (0);
    200       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    201                   "Failed to parse proto contract at field %s\n",
    202                   ename);
    203       goto cleanup;
    204     }
    205     if (NULL != products)
    206     {
    207       pc->products_len = json_array_size (products);
    208       if (0 != pc->products_len)
    209       {
    210         size_t i;
    211         json_t *p;
    212 
    213         pc->products = GNUNET_new_array (
    214           pc->products_len,
    215           struct TALER_MERCHANT_ProductSold);
    216         json_array_foreach (products, i, p)
    217         {
    218           if (GNUNET_OK !=
    219               TALER_MERCHANT_parse_product_sold (p,
    220                                                  &pc->products[i],
    221                                                  false))
    222           {
    223             GNUNET_break (0);
    224             GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    225                         "Failed to parse product at offset %u\n",
    226                         (unsigned int) i);
    227             goto cleanup;
    228           }
    229         }
    230       }
    231     }
    232   }
    233   switch (base->version)
    234   {
    235   case TALER_MERCHANT_CONTRACT_VERSION_0:
    236     if (GNUNET_OK ==
    237         parse_contract_v0 (input,
    238                            pc))
    239       return pc;
    240     GNUNET_break_op (0);
    241     break;
    242   case TALER_MERCHANT_CONTRACT_VERSION_1:
    243     if (GNUNET_OK ==
    244         parse_contract_v1 (input,
    245                            pc))
    246       return pc;
    247     GNUNET_break_op (0);
    248     break;
    249   }
    250 cleanup:
    251   TALER_MERCHANT_proto_contract_free (pc);
    252   return NULL;
    253 }
    254 
    255 
    256 /**
    257  * Free the proto-contract at @a pc
    258  *
    259  * @param[in] pc proto-contract to free
    260  */
    261 void
    262 TALER_MERCHANT_proto_contract_free (
    263   struct TALER_MERCHANT_ProtoContract *pc)
    264 {
    265   if (NULL != pc->products)
    266   {
    267     for (size_t i = 0; i<pc->products_len; i++)
    268       TALER_MERCHANT_product_sold_free (&pc->products[i]);
    269     GNUNET_free (pc->products);
    270   }
    271   if (NULL != pc->base)
    272   {
    273     switch (pc->base->version)
    274     {
    275     case TALER_MERCHANT_CONTRACT_VERSION_0:
    276       break;
    277     case TALER_MERCHANT_CONTRACT_VERSION_1:
    278       for (unsigned int i = 0;
    279            i < pc->details.v1.choices_len;
    280            i++)
    281         TALER_MERCHANT_contract_choice_free (
    282           &pc->details.v1.choices[i]);
    283       GNUNET_free (pc->details.v1.choices);
    284       for (unsigned int i = 0;
    285            i < pc->details.v1.token_authorities_len;
    286            i++)
    287         TALER_MERCHANT_contract_token_family_free (
    288           &pc->details.v1.token_authorities[i]);
    289       GNUNET_free (pc->details.v1.token_authorities);
    290       break;
    291     }
    292     TALER_MERCHANT_base_terms_free (pc->base);
    293     pc->base = NULL;
    294   }
    295   GNUNET_free (pc->wire_method);
    296   GNUNET_free (pc->merchant_base_url);
    297   TALER_MERCHANT_metadata_free (&pc->merchant);
    298   if (NULL != pc->exchanges)
    299   {
    300     json_decref (pc->exchanges);
    301     pc->exchanges = NULL;
    302   }
    303   GNUNET_free (pc->order_id);
    304   GNUNET_free (pc);
    305 }
    306 
    307 
    308 struct TALER_MERCHANT_Contract *
    309 TALER_MERCHANT_contract_parse (json_t *input)
    310 {
    311   struct TALER_MERCHANT_Contract *contract
    312     = GNUNET_new (struct TALER_MERCHANT_Contract);
    313   struct GNUNET_JSON_Specification espec[] = {
    314     GNUNET_JSON_spec_string_copy ("nonce",
    315                                   &contract->nonce),
    316     GNUNET_JSON_spec_end ()
    317   };
    318   enum GNUNET_GenericReturnValue res;
    319   const char *ename;
    320   unsigned int eline;
    321 
    322   GNUNET_assert (NULL != input);
    323   contract->pc = TALER_MERCHANT_proto_contract_parse (input);
    324   if (NULL == contract->pc)
    325   {
    326     GNUNET_break_op (0);
    327     GNUNET_free (contract);
    328     return NULL;
    329   }
    330   res = GNUNET_JSON_parse (input,
    331                            espec,
    332                            &ename,
    333                            &eline);
    334   if (GNUNET_OK != res)
    335   {
    336     GNUNET_break_op (0);
    337     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    338                 "Failed to parse contract at field %s\n",
    339                 ename);
    340     goto cleanup;
    341   }
    342   return contract;
    343 
    344 cleanup:
    345   TALER_MERCHANT_contract_free (contract);
    346   return NULL;
    347 }
    348 
    349 
    350 void
    351 TALER_MERCHANT_contract_free (
    352   struct TALER_MERCHANT_Contract *contract)
    353 {
    354   if (NULL == contract)
    355     return;
    356   if (NULL != contract->pc)
    357   {
    358     TALER_MERCHANT_proto_contract_free (contract->pc);
    359     contract->pc = NULL;
    360   }
    361   GNUNET_free (contract->nonce);
    362   GNUNET_free (contract);
    363 }