merchant

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

contract_serialize.c (16452B)


      1 /*
      2   This file is part of GNU Taler
      3   Copyright (C) 2024 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU 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 util/contract_serialize.c
     18  * @brief shared logic for contract terms serialization
     19  * @author Iván Ávalos
     20  */
     21 
     22 #include "platform.h"
     23 #include <gnunet/gnunet_json_lib.h>
     24 #include <gnunet/gnunet_common.h>
     25 #include <taler/taler_json_lib.h>
     26 #include <jansson.h>
     27 #include "taler/taler_util.h"
     28 #include "taler_merchant_util.h"
     29 
     30 /**
     31  * Get JSON representation of merchant details.
     32  *
     33  * @param[in] contract contract terms
     34  * @return JSON object with merchant details; NULL on error
     35  */
     36 static json_t *
     37 json_from_merchant_details (
     38   const struct TALER_MERCHANT_Contract *contract)
     39 {
     40   return GNUNET_JSON_PACK (
     41     GNUNET_JSON_pack_string ("name",
     42                              contract->merchant.name),
     43     GNUNET_JSON_pack_allow_null (
     44       GNUNET_JSON_pack_string ("email",
     45                                contract->merchant.email)),
     46     GNUNET_JSON_pack_allow_null (
     47       GNUNET_JSON_pack_string ("website",
     48                                contract->merchant.website)),
     49     GNUNET_JSON_pack_allow_null (
     50       GNUNET_JSON_pack_string ("logo",
     51                                contract->merchant.logo)),
     52     GNUNET_JSON_pack_allow_null (
     53       GNUNET_JSON_pack_object_steal ("address",
     54                                      contract->merchant.address)),
     55     GNUNET_JSON_pack_allow_null (
     56       GNUNET_JSON_pack_object_steal ("jurisdiction",
     57                                      contract->merchant.jurisdiction)));
     58 }
     59 
     60 
     61 /**
     62  * Get JSON representation of contract choice input.
     63  *
     64  * @param[in] input contract terms choice input
     65  * @return JSON representation of @a input; NULL on error
     66  */
     67 static json_t *
     68 json_from_contract_input (
     69   const struct TALER_MERCHANT_ContractInput *input)
     70 {
     71   switch (input->type)
     72   {
     73   case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID:
     74     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     75                 "invalid contract input type");
     76     GNUNET_assert (0);
     77     return NULL;
     78   case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN:
     79     return GNUNET_JSON_PACK (
     80       GNUNET_JSON_pack_string ("type",
     81                                "token"),
     82       GNUNET_JSON_pack_string ("token_family_slug",
     83                                input->details.token.token_family_slug),
     84       GNUNET_JSON_pack_int64 ("count",
     85                               input->details.token.count));
     86   }
     87 
     88   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     89               "unsupported contract input type %d",
     90               input->type);
     91   GNUNET_assert (0);
     92   return NULL;
     93 }
     94 
     95 
     96 /**
     97  * Get JSON representation of contract choice output.
     98  *
     99  * @param[in] output contract terms choice output
    100  * @return JSON representation of @a output; NULL on error
    101  */
    102 static json_t *
    103 json_from_contract_output (
    104   const struct TALER_MERCHANT_ContractOutput *output)
    105 {
    106   switch (output->type)
    107   {
    108   case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
    109     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    110                 "invalid contract output type");
    111     GNUNET_assert (0);
    112     return NULL;
    113   case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
    114     return GNUNET_JSON_PACK (
    115       GNUNET_JSON_pack_string ("type",
    116                                "token"),
    117       GNUNET_JSON_pack_string ("token_family_slug",
    118                                output->details.token.token_family_slug),
    119       GNUNET_JSON_pack_uint64 ("count",
    120                                output->details.token.count),
    121       GNUNET_JSON_pack_uint64 ("key_index",
    122                                output->details.token.key_index));
    123   case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
    124     {
    125       json_t *donau_urls;
    126 
    127       donau_urls = json_array ();
    128       GNUNET_assert (NULL != donau_urls);
    129       for (unsigned i = 0;
    130            i < output->details.donation_receipt.donau_urls_len;
    131            i++)
    132         GNUNET_assert (0 ==
    133                        json_array_append_new (
    134                          donau_urls,
    135                          json_string (
    136                            output->details.donation_receipt.donau_urls[i])));
    137 
    138       return GNUNET_JSON_PACK (
    139         GNUNET_JSON_pack_string ("type",
    140                                  "tax-receipt"),
    141         GNUNET_JSON_pack_array_steal ("donau_urls",
    142                                       donau_urls),
    143         GNUNET_JSON_pack_allow_null (
    144           TALER_JSON_pack_amount ("amount",
    145                                   &output->details.donation_receipt.amount)));
    146     }
    147   }
    148   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    149               "Unsupported contract output type %d",
    150               output->type);
    151   GNUNET_assert (0);
    152   return NULL;
    153 }
    154 
    155 
    156 json_t *
    157 TALER_MERCHANT_json_from_contract_choice (
    158   const struct TALER_MERCHANT_ContractChoice *choice,
    159   bool order)
    160 {
    161   json_t *inputs;
    162   json_t *outputs;
    163 
    164   inputs = json_array ();
    165   GNUNET_assert (NULL != inputs);
    166   for (unsigned int i = 0; i < choice->inputs_len; i++)
    167     GNUNET_assert (0 ==
    168                    json_array_append_new (inputs,
    169                                           json_from_contract_input (
    170                                             &choice->inputs[i])));
    171   outputs = json_array ();
    172   GNUNET_assert (NULL != outputs);
    173   for (unsigned int i = 0; i < choice->outputs_len; i++)
    174     GNUNET_assert (0 ==
    175                    json_array_append_new (outputs,
    176                                           json_from_contract_output (
    177                                             &choice->outputs[i])));
    178 
    179   return GNUNET_JSON_PACK (
    180     TALER_JSON_pack_amount ("amount",
    181                             &choice->amount),
    182     GNUNET_JSON_pack_allow_null (
    183       GNUNET_JSON_pack_string ("description",
    184                                choice->description)),
    185     GNUNET_JSON_pack_allow_null (
    186       GNUNET_JSON_pack_object_incref ("description_i18n",
    187                                       choice->description_i18n)),
    188     (order)
    189     ? GNUNET_JSON_pack_allow_null (
    190       TALER_JSON_pack_amount (
    191         "max_fee",
    192         /* workaround for nullable amount */
    193         (GNUNET_OK ==
    194          TALER_amount_is_valid (&choice->max_fee))
    195         ? &choice->max_fee
    196         : NULL))
    197     : TALER_JSON_pack_amount ("max_fee",
    198                               &choice->max_fee),
    199     (order)
    200     ? GNUNET_JSON_pack_allow_null (
    201       GNUNET_JSON_pack_array_steal ("inputs",
    202                                     inputs))
    203     : GNUNET_JSON_pack_array_steal ("inputs",
    204                                     inputs),
    205     (order)
    206     ? GNUNET_JSON_pack_allow_null (
    207       GNUNET_JSON_pack_array_steal ("outputs",
    208                                     outputs))
    209     : GNUNET_JSON_pack_array_steal ("outputs",
    210                                     outputs));
    211 }
    212 
    213 
    214 /**
    215  * Get JSON representation of contract token family key.
    216  *
    217  * @param[in] key contract token family key
    218  * @return JSON representation of @a key; NULL on error
    219  */
    220 static json_t *
    221 json_from_token_family_key (
    222   const struct TALER_MERCHANT_ContractTokenFamilyKey *key)
    223 {
    224   return GNUNET_JSON_PACK (
    225     GNUNET_JSON_pack_timestamp ("signature_validity_start",
    226                                 key->valid_after),
    227     GNUNET_JSON_pack_timestamp ("signature_validity_end",
    228                                 key->valid_before),
    229     TALER_JSON_pack_token_pub (NULL,
    230                                &key->pub));
    231 }
    232 
    233 
    234 /**
    235  * Get JSON representation of contract token family details.
    236  *
    237  * @param[in] family contract token family
    238  * @return JSON representation of @a family->details; NULL on error
    239  */
    240 static json_t *
    241 json_from_token_family_details (
    242   const struct TALER_MERCHANT_ContractTokenFamily *family)
    243 {
    244   switch (family->kind)
    245   {
    246   case TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID:
    247     break;
    248   case TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION:
    249     {
    250       json_t *trusted_domains;
    251 
    252       trusted_domains = json_array ();
    253       GNUNET_assert (NULL != trusted_domains);
    254       for (unsigned int i = 0;
    255            i < family->details.subscription.trusted_domains_len;
    256            i++)
    257         GNUNET_assert (0 ==
    258                        json_array_append_new (
    259                          trusted_domains,
    260                          json_string (
    261                            family->details.subscription.trusted_domains[i])));
    262 
    263       return GNUNET_JSON_PACK (
    264         GNUNET_JSON_pack_string ("class",
    265                                  "subscription"),
    266         GNUNET_JSON_pack_array_steal ("trusted_domains",
    267                                       trusted_domains));
    268     }
    269   case TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT:
    270     {
    271       json_t *expected_domains;
    272 
    273       expected_domains = json_array ();
    274       GNUNET_assert (NULL != expected_domains);
    275       for (unsigned int i = 0;
    276            i < family->details.discount.expected_domains_len;
    277            i++)
    278         GNUNET_assert (0 ==
    279                        json_array_append_new (
    280                          expected_domains,
    281                          json_string (
    282                            family->details.discount.expected_domains[i])));
    283 
    284       return GNUNET_JSON_PACK (
    285         GNUNET_JSON_pack_string ("class",
    286                                  "discount"),
    287         GNUNET_JSON_pack_array_steal ("expected_domains",
    288                                       expected_domains));
    289     }
    290   }
    291 
    292   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    293               "unsupported token family kind %d",
    294               family->kind);
    295   GNUNET_assert (0);
    296   return NULL;
    297 }
    298 
    299 
    300 json_t *
    301 TALER_MERCHANT_json_from_token_family (
    302   const struct TALER_MERCHANT_ContractTokenFamily *family)
    303 {
    304   json_t *keys;
    305 
    306   keys = json_array ();
    307   GNUNET_assert (NULL != keys);
    308   for (unsigned int i = 0; i < family->keys_len; i++)
    309     GNUNET_assert (0 == json_array_append_new (
    310                      keys,
    311                      json_from_token_family_key (
    312                        &family->keys[i])));
    313 
    314   return GNUNET_JSON_PACK (
    315     GNUNET_JSON_pack_string ("name",
    316                              family->name),
    317     GNUNET_JSON_pack_string ("description",
    318                              family->description),
    319     GNUNET_JSON_pack_object_incref ("description_i18n",
    320                                     family->description_i18n),
    321     GNUNET_JSON_pack_array_steal ("keys",
    322                                   keys),
    323     GNUNET_JSON_pack_object_steal ("details",
    324                                    json_from_token_family_details (family)),
    325     GNUNET_JSON_pack_bool ("critical",
    326                            family->critical));
    327 }
    328 
    329 
    330 /**
    331  * Get JSON object with contract terms v0-specific fields.
    332  *
    333  * @param[in] input contract terms v0
    334  * @return JSON object with @a input v0 fields; NULL on error
    335  */
    336 static json_t *
    337 json_from_contract_v0 (
    338   const struct TALER_MERCHANT_Contract *input)
    339 {
    340   return GNUNET_JSON_PACK (
    341     TALER_JSON_pack_amount ("amount",
    342                             &input->details.v0.brutto),
    343     TALER_JSON_pack_amount ("max_fee",
    344                             &input->details.v0.max_fee));
    345 }
    346 
    347 
    348 /**
    349  * Get JSON object with contract terms v1-specific fields.
    350  *
    351  * @param[in] input contract terms v1
    352  * @return JSON object with @a input v1 fields; NULL on error
    353  */
    354 static json_t *
    355 json_from_contract_v1 (
    356   const struct TALER_MERCHANT_Contract *input)
    357 {
    358   json_t *choices;
    359   json_t *families;
    360 
    361   choices = json_array ();
    362   GNUNET_assert (0 != choices);
    363   for (unsigned i = 0; i < input->details.v1.choices_len; i++)
    364     GNUNET_assert (0 == json_array_append_new (
    365                      choices,
    366                      TALER_MERCHANT_json_from_contract_choice (
    367                        &input->details.v1.choices[i],
    368                        false)));
    369 
    370   families = json_object ();
    371   GNUNET_assert (0 != families);
    372   for (unsigned i = 0; i < input->details.v1.token_authorities_len; i++)
    373     GNUNET_assert (0 == json_object_set_new (
    374                      families,
    375                      input->details.v1.token_authorities[i].slug,
    376                      TALER_MERCHANT_json_from_token_family (
    377                        &input->details.v1.token_authorities[i])));
    378 
    379   return GNUNET_JSON_PACK (
    380     GNUNET_JSON_pack_array_steal ("choices",
    381                                   choices),
    382     GNUNET_JSON_pack_object_steal ("token_families",
    383                                    families));
    384 }
    385 
    386 
    387 json_t *
    388 TALER_MERCHANT_contract_serialize (
    389   const struct TALER_MERCHANT_Contract *input,
    390   bool nonce_optional)
    391 {
    392   json_t *details;
    393 
    394   switch (input->version)
    395   {
    396   case TALER_MERCHANT_CONTRACT_VERSION_0:
    397     details = json_from_contract_v0 (input);
    398     goto success;
    399   case TALER_MERCHANT_CONTRACT_VERSION_1:
    400     details = json_from_contract_v1 (input);
    401     goto success;
    402   }
    403 
    404   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    405               "unknown contract type version %d",
    406               input->version);
    407   GNUNET_assert (0);
    408   return NULL;
    409 
    410 success:
    411   return GNUNET_JSON_PACK (
    412     GNUNET_JSON_pack_uint64 ("version",
    413                              input->version),
    414     GNUNET_JSON_pack_string ("summary",
    415                              input->summary),
    416     GNUNET_JSON_pack_allow_null (
    417       GNUNET_JSON_pack_object_steal ("summary_i18n",
    418                                      input->summary_i18n)),
    419     GNUNET_JSON_pack_string ("order_id",
    420                              input->order_id),
    421     GNUNET_JSON_pack_allow_null (
    422       GNUNET_JSON_pack_string ("public_reorder_url",
    423                                input->public_reorder_url)),
    424     GNUNET_JSON_pack_allow_null (
    425       GNUNET_JSON_pack_string ("fulfillment_url",
    426                                input->fulfillment_url)),
    427     GNUNET_JSON_pack_allow_null (
    428       GNUNET_JSON_pack_string ("fulfillment_message",
    429                                input->fulfillment_message)),
    430     GNUNET_JSON_pack_allow_null (
    431       GNUNET_JSON_pack_object_steal ("fulfillment_message_i18n",
    432                                      input->fulfillment_message_i18n)),
    433     GNUNET_JSON_pack_array_steal ("products",
    434                                   input->products),
    435     GNUNET_JSON_pack_timestamp ("timestamp",
    436                                 input->timestamp),
    437     GNUNET_JSON_pack_timestamp ("refund_deadline",
    438                                 input->refund_deadline),
    439     GNUNET_JSON_pack_timestamp ("pay_deadline",
    440                                 input->pay_deadline),
    441     GNUNET_JSON_pack_timestamp ("wire_transfer_deadline",
    442                                 input->wire_deadline),
    443     GNUNET_JSON_pack_data_auto ("merchant_pub",
    444                                 &input->merchant_pub.eddsa_pub),
    445     GNUNET_JSON_pack_string ("merchant_base_url",
    446                              input->merchant_base_url),
    447     GNUNET_JSON_pack_object_steal ("merchant",
    448                                    json_from_merchant_details (input)),
    449     GNUNET_JSON_pack_data_auto ("h_wire",
    450                                 &input->h_wire),
    451     GNUNET_JSON_pack_string ("wire_method",
    452                              input->wire_method),
    453     GNUNET_JSON_pack_array_steal ("exchanges",
    454                                   input->exchanges),
    455     GNUNET_JSON_pack_allow_null (
    456       GNUNET_JSON_pack_object_steal ("delivery_location",
    457                                      input->delivery_location)),
    458     GNUNET_JSON_pack_allow_null (
    459       GNUNET_JSON_pack_timestamp ("delivery_date",
    460                                   input->delivery_date)),
    461     (nonce_optional)
    462     ? GNUNET_JSON_pack_allow_null (
    463       GNUNET_JSON_pack_string ("nonce",
    464                                input->nonce))
    465     : GNUNET_JSON_pack_string ("nonce",
    466                                input->nonce),
    467     GNUNET_JSON_pack_allow_null (
    468       GNUNET_JSON_pack_time_rel ("auto_refund",
    469                                  input->auto_refund)),
    470     GNUNET_JSON_pack_allow_null (
    471       GNUNET_JSON_pack_object_steal ("extra",
    472                                      input->extra)),
    473     GNUNET_JSON_pack_allow_null (
    474       GNUNET_JSON_pack_uint64 ("minimum_age",
    475                                input->minimum_age)),
    476     GNUNET_JSON_pack_object_steal (NULL,
    477                                    details));
    478 }