merchant

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

contract_serialize.c (20798B)


      1 /*
      2   This file is part of GNU Taler
      3   Copyright (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 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  * @author Christian Grothoff
     21  */
     22 
     23 #include "taler/platform.h"
     24 #include <gnunet/gnunet_json_lib.h>
     25 #include <gnunet/gnunet_common.h>
     26 #include <taler/taler_json_lib.h>
     27 #include <jansson.h>
     28 #include "taler/taler_util.h"
     29 #include "taler/taler_merchant_util.h"
     30 
     31 /**
     32  * Get JSON representation of merchant details.
     33  *
     34  * @param[in] contract contract terms
     35  * @return JSON object with merchant details; NULL on error
     36  */
     37 static json_t *
     38 json_from_merchant_details (
     39   const struct TALER_MERCHANT_Contract *contract)
     40 {
     41   return GNUNET_JSON_PACK (
     42     GNUNET_JSON_pack_string ("name",
     43                              contract->merchant.name),
     44     GNUNET_JSON_pack_allow_null (
     45       GNUNET_JSON_pack_string ("email",
     46                                contract->merchant.email)),
     47     GNUNET_JSON_pack_allow_null (
     48       GNUNET_JSON_pack_string ("website",
     49                                contract->merchant.website)),
     50     GNUNET_JSON_pack_allow_null (
     51       GNUNET_JSON_pack_string ("logo",
     52                                contract->merchant.logo)),
     53     GNUNET_JSON_pack_allow_null (
     54       GNUNET_JSON_pack_object_steal ("address",
     55                                      contract->merchant.address)),
     56     GNUNET_JSON_pack_allow_null (
     57       GNUNET_JSON_pack_object_steal ("jurisdiction",
     58                                      contract->merchant.jurisdiction)));
     59 }
     60 
     61 
     62 /**
     63  * Get JSON representation of contract choice input.
     64  *
     65  * @param[in] input contract terms choice input
     66  * @return JSON representation of @a input; NULL on error
     67  */
     68 static json_t *
     69 json_from_contract_input (
     70   const struct TALER_MERCHANT_ContractInput *input)
     71 {
     72   switch (input->type)
     73   {
     74   case TALER_MERCHANT_CONTRACT_INPUT_TYPE_INVALID:
     75     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     76                 "invalid contract input type");
     77     GNUNET_assert (0);
     78     return NULL;
     79   case TALER_MERCHANT_CONTRACT_INPUT_TYPE_TOKEN:
     80     return GNUNET_JSON_PACK (
     81       GNUNET_JSON_pack_string ("type",
     82                                "token"),
     83       GNUNET_JSON_pack_string ("token_family_slug",
     84                                input->details.token.token_family_slug),
     85       GNUNET_JSON_pack_int64 ("count",
     86                               input->details.token.count));
     87   }
     88 
     89   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     90               "unsupported contract input type %d",
     91               input->type);
     92   GNUNET_assert (0);
     93   return NULL;
     94 }
     95 
     96 
     97 /**
     98  * Get JSON representation of contract choice output.
     99  *
    100  * @param[in] output contract terms choice output
    101  * @return JSON representation of @a output; NULL on error
    102  */
    103 static json_t *
    104 json_from_contract_output (
    105   const struct TALER_MERCHANT_ContractOutput *output)
    106 {
    107   switch (output->type)
    108   {
    109   case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
    110     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    111                 "invalid contract output type");
    112     GNUNET_assert (0);
    113     return NULL;
    114   case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
    115     return GNUNET_JSON_PACK (
    116       GNUNET_JSON_pack_string ("type",
    117                                "token"),
    118       GNUNET_JSON_pack_string ("token_family_slug",
    119                                output->details.token.token_family_slug),
    120       GNUNET_JSON_pack_uint64 ("count",
    121                                output->details.token.count),
    122       GNUNET_JSON_pack_uint64 ("key_index",
    123                                output->details.token.key_index));
    124   case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
    125     {
    126       json_t *donau_urls;
    127 
    128       donau_urls = json_array ();
    129       GNUNET_assert (NULL != donau_urls);
    130       for (unsigned i = 0;
    131            i < output->details.donation_receipt.donau_urls_len;
    132            i++)
    133         GNUNET_assert (0 ==
    134                        json_array_append_new (
    135                          donau_urls,
    136                          json_string (
    137                            output->details.donation_receipt.donau_urls[i])));
    138 
    139       return GNUNET_JSON_PACK (
    140         GNUNET_JSON_pack_string ("type",
    141                                  "tax-receipt"),
    142         GNUNET_JSON_pack_array_steal ("donau_urls",
    143                                       donau_urls),
    144         GNUNET_JSON_pack_allow_null (
    145           TALER_JSON_pack_amount ("amount",
    146                                   &output->details.donation_receipt.amount)));
    147     }
    148   }
    149   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    150               "Unsupported contract output type %d",
    151               output->type);
    152   GNUNET_assert (0);
    153   return NULL;
    154 }
    155 
    156 
    157 json_t *
    158 TALER_MERCHANT_json_from_contract_choice (
    159   const struct TALER_MERCHANT_ContractChoice *choice,
    160   bool order)
    161 {
    162   json_t *inputs;
    163   json_t *outputs;
    164 
    165   inputs = json_array ();
    166   GNUNET_assert (NULL != inputs);
    167   for (unsigned int i = 0; i < choice->inputs_len; i++)
    168     GNUNET_assert (0 ==
    169                    json_array_append_new (inputs,
    170                                           json_from_contract_input (
    171                                             &choice->inputs[i])));
    172   outputs = json_array ();
    173   GNUNET_assert (NULL != outputs);
    174   for (unsigned int i = 0; i < choice->outputs_len; i++)
    175     GNUNET_assert (0 ==
    176                    json_array_append_new (outputs,
    177                                           json_from_contract_output (
    178                                             &choice->outputs[i])));
    179 
    180   return GNUNET_JSON_PACK (
    181     TALER_JSON_pack_amount ("amount",
    182                             &choice->amount),
    183     GNUNET_JSON_pack_allow_null (
    184       TALER_JSON_pack_amount ("tip",
    185                               choice->no_tip
    186                               ? NULL
    187                               : &choice->tip)),
    188     GNUNET_JSON_pack_allow_null (
    189       GNUNET_JSON_pack_string ("description",
    190                                choice->description)),
    191     GNUNET_JSON_pack_allow_null (
    192       GNUNET_JSON_pack_object_incref ("description_i18n",
    193                                       choice->description_i18n)),
    194     (order)
    195     ? GNUNET_JSON_pack_allow_null (
    196       TALER_JSON_pack_amount (
    197         "max_fee",
    198         /* workaround for nullable amount */
    199         (GNUNET_OK ==
    200          TALER_amount_is_valid (&choice->max_fee))
    201         ? &choice->max_fee
    202         : NULL))
    203     : TALER_JSON_pack_amount ("max_fee",
    204                               &choice->max_fee),
    205     (order)
    206     ? GNUNET_JSON_pack_allow_null (
    207       GNUNET_JSON_pack_array_steal ("inputs",
    208                                     inputs))
    209     : GNUNET_JSON_pack_array_steal ("inputs",
    210                                     inputs),
    211     (order)
    212     ? GNUNET_JSON_pack_allow_null (
    213       GNUNET_JSON_pack_array_steal ("outputs",
    214                                     outputs))
    215     : GNUNET_JSON_pack_array_steal ("outputs",
    216                                     outputs));
    217 }
    218 
    219 
    220 /**
    221  * Get JSON representation of contract token family key.
    222  *
    223  * @param[in] key contract token family key
    224  * @return JSON representation of @a key; NULL on error
    225  */
    226 static json_t *
    227 json_from_token_family_key (
    228   const struct TALER_MERCHANT_ContractTokenFamilyKey *key)
    229 {
    230   return GNUNET_JSON_PACK (
    231     GNUNET_JSON_pack_timestamp ("signature_validity_start",
    232                                 key->valid_after),
    233     GNUNET_JSON_pack_timestamp ("signature_validity_end",
    234                                 key->valid_before),
    235     TALER_JSON_pack_token_pub (NULL,
    236                                &key->pub));
    237 }
    238 
    239 
    240 /**
    241  * Get JSON representation of contract token family details.
    242  *
    243  * @param[in] family contract token family
    244  * @return JSON representation of @a family->details; NULL on error
    245  */
    246 static json_t *
    247 json_from_token_family_details (
    248   const struct TALER_MERCHANT_ContractTokenFamily *family)
    249 {
    250   switch (family->kind)
    251   {
    252   case TALER_MERCHANT_CONTRACT_TOKEN_KIND_INVALID:
    253     break;
    254   case TALER_MERCHANT_CONTRACT_TOKEN_KIND_SUBSCRIPTION:
    255     {
    256       json_t *trusted_domains;
    257 
    258       trusted_domains = json_array ();
    259       GNUNET_assert (NULL != trusted_domains);
    260       for (unsigned int i = 0;
    261            i < family->details.subscription.trusted_domains_len;
    262            i++)
    263         GNUNET_assert (0 ==
    264                        json_array_append_new (
    265                          trusted_domains,
    266                          json_string (
    267                            family->details.subscription.trusted_domains[i])));
    268 
    269       return GNUNET_JSON_PACK (
    270         GNUNET_JSON_pack_string ("class",
    271                                  "subscription"),
    272         GNUNET_JSON_pack_array_steal ("trusted_domains",
    273                                       trusted_domains));
    274     }
    275   case TALER_MERCHANT_CONTRACT_TOKEN_KIND_DISCOUNT:
    276     {
    277       json_t *expected_domains;
    278 
    279       expected_domains = json_array ();
    280       GNUNET_assert (NULL != expected_domains);
    281       for (unsigned int i = 0;
    282            i < family->details.discount.expected_domains_len;
    283            i++)
    284         GNUNET_assert (0 ==
    285                        json_array_append_new (
    286                          expected_domains,
    287                          json_string (
    288                            family->details.discount.expected_domains[i])));
    289 
    290       return GNUNET_JSON_PACK (
    291         GNUNET_JSON_pack_string ("class",
    292                                  "discount"),
    293         GNUNET_JSON_pack_array_steal ("expected_domains",
    294                                       expected_domains));
    295     }
    296   }
    297 
    298   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    299               "unsupported token family kind %d",
    300               family->kind);
    301   GNUNET_assert (0);
    302   return NULL;
    303 }
    304 
    305 
    306 json_t *
    307 TALER_MERCHANT_json_from_token_family (
    308   const struct TALER_MERCHANT_ContractTokenFamily *family)
    309 {
    310   json_t *keys;
    311 
    312   keys = json_array ();
    313   GNUNET_assert (NULL != keys);
    314   for (unsigned int i = 0; i < family->keys_len; i++)
    315     GNUNET_assert (0 == json_array_append_new (
    316                      keys,
    317                      json_from_token_family_key (
    318                        &family->keys[i])));
    319 
    320   return GNUNET_JSON_PACK (
    321     GNUNET_JSON_pack_string ("name",
    322                              family->name),
    323     GNUNET_JSON_pack_string ("description",
    324                              family->description),
    325     GNUNET_JSON_pack_object_incref ("description_i18n",
    326                                     family->description_i18n),
    327     GNUNET_JSON_pack_array_steal ("keys",
    328                                   keys),
    329     GNUNET_JSON_pack_object_steal ("details",
    330                                    json_from_token_family_details (family)),
    331     GNUNET_JSON_pack_bool ("critical",
    332                            family->critical));
    333 }
    334 
    335 
    336 /**
    337  * Get JSON object with contract terms v0-specific fields.
    338  *
    339  * @param[in] input contract terms v0
    340  * @return JSON object with @a input v0 fields; NULL on error
    341  */
    342 static json_t *
    343 json_from_contract_v0 (
    344   const struct TALER_MERCHANT_Contract *input)
    345 {
    346   return GNUNET_JSON_PACK (
    347     TALER_JSON_pack_amount ("amount",
    348                             &input->details.v0.brutto),
    349     GNUNET_JSON_pack_allow_null (
    350       TALER_JSON_pack_amount ("tip",
    351                               input->details.v0.no_tip
    352                               ? NULL
    353                               : &input->details.v0.tip)),
    354     TALER_JSON_pack_amount ("max_fee",
    355                             &input->details.v0.max_fee));
    356 }
    357 
    358 
    359 /**
    360  * Get JSON object with contract terms v1-specific fields.
    361  *
    362  * @param[in] input contract terms v1
    363  * @return JSON object with @a input v1 fields; NULL on error
    364  */
    365 static json_t *
    366 json_from_contract_v1 (
    367   const struct TALER_MERCHANT_Contract *input)
    368 {
    369   json_t *choices;
    370   json_t *families;
    371 
    372   choices = json_array ();
    373   GNUNET_assert (0 != choices);
    374   for (unsigned i = 0; i < input->details.v1.choices_len; i++)
    375     GNUNET_assert (0 == json_array_append_new (
    376                      choices,
    377                      TALER_MERCHANT_json_from_contract_choice (
    378                        &input->details.v1.choices[i],
    379                        false)));
    380 
    381   families = json_object ();
    382   GNUNET_assert (0 != families);
    383   for (unsigned i = 0; i < input->details.v1.token_authorities_len; i++)
    384     GNUNET_assert (0 == json_object_set_new (
    385                      families,
    386                      input->details.v1.token_authorities[i].slug,
    387                      TALER_MERCHANT_json_from_token_family (
    388                        &input->details.v1.token_authorities[i])));
    389 
    390   return GNUNET_JSON_PACK (
    391     GNUNET_JSON_pack_array_steal ("choices",
    392                                   choices),
    393     GNUNET_JSON_pack_object_steal ("token_families",
    394                                    families));
    395 }
    396 
    397 
    398 /**
    399  * Convert quantity @a q into a string for JSON serialization
    400  *
    401  * @param q quantity to convert
    402  * @return formatted string
    403  */
    404 static const char *
    405 quantity_to_string (const struct TALER_MERCHANT_ProductQuantity *q)
    406 {
    407   static char res[64];
    408 
    409   TALER_MERCHANT_vk_format_fractional_string (TALER_MERCHANT_VK_QUANTITY,
    410                                               q->integer,
    411                                               q->fractional,
    412                                               sizeof (res),
    413                                               res);
    414   return res;
    415 }
    416 
    417 
    418 json_t *
    419 TALER_MERCHANT_product_sold_serialize (
    420   const struct TALER_MERCHANT_ProductSold *p)
    421 {
    422   json_t *prices;
    423 
    424   prices = json_array ();
    425   GNUNET_assert (NULL != prices);
    426   for (unsigned int i = 0; i<p->prices_length; i++)
    427     GNUNET_assert (0 ==
    428                    json_array_append_new (prices,
    429                                           TALER_JSON_from_amount (
    430                                             &p->prices[i])));
    431   return GNUNET_JSON_PACK (
    432     GNUNET_JSON_pack_allow_null (
    433       GNUNET_JSON_pack_string ("product_id",
    434                                p->product_id)),
    435     GNUNET_JSON_pack_allow_null (
    436       GNUNET_JSON_pack_string ("product_name",
    437                                p->product_name)),
    438     GNUNET_JSON_pack_allow_null (
    439       GNUNET_JSON_pack_string ("description",
    440                                p->description)),
    441     GNUNET_JSON_pack_allow_null (
    442       GNUNET_JSON_pack_object_incref ("description_i18n",
    443                                       (json_t *) p->description_i18n)),
    444     GNUNET_JSON_pack_allow_null (
    445       ( (0 != p->unit_quantity.integer) ||
    446         (0 != p->unit_quantity.fractional) )
    447       ? GNUNET_JSON_pack_string ("unit_quantity",
    448                                  quantity_to_string (&p->unit_quantity))
    449       : GNUNET_JSON_pack_string ("dummy",
    450                                  NULL) ),
    451     /* Legacy */
    452     GNUNET_JSON_pack_allow_null (
    453       (0 == p->unit_quantity.fractional)
    454       ? GNUNET_JSON_pack_uint64 ("quantity",
    455                                  p->unit_quantity.integer)
    456       : GNUNET_JSON_pack_string ("dummy",
    457                                  NULL) ),
    458     GNUNET_JSON_pack_allow_null (
    459       GNUNET_JSON_pack_string ("unit",
    460                                p->unit)),
    461     /* Deprecated, use prices! */
    462     GNUNET_JSON_pack_allow_null (
    463       TALER_JSON_pack_amount ("price",
    464                               0 < p->prices_length
    465                               ? &p->prices[0]
    466                               : NULL)),
    467     GNUNET_JSON_pack_array_steal ("prices",
    468                                   prices),
    469     GNUNET_JSON_pack_allow_null (
    470       GNUNET_JSON_pack_string ("image",
    471                                p->image)),
    472     GNUNET_JSON_pack_allow_null (
    473       GNUNET_JSON_pack_array_incref ("taxes",
    474                                      (json_t *) p->taxes)),
    475     GNUNET_JSON_pack_allow_null (
    476       GNUNET_TIME_absolute_is_never (p->delivery_date.abs_time)
    477       ? GNUNET_JSON_pack_string ("dummy",
    478                                  NULL)
    479       : GNUNET_JSON_pack_timestamp ("delivery_date",
    480                                     p->delivery_date)),
    481     GNUNET_JSON_pack_uint64 ("product_money_pot",
    482                              p->product_money_pot));
    483 }
    484 
    485 
    486 json_t *
    487 TALER_MERCHANT_contract_serialize (
    488   const struct TALER_MERCHANT_Contract *input,
    489   bool nonce_optional)
    490 {
    491   json_t *details;
    492   json_t *products;
    493 
    494   switch (input->version)
    495   {
    496   case TALER_MERCHANT_CONTRACT_VERSION_0:
    497     details = json_from_contract_v0 (input);
    498     goto success;
    499   case TALER_MERCHANT_CONTRACT_VERSION_1:
    500     details = json_from_contract_v1 (input);
    501     goto success;
    502   }
    503 
    504   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    505               "unknown contract type version %d",
    506               input->version);
    507   GNUNET_assert (0);
    508   return NULL;
    509 
    510 success:
    511   products = json_array ();
    512   GNUNET_assert (NULL != products);
    513   for (size_t i = 0; i<input->products_len; i++)
    514   {
    515     GNUNET_assert (
    516       0 ==
    517       json_array_append_new (products,
    518                              TALER_MERCHANT_product_sold_serialize (
    519                                &input->products[i])));
    520   }
    521 
    522   return GNUNET_JSON_PACK (
    523     GNUNET_JSON_pack_uint64 ("version",
    524                              input->version),
    525     GNUNET_JSON_pack_string ("summary",
    526                              input->summary),
    527     GNUNET_JSON_pack_allow_null (
    528       GNUNET_JSON_pack_object_steal ("summary_i18n",
    529                                      input->summary_i18n)),
    530     GNUNET_JSON_pack_string ("order_id",
    531                              input->order_id),
    532     GNUNET_JSON_pack_allow_null (
    533       GNUNET_JSON_pack_string ("public_reorder_url",
    534                                input->public_reorder_url)),
    535     GNUNET_JSON_pack_allow_null (
    536       GNUNET_JSON_pack_string ("fulfillment_url",
    537                                input->fulfillment_url)),
    538     GNUNET_JSON_pack_allow_null (
    539       GNUNET_JSON_pack_string ("fulfillment_message",
    540                                input->fulfillment_message)),
    541     GNUNET_JSON_pack_allow_null (
    542       GNUNET_JSON_pack_object_steal ("fulfillment_message_i18n",
    543                                      input->fulfillment_message_i18n)),
    544     GNUNET_JSON_pack_array_steal ("products",
    545                                   products),
    546     GNUNET_JSON_pack_timestamp ("timestamp",
    547                                 input->timestamp),
    548     GNUNET_JSON_pack_timestamp ("refund_deadline",
    549                                 input->refund_deadline),
    550     GNUNET_JSON_pack_timestamp ("pay_deadline",
    551                                 input->pay_deadline),
    552     GNUNET_JSON_pack_timestamp ("wire_transfer_deadline",
    553                                 input->wire_deadline),
    554     GNUNET_JSON_pack_data_auto ("merchant_pub",
    555                                 &input->merchant_pub.eddsa_pub),
    556     GNUNET_JSON_pack_string ("merchant_base_url",
    557                              input->merchant_base_url),
    558     GNUNET_JSON_pack_object_steal ("merchant",
    559                                    json_from_merchant_details (input)),
    560     GNUNET_JSON_pack_data_auto ("h_wire",
    561                                 &input->h_wire),
    562     GNUNET_JSON_pack_string ("wire_method",
    563                              input->wire_method),
    564     GNUNET_JSON_pack_array_steal ("exchanges",
    565                                   input->exchanges),
    566     GNUNET_JSON_pack_allow_null (
    567       GNUNET_JSON_pack_object_steal ("delivery_location",
    568                                      input->delivery_location)),
    569     GNUNET_JSON_pack_allow_null (
    570       GNUNET_JSON_pack_timestamp ("delivery_date",
    571                                   input->delivery_date)),
    572     (nonce_optional)
    573     ? GNUNET_JSON_pack_allow_null (
    574       GNUNET_JSON_pack_string ("nonce",
    575                                input->nonce))
    576     : GNUNET_JSON_pack_string ("nonce",
    577                                input->nonce),
    578     GNUNET_JSON_pack_allow_null (
    579       GNUNET_JSON_pack_time_rel ("auto_refund",
    580                                  input->auto_refund)),
    581     GNUNET_JSON_pack_allow_null (
    582       GNUNET_JSON_pack_object_steal ("extra",
    583                                      input->extra)),
    584     GNUNET_JSON_pack_allow_null (
    585       GNUNET_JSON_pack_uint64 ("minimum_age",
    586                                input->minimum_age)),
    587     (0 == input->default_money_pot)
    588     ? GNUNET_JSON_pack_allow_null (
    589       GNUNET_JSON_pack_string ("dummy",
    590                                NULL))
    591     : GNUNET_JSON_pack_uint64 ("default_money_pot",
    592                                input->default_money_pot),
    593     GNUNET_JSON_pack_object_steal (NULL,
    594                                    details));
    595 }