merchant

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

merchant_api_post-private-products.c (17238B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2020-2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU Lesser General Public License as
      7   published by the Free Software Foundation; either version 2.1,
      8   or (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful,
     11   but WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU Lesser General Public License for more details.
     14 
     15   You should have received a copy of the GNU Lesser General
     16   Public License along with TALER; see the file COPYING.LGPL.
     17   If not, see <http://www.gnu.org/licenses/>
     18 */
     19 /**
     20  * @file merchant_api_post-private-products-new.c
     21  * @brief Implementation of the POST /private/products request
     22  * @author Christian Grothoff
     23  */
     24 #include "taler/platform.h"
     25 #include <curl/curl.h>
     26 #include <jansson.h>
     27 #include <microhttpd.h> /* just for HTTP status codes */
     28 #include <gnunet/gnunet_util_lib.h>
     29 #include <gnunet/gnunet_curl_lib.h>
     30 #include <taler/taler-merchant/post-private-products.h>
     31 #include "merchant_api_curl_defaults.h"
     32 #include "merchant_api_common.h"
     33 #include <taler/taler_json_lib.h>
     34 #include <taler/taler_curl_lib.h>
     35 
     36 
     37 /**
     38  * Handle for a POST /private/products operation.
     39  */
     40 struct TALER_MERCHANT_PostPrivateProductsHandle
     41 {
     42   /**
     43    * Base URL of the merchant backend.
     44    */
     45   char *base_url;
     46 
     47   /**
     48    * The full URL for this request.
     49    */
     50   char *url;
     51 
     52   /**
     53    * Handle for the request.
     54    */
     55   struct GNUNET_CURL_Job *job;
     56 
     57   /**
     58    * Function to call with the result.
     59    */
     60   TALER_MERCHANT_PostPrivateProductsCallback cb;
     61 
     62   /**
     63    * Closure for @a cb.
     64    */
     65   TALER_MERCHANT_POST_PRIVATE_PRODUCTS_RESULT_CLOSURE *cb_cls;
     66 
     67   /**
     68    * Reference to the execution context.
     69    */
     70   struct GNUNET_CURL_Context *ctx;
     71 
     72   /**
     73    * Minor context that holds body and headers.
     74    */
     75   struct TALER_CURL_PostContext post_ctx;
     76 
     77   /**
     78    * Product identifier.
     79    */
     80   char *product_id;
     81 
     82   /**
     83    * Human-readable description.
     84    */
     85   char *description;
     86 
     87   /**
     88    * Unit of measurement.
     89    */
     90   char *unit;
     91 
     92   /**
     93    * Product image (base64-encoded or empty), set via option.
     94    */
     95   char *image;
     96 
     97   /**
     98    * Explicit product name (if different from description).
     99    */
    100   char *product_name;
    101 
    102   /**
    103    * Total stock (-1 for unlimited).
    104    */
    105   int64_t total_stock;
    106 
    107   /**
    108    * Product group ID.
    109    */
    110   uint64_t product_group_id;
    111 
    112   /**
    113    * Whether product_group_id has been set.
    114    */
    115   bool have_product_group_id;
    116 
    117   /**
    118    * Money pot ID.
    119    */
    120   uint64_t money_pot_id;
    121 
    122   /**
    123    * Whether money_pot_id has been set.
    124    */
    125   bool have_money_pot_id;
    126 
    127   /**
    128    * Whether the price is net (before tax).
    129    */
    130   bool price_is_net;
    131 
    132   /**
    133    * Whether price_is_net has been set.
    134    */
    135   bool have_price_is_net;
    136 
    137   /**
    138    * Optional internationalized descriptions (JSON).
    139    */
    140   json_t *description_i18n;
    141 
    142   /**
    143    * Optional tax information (JSON array).
    144    */
    145   json_t *taxes;
    146 
    147   /**
    148    * Optional storage location (JSON).
    149    */
    150   json_t *address;
    151 
    152   /**
    153    * Optional expected restock time.
    154    */
    155   struct GNUNET_TIME_Timestamp next_restock;
    156 
    157   /**
    158    * Whether next_restock has been set.
    159    */
    160   bool have_next_restock;
    161 
    162   /**
    163    * Optional minimum age requirement.
    164    */
    165   uint32_t minimum_age;
    166 
    167   /**
    168    * Whether minimum_age has been set.
    169    */
    170   bool have_minimum_age;
    171 
    172   /**
    173    * Optional category IDs.
    174    */
    175   uint64_t *cats;
    176 
    177   /**
    178    * Number of category IDs.
    179    */
    180   unsigned int num_cats;
    181 
    182   /**
    183    * Array of unit prices.
    184    */
    185   struct TALER_Amount *unit_prices;
    186 
    187   /**
    188    * Length of @e unit_prices array.
    189    */
    190   size_t unit_prices_len;
    191 
    192   /**
    193    * Fractional part of total stock.
    194    */
    195   uint32_t total_stock_frac;
    196 
    197   /**
    198    * Whether fractional quantities are allowed.
    199    */
    200   bool unit_allow_fraction;
    201 
    202   /**
    203    * Whether unit_allow_fraction has been set.
    204    */
    205   bool have_unit_allow_fraction;
    206 
    207   /**
    208    * Precision level for fractions.
    209    */
    210   uint32_t unit_precision_level;
    211 
    212   /**
    213    * Whether unit_precision_level has been set.
    214    */
    215   bool have_unit_precision_level;
    216 };
    217 
    218 
    219 /**
    220  * Function called when we're done processing the
    221  * HTTP POST /private/products request.
    222  *
    223  * @param cls the `struct TALER_MERCHANT_PostPrivateProductsHandle`
    224  * @param response_code HTTP response code, 0 on error
    225  * @param response response body, NULL if not in JSON
    226  */
    227 static void
    228 handle_post_products_finished (void *cls,
    229                                long response_code,
    230                                const void *response)
    231 {
    232   struct TALER_MERCHANT_PostPrivateProductsHandle *ppph = cls;
    233   const json_t *json = response;
    234   struct TALER_MERCHANT_PostPrivateProductsResponse ppr = {
    235     .hr.http_status = (unsigned int) response_code,
    236     .hr.reply = json
    237   };
    238 
    239   ppph->job = NULL;
    240   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    241               "POST /private/products completed with response code %u\n",
    242               (unsigned int) response_code);
    243   switch (response_code)
    244   {
    245   case 0:
    246     ppr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    247     break;
    248   case MHD_HTTP_NO_CONTENT:
    249     break;
    250   case MHD_HTTP_BAD_REQUEST:
    251     ppr.hr.ec = TALER_JSON_get_error_code (json);
    252     ppr.hr.hint = TALER_JSON_get_error_hint (json);
    253     break;
    254   case MHD_HTTP_UNAUTHORIZED:
    255     ppr.hr.ec = TALER_JSON_get_error_code (json);
    256     ppr.hr.hint = TALER_JSON_get_error_hint (json);
    257     break;
    258   case MHD_HTTP_FORBIDDEN:
    259     ppr.hr.ec = TALER_JSON_get_error_code (json);
    260     ppr.hr.hint = TALER_JSON_get_error_hint (json);
    261     break;
    262   case MHD_HTTP_NOT_FOUND:
    263     ppr.hr.ec = TALER_JSON_get_error_code (json);
    264     ppr.hr.hint = TALER_JSON_get_error_hint (json);
    265     break;
    266   case MHD_HTTP_CONFLICT:
    267     ppr.hr.ec = TALER_JSON_get_error_code (json);
    268     ppr.hr.hint = TALER_JSON_get_error_hint (json);
    269     break;
    270   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    271     ppr.hr.ec = TALER_JSON_get_error_code (json);
    272     ppr.hr.hint = TALER_JSON_get_error_hint (json);
    273     break;
    274   default:
    275     TALER_MERCHANT_parse_error_details_ (json,
    276                                          response_code,
    277                                          &ppr.hr);
    278     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    279                 "Unexpected response code %u/%d\n",
    280                 (unsigned int) response_code,
    281                 (int) ppr.hr.ec);
    282     GNUNET_break_op (0);
    283     break;
    284   }
    285   ppph->cb (ppph->cb_cls,
    286             &ppr);
    287   TALER_MERCHANT_post_private_products_cancel (ppph);
    288 }
    289 
    290 
    291 struct TALER_MERCHANT_PostPrivateProductsHandle *
    292 TALER_MERCHANT_post_private_products_create (
    293   struct GNUNET_CURL_Context *ctx,
    294   const char *url,
    295   const char *product_id,
    296   const char *description,
    297   const char *unit,
    298   unsigned int num_prices,
    299   const struct TALER_Amount prices[static num_prices])
    300 {
    301   struct TALER_MERCHANT_PostPrivateProductsHandle *ppph;
    302 
    303   ppph = GNUNET_new (struct TALER_MERCHANT_PostPrivateProductsHandle);
    304   ppph->ctx = ctx;
    305   ppph->base_url = GNUNET_strdup (url);
    306   ppph->product_id = GNUNET_strdup (product_id);
    307   ppph->description = GNUNET_strdup (description);
    308   ppph->unit = GNUNET_strdup (unit);
    309   ppph->unit_prices_len = num_prices;
    310   ppph->unit_prices = GNUNET_new_array (num_prices,
    311                                         struct TALER_Amount);
    312   memcpy (ppph->unit_prices,
    313           prices,
    314           num_prices * sizeof (struct TALER_Amount));
    315   return ppph;
    316 }
    317 
    318 
    319 enum GNUNET_GenericReturnValue
    320 TALER_MERCHANT_post_private_products_set_options_ (
    321   struct TALER_MERCHANT_PostPrivateProductsHandle *ppph,
    322   unsigned int num_options,
    323   const struct TALER_MERCHANT_PostPrivateProductsOptionValue *options)
    324 {
    325   for (unsigned int i = 0; i < num_options; i++)
    326   {
    327     switch (options[i].option)
    328     {
    329     case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_END:
    330       return GNUNET_OK;
    331     case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_DESCRIPTION_I18N:
    332       json_decref (ppph->description_i18n);
    333       ppph->description_i18n = json_incref ((json_t *) options[i].details.
    334                                             description_i18n);
    335       continue;
    336     case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_TAXES:
    337       json_decref (ppph->taxes);
    338       ppph->taxes = json_incref ((json_t *) options[i].details.taxes);
    339       continue;
    340     case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_ADDRESS:
    341       json_decref (ppph->address);
    342       ppph->address = json_incref ((json_t *) options[i].details.address);
    343       continue;
    344     case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_NEXT_RESTOCK:
    345       ppph->next_restock = options[i].details.next_restock;
    346       ppph->have_next_restock = true;
    347       continue;
    348     case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_MINIMUM_AGE:
    349       ppph->minimum_age = options[i].details.minimum_age;
    350       ppph->have_minimum_age = true;
    351       continue;
    352     case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_CATEGORIES:
    353       ppph->num_cats = options[i].details.categories.num;
    354       GNUNET_free (ppph->cats);
    355       ppph->cats = GNUNET_new_array (ppph->num_cats,
    356                                      uint64_t);
    357       memcpy (ppph->cats,
    358               options[i].details.categories.cats,
    359               ppph->num_cats * sizeof (uint64_t));
    360       continue;
    361     case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_TOTAL_STOCK_FRAC:
    362       ppph->total_stock_frac = options[i].details.total_stock.frac;
    363       continue;
    364     case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_TOTAL_STOCK_VAL:
    365       ppph->total_stock = options[i].details.total_stock.val;
    366       continue;
    367     case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_TOTAL_STOCK:
    368       ppph->total_stock_frac = options[i].details.total_stock.frac;
    369       ppph->total_stock = options[i].details.total_stock.val;
    370       continue;
    371     case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_UNIT_ALLOW_FRACTION:
    372       ppph->unit_allow_fraction = options[i].details.unit_allow_fraction;
    373       ppph->have_unit_allow_fraction = true;
    374       continue;
    375     case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_UNIT_PRECISION_LEVEL:
    376       ppph->unit_precision_level = options[i].details.unit_precision_level;
    377       ppph->have_unit_precision_level = true;
    378       continue;
    379     case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_PRODUCT_NAME:
    380       GNUNET_free (ppph->product_name);
    381       ppph->product_name = GNUNET_strdup (options[i].details.product_name);
    382       continue;
    383     case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_IMAGE:
    384       GNUNET_free (ppph->image);
    385       ppph->image = GNUNET_strdup (options[i].details.image);
    386       continue;
    387     case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_PRODUCT_GROUP_ID:
    388       ppph->product_group_id = options[i].details.product_group_id;
    389       ppph->have_product_group_id = true;
    390       continue;
    391     case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_MONEY_POT_ID:
    392       ppph->money_pot_id = options[i].details.money_pot_id;
    393       ppph->have_money_pot_id = true;
    394       continue;
    395     case TALER_MERCHANT_POST_PRIVATE_PRODUCTS_OPTION_PRICE_IS_NET:
    396       ppph->price_is_net = options[i].details.price_is_net;
    397       ppph->have_price_is_net = true;
    398       continue;
    399     }
    400     GNUNET_break (0);
    401     return GNUNET_SYSERR;
    402   }
    403   return GNUNET_OK;
    404 }
    405 
    406 
    407 enum TALER_ErrorCode
    408 TALER_MERCHANT_post_private_products_start (
    409   struct TALER_MERCHANT_PostPrivateProductsHandle *ppph,
    410   TALER_MERCHANT_PostPrivateProductsCallback cb,
    411   TALER_MERCHANT_POST_PRIVATE_PRODUCTS_RESULT_CLOSURE *cb_cls)
    412 {
    413   json_t *req_obj;
    414   json_t *categories;
    415   CURL *eh;
    416   char unit_total_stock_buf[64];
    417 
    418   ppph->cb = cb;
    419   ppph->cb_cls = cb_cls;
    420   ppph->url = TALER_url_join (ppph->base_url,
    421                               "private/products",
    422                               NULL);
    423   if (NULL == ppph->url)
    424     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    425 
    426   TALER_MERCHANT_format_stock_string (ppph->total_stock,
    427                                       ppph->total_stock_frac,
    428                                       unit_total_stock_buf,
    429                                       sizeof (unit_total_stock_buf));
    430 
    431   if (0 == ppph->num_cats)
    432   {
    433     categories = NULL;
    434   }
    435   else
    436   {
    437     categories = json_array ();
    438     GNUNET_assert (NULL != categories);
    439     for (unsigned int i = 0; i < ppph->num_cats; i++)
    440       GNUNET_assert (0 ==
    441                      json_array_append_new (categories,
    442                                             json_integer (ppph->cats[i])));
    443   }
    444 
    445   req_obj = GNUNET_JSON_PACK (
    446     GNUNET_JSON_pack_string ("product_id",
    447                              ppph->product_id),
    448     GNUNET_JSON_pack_string ("product_name",
    449                              (NULL != ppph->product_name)
    450                              ? ppph->product_name
    451                              : ppph->description),
    452     GNUNET_JSON_pack_string ("description",
    453                              ppph->description),
    454     GNUNET_JSON_pack_allow_null (
    455       GNUNET_JSON_pack_object_incref ("description_i18n",
    456                                       ppph->description_i18n)),
    457     GNUNET_JSON_pack_allow_null (
    458       GNUNET_JSON_pack_array_steal ("categories",
    459                                     categories)),
    460     GNUNET_JSON_pack_string ("unit",
    461                              ppph->unit),
    462     TALER_JSON_pack_amount_array ("unit_price",
    463                                   ppph->unit_prices_len,
    464                                   ppph->unit_prices),
    465     GNUNET_JSON_pack_allow_null (
    466       GNUNET_JSON_pack_string ("image",
    467                                ppph->image)),
    468     GNUNET_JSON_pack_allow_null (
    469       GNUNET_JSON_pack_array_incref ("taxes",
    470                                      ppph->taxes)),
    471     GNUNET_JSON_pack_string ("unit_total_stock",
    472                              unit_total_stock_buf),
    473     GNUNET_JSON_pack_allow_null (
    474       GNUNET_JSON_pack_object_incref ("address",
    475                                       ppph->address)),
    476     GNUNET_JSON_pack_allow_null (
    477       GNUNET_JSON_pack_timestamp ("next_restock",
    478                                   ppph->have_next_restock
    479                                   ? ppph->next_restock
    480                                   : GNUNET_TIME_UNIT_ZERO_TS)));
    481   if (ppph->have_minimum_age)
    482   {
    483     GNUNET_assert (0 ==
    484                    json_object_set_new (req_obj,
    485                                         "minimum_age",
    486                                         json_integer (
    487                                           ppph->minimum_age)));
    488   }
    489   if (ppph->have_product_group_id)
    490   {
    491     GNUNET_assert (0 ==
    492                    json_object_set_new (req_obj,
    493                                         "product_group_id",
    494                                         json_integer (
    495                                           ppph->product_group_id)));
    496   }
    497   if (ppph->have_money_pot_id)
    498   {
    499     GNUNET_assert (0 ==
    500                    json_object_set_new (req_obj,
    501                                         "money_pot_id",
    502                                         json_integer (
    503                                           ppph->money_pot_id)));
    504   }
    505   if (ppph->have_price_is_net)
    506   {
    507     GNUNET_assert (0 ==
    508                    json_object_set_new (req_obj,
    509                                         "price_is_net",
    510                                         json_boolean (
    511                                           ppph->price_is_net)));
    512   }
    513   if (ppph->have_unit_allow_fraction &&
    514       ppph->unit_allow_fraction)
    515   {
    516     GNUNET_assert (0 ==
    517                    json_object_set_new (req_obj,
    518                                         "unit_allow_fraction",
    519                                         json_true ()));
    520     if (ppph->have_unit_precision_level)
    521     {
    522       GNUNET_assert (0 ==
    523                      json_object_set_new (req_obj,
    524                                           "unit_precision_level",
    525                                           json_integer (
    526                                             ppph->unit_precision_level)));
    527     }
    528   }
    529   eh = TALER_MERCHANT_curl_easy_get_ (ppph->url);
    530   if ( (NULL == eh) ||
    531        (GNUNET_OK !=
    532         TALER_curl_easy_post (&ppph->post_ctx,
    533                               eh,
    534                               req_obj)) )
    535   {
    536     GNUNET_break (0);
    537     json_decref (req_obj);
    538     if (NULL != eh)
    539       curl_easy_cleanup (eh);
    540     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    541   }
    542   json_decref (req_obj);
    543   ppph->job = GNUNET_CURL_job_add2 (ppph->ctx,
    544                                     eh,
    545                                     ppph->post_ctx.headers,
    546                                     &handle_post_products_finished,
    547                                     ppph);
    548   if (NULL == ppph->job)
    549     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    550   return TALER_EC_NONE;
    551 }
    552 
    553 
    554 void
    555 TALER_MERCHANT_post_private_products_cancel (
    556   struct TALER_MERCHANT_PostPrivateProductsHandle *ppph)
    557 {
    558   if (NULL != ppph->job)
    559   {
    560     GNUNET_CURL_job_cancel (ppph->job);
    561     ppph->job = NULL;
    562   }
    563   TALER_curl_easy_post_finished (&ppph->post_ctx);
    564   json_decref (ppph->description_i18n);
    565   json_decref (ppph->taxes);
    566   json_decref (ppph->address);
    567   GNUNET_free (ppph->cats);
    568   GNUNET_free (ppph->product_id);
    569   GNUNET_free (ppph->description);
    570   GNUNET_free (ppph->unit);
    571   GNUNET_free (ppph->image);
    572   GNUNET_free (ppph->product_name);
    573   GNUNET_free (ppph->url);
    574   GNUNET_free (ppph->base_url);
    575   GNUNET_free (ppph);
    576 }
    577 
    578 
    579 /* end of merchant_api_post-private-products-new.c */