merchant

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

order_choice_parse.c (13476B)


      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_choice_parse.c
     18  * @brief shared logic for order choice 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 JSON order choice input.
     35  *
     36  * @param[in] root JSON object containing choice input
     37  * @param[out] input parsed choice input, NULL if @a input is malformed
     38  * @param index index of choice input in inputs array
     39  * @return #GNUNET_SYSERR if @a input is malformed; #GNUNET_OK otherwise
     40  */
     41 static enum GNUNET_GenericReturnValue
     42 parse_order_choice_input (
     43   json_t *root,
     44   struct TALER_MERCHANT_OrderInput *input,
     45   size_t index)
     46 {
     47   const char *ename;
     48   unsigned int eline;
     49   struct GNUNET_JSON_Specification ispec[] = {
     50     TALER_MERCHANT_json_spec_cit ("type",
     51                                   &input->type),
     52     GNUNET_JSON_spec_end ()
     53   };
     54 
     55   if (GNUNET_OK !=
     56       GNUNET_JSON_parse (root,
     57                          ispec,
     58                          &ename,
     59                          &eline))
     60   {
     61     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     62                 "Failed to parse %s at %u: %s\n",
     63                 ispec[eline].field,
     64                 eline,
     65                 ename);
     66     GNUNET_break_op (0);
     67     return GNUNET_SYSERR;
     68   }
     69 
     70   switch (input->type)
     71   {
     72   case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID:
     73     GNUNET_break (0);
     74     break;
     75   case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN:
     76     {
     77       struct GNUNET_JSON_Specification spec[] = {
     78         GNUNET_JSON_spec_string ("token_family_slug",
     79                                  &input->details.token.token_family_slug),
     80         GNUNET_JSON_spec_mark_optional (
     81           GNUNET_JSON_spec_uint ("count",
     82                                  &input->details.token.count),
     83           NULL),
     84         GNUNET_JSON_spec_end ()
     85       };
     86 
     87       if (GNUNET_OK !=
     88           GNUNET_JSON_parse (root,
     89                              spec,
     90                              &ename,
     91                              &eline))
     92       {
     93         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     94                     "Failed to parse %s at %u: %s\n",
     95                     spec[eline].field,
     96                     eline,
     97                     ename);
     98         GNUNET_break_op (0);
     99         return GNUNET_SYSERR;
    100       }
    101 
    102       return GNUNET_OK;
    103     }
    104   }
    105 
    106   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    107               "Field 'type' invalid in input #%u\n",
    108               (unsigned int) index);
    109   GNUNET_break_op (0);
    110   return GNUNET_SYSERR;
    111 }
    112 
    113 
    114 /**
    115  * Parse JSON order choice output.
    116  *
    117  * @param[in] root JSON object containing choice output
    118  * @param[out] output parsed choice output, NULL if @a output is malformed
    119  * @param index index of choice output in outputs array
    120  * @return #GNUNET_SYSERR if @a output is malformed; #GNUNET_OK otherwise
    121  */
    122 static enum GNUNET_GenericReturnValue
    123 parse_order_choice_output (
    124   json_t *root,
    125   struct TALER_MERCHANT_OrderOutput *output,
    126   size_t index)
    127 {
    128   const char *ename;
    129   unsigned int eline;
    130   struct GNUNET_JSON_Specification ispec[] = {
    131     TALER_MERCHANT_json_spec_cot ("type",
    132                                   &output->type),
    133     GNUNET_JSON_spec_end ()
    134   };
    135 
    136   if (GNUNET_OK !=
    137       GNUNET_JSON_parse (root,
    138                          ispec,
    139                          &ename,
    140                          &eline))
    141   {
    142     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    143                 "Failed to parse %s at %u: %s\n",
    144                 ispec[eline].field,
    145                 eline,
    146                 ename);
    147     GNUNET_break_op (0);
    148     return GNUNET_SYSERR;
    149   }
    150 
    151   switch (output->type)
    152   {
    153   case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
    154     GNUNET_break (0);
    155     break;
    156   case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
    157     {
    158       struct GNUNET_JSON_Specification spec[] = {
    159         GNUNET_JSON_spec_string ("token_family_slug",
    160                                  &output->details.token.token_family_slug),
    161         GNUNET_JSON_spec_mark_optional (
    162           GNUNET_JSON_spec_uint ("count",
    163                                  &output->details.token.count),
    164           NULL),
    165         GNUNET_JSON_spec_mark_optional (
    166           GNUNET_JSON_spec_timestamp ("valid_at",
    167                                       &output->details.token.valid_at),
    168           NULL),
    169         GNUNET_JSON_spec_end ()
    170       };
    171 
    172       if (GNUNET_OK !=
    173           GNUNET_JSON_parse (root,
    174                              spec,
    175                              &ename,
    176                              &eline))
    177       {
    178         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    179                     "Failed to parse %s at %u: %s\n",
    180                     spec[eline].field,
    181                     eline,
    182                     ename);
    183         GNUNET_break_op (0);
    184         return GNUNET_SYSERR;
    185       }
    186 
    187       return GNUNET_OK;
    188     }
    189   case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
    190     {
    191       struct GNUNET_JSON_Specification spec[] = {
    192         GNUNET_JSON_spec_mark_optional (
    193           TALER_JSON_spec_amount_any ("amount",
    194                                       &output->details.donation_receipt.amount),
    195           &output->details.donation_receipt.no_amount),
    196         GNUNET_JSON_spec_end ()
    197       };
    198 
    199       if (GNUNET_OK !=
    200           GNUNET_JSON_parse (root,
    201                              spec,
    202                              &ename,
    203                              &eline))
    204       {
    205         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    206                     "Failed to parse %s at %u: %s\n",
    207                     spec[eline].field,
    208                     eline,
    209                     ename);
    210         GNUNET_break_op (0);
    211         return GNUNET_SYSERR;
    212       }
    213       return GNUNET_OK;
    214     }
    215   }
    216 
    217   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    218               "Field 'type' invalid in output #%u\n",
    219               (unsigned int) index);
    220   GNUNET_break_op (0);
    221   return GNUNET_SYSERR;
    222 }
    223 
    224 
    225 /**
    226  * Parse given JSON object to choices array.
    227  *
    228  * @param cls closure, pointer to array length
    229  * @param root the json array representing the choices
    230  * @param[out] ospec where to write the data
    231  * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
    232  */
    233 static enum GNUNET_GenericReturnValue
    234 parse_order_choices (
    235   void *cls,
    236   json_t *root,
    237   struct GNUNET_JSON_Specification *ospec)
    238 {
    239   struct TALER_MERCHANT_OrderChoice **choices = ospec->ptr;
    240   unsigned int *choices_len = cls;
    241 
    242   if (! json_is_array (root))
    243   {
    244     GNUNET_break_op (0);
    245     return GNUNET_SYSERR;
    246   }
    247   if (0 == json_array_size (root))
    248   {
    249     /* empty list of choices is not allowed */
    250     GNUNET_break_op (0);
    251     return GNUNET_SYSERR;
    252   }
    253   *choices = NULL;
    254   *choices_len = 0;
    255   GNUNET_array_grow (*choices,
    256                      *choices_len,
    257                      json_array_size (root));
    258 
    259   for (unsigned int i = 0; i < *choices_len; i++)
    260   {
    261     struct TALER_MERCHANT_OrderChoice *choice = &(*choices)[i];
    262     const json_t *jinputs = NULL;
    263     const json_t *joutputs = NULL;
    264     struct GNUNET_JSON_Specification spec[] = {
    265       TALER_JSON_spec_amount_any ("amount",
    266                                   &choice->amount),
    267       GNUNET_JSON_spec_mark_optional (
    268         TALER_JSON_spec_amount_any ("tip",
    269                                     &choice->tip),
    270         &choice->no_tip),
    271       GNUNET_JSON_spec_mark_optional (
    272         GNUNET_JSON_spec_string_copy ("description",
    273                                       &choice->description),
    274         NULL),
    275       GNUNET_JSON_spec_mark_optional (
    276         GNUNET_JSON_spec_object_copy ("description_i18n",
    277                                       &choice->description_i18n),
    278         NULL),
    279       GNUNET_JSON_spec_mark_optional (
    280         TALER_JSON_spec_amount_any ("max_fee",
    281                                     &choice->max_fee),
    282         &choice->no_max_fee),
    283       GNUNET_JSON_spec_mark_optional (
    284         GNUNET_JSON_spec_array_const ("inputs",
    285                                       &jinputs),
    286         NULL),
    287       GNUNET_JSON_spec_mark_optional (
    288         GNUNET_JSON_spec_array_const ("outputs",
    289                                       &joutputs),
    290         NULL),
    291       GNUNET_JSON_spec_end ()
    292     };
    293     const char *ename;
    294     unsigned int eline;
    295 
    296     if (GNUNET_OK !=
    297         GNUNET_JSON_parse (json_array_get (root, i),
    298                            spec,
    299                            &ename,
    300                            &eline))
    301     {
    302       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    303                   "Failed to parse %s at %u: %s\n",
    304                   spec[eline].field,
    305                   eline,
    306                   ename);
    307       GNUNET_break_op (0);
    308       return GNUNET_SYSERR;
    309     }
    310     if ( (! choice->no_max_fee) &&
    311          (GNUNET_OK !=
    312           TALER_amount_cmp_currency (&choice->amount,
    313                                      &choice->max_fee)) )
    314     {
    315       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    316                   "Fee currency does not match amount currency in choice #%u\n",
    317                   i);
    318       GNUNET_break_op (0);
    319       return GNUNET_SYSERR;
    320     }
    321     if ( (! choice->no_tip) &&
    322          (GNUNET_OK !=
    323           TALER_amount_cmp_currency (&choice->amount,
    324                                      &choice->tip)) )
    325     {
    326       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    327                   "Tip currency does not match amount currency in choice #%u\n",
    328                   i);
    329       GNUNET_break_op (0);
    330       return GNUNET_SYSERR;
    331     }
    332 
    333     if (NULL != jinputs)
    334     {
    335       const json_t *jinput;
    336       size_t idx;
    337 
    338       json_array_foreach ((json_t *) jinputs, idx, jinput)
    339       {
    340         struct TALER_MERCHANT_OrderInput input = {
    341           .details.token.count = 1
    342         };
    343 
    344         if (GNUNET_OK !=
    345             parse_order_choice_input ((json_t *) jinput,
    346                                       &input,
    347                                       idx))
    348         {
    349           GNUNET_break (0);
    350           return GNUNET_SYSERR;
    351         }
    352         switch (input.type)
    353         {
    354         case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID:
    355           GNUNET_break_op (0);
    356           return GNUNET_SYSERR;
    357         case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN:
    358           /* Ignore inputs tokens with 'count' field set to 0 */
    359           if (0 == input.details.token.count)
    360             continue;
    361           break;
    362         }
    363         GNUNET_array_append (choice->inputs,
    364                              choice->inputs_len,
    365                              input);
    366       }
    367     }
    368 
    369     if (NULL != joutputs)
    370     {
    371       const json_t *joutput;
    372       size_t idx;
    373       json_array_foreach ((json_t *) joutputs, idx, joutput)
    374       {
    375         struct TALER_MERCHANT_OrderOutput output = {
    376           .details.token.count = 1
    377         };
    378 
    379         if (GNUNET_OK !=
    380             parse_order_choice_output ((json_t *) joutput,
    381                                        &output,
    382                                        idx))
    383         {
    384           GNUNET_break (0);
    385           return GNUNET_SYSERR;
    386         }
    387         switch (output.type)
    388         {
    389         case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
    390           GNUNET_break_op (0);
    391           return GNUNET_SYSERR;
    392         case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
    393           /* Ignore output tokens with 'count' field set to 0 */
    394           if (0 == output.details.token.count)
    395             continue;
    396           break;
    397         case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
    398           break;
    399         }
    400         GNUNET_array_append (choice->outputs,
    401                              choice->outputs_len,
    402                              output);
    403       }
    404     }
    405   }
    406 
    407   return GNUNET_OK;
    408 }
    409 
    410 
    411 struct GNUNET_JSON_Specification
    412 TALER_MERCHANT_spec_order_choices (
    413   const char *name,
    414   struct TALER_MERCHANT_OrderChoice **choices,
    415   unsigned int *choices_len)
    416 {
    417   struct GNUNET_JSON_Specification ret = {
    418     .cls = (void *) choices_len,
    419     .parser = &parse_order_choices,
    420     .field = name,
    421     .ptr = choices,
    422   };
    423 
    424   return ret;
    425 }
    426 
    427 
    428 void
    429 TALER_MERCHANT_order_choice_free (
    430   struct TALER_MERCHANT_OrderChoice *choice)
    431 {
    432   for (unsigned int i = 0; i < choice->outputs_len; i++)
    433   {
    434     struct TALER_MERCHANT_OrderOutput *output = &choice->outputs[i];
    435 
    436     switch (output->type)
    437     {
    438     case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
    439       GNUNET_break (0);
    440       break;
    441     case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
    442       break;
    443     case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
    444       break;
    445 #if FUTURE
    446     case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_COIN:
    447       GNUNET_free (output->details.coin.exchange_url);
    448       break;
    449 #endif
    450     }
    451   }
    452   GNUNET_free (choice->description);
    453   if (NULL != choice->description_i18n)
    454   {
    455     json_decref (choice->description_i18n);
    456     choice->description_i18n = NULL;
    457   }
    458   GNUNET_free (choice->inputs);
    459   GNUNET_free (choice->outputs);
    460 }