merchant

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

merchant_api_patch-private-products-PRODUCT_ID.c (16068B)


      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_patch-private-products-PRODUCT_ID.c
     21  * @brief Implementation of the PATCH /private/products/$PRODUCT_ID 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/patch-private-products-PRODUCT_ID.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 PATCH /private/products/$PRODUCT_ID operation.
     39  */
     40 struct TALER_MERCHANT_PatchPrivateProductHandle
     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_PatchPrivateProductCallback cb;
     61 
     62   /**
     63    * Closure for @a cb.
     64    */
     65   TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_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    * Identifier of the product to update.
     79    */
     80   char *product_id;
     81 
     82   /**
     83    * New human-readable description.
     84    */
     85   char *description;
     86 
     87   /**
     88    * Internationalized descriptions (JSON).
     89    */
     90   json_t *description_i18n;
     91 
     92   /**
     93    * Unit of measurement.
     94    */
     95   char *unit;
     96 
     97   /**
     98    * New base64-encoded image.
     99    */
    100   char *image;
    101 
    102   /**
    103    * Explicit product name (if different from description).
    104    */
    105   char *product_name;
    106 
    107   /**
    108    * Optional category IDs.
    109    */
    110   uint64_t *cats;
    111 
    112   /**
    113    * Product group ID.
    114    */
    115   uint64_t 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    * Whether product_group_id has been set.
    139    */
    140   bool have_product_group_id;
    141 
    142   /**
    143    * Number of category IDs.
    144    */
    145   unsigned int num_cats;
    146 
    147   /**
    148    * New tax information (JSON array).
    149    */
    150   json_t *taxes;
    151 
    152   /**
    153    * New total stock (-1 for unlimited).
    154    */
    155   int64_t total_stock;
    156 
    157   /**
    158    * Total units lost/expired.
    159    */
    160   uint64_t total_lost;
    161 
    162   /**
    163    * Storage location (JSON).
    164    */
    165   json_t *address;
    166 
    167   /**
    168    * Expected restock time.
    169    */
    170   struct GNUNET_TIME_Timestamp next_restock;
    171 
    172   /**
    173    * Array of unit prices.
    174    */
    175   struct TALER_Amount *unit_prices;
    176 
    177   /**
    178    * Optional minimum age requirement.
    179    */
    180   uint32_t minimum_age;
    181 
    182   /**
    183    * Whether minimum_age has been set.
    184    */
    185   bool have_minimum_age;
    186 
    187   /**
    188    * Number of prices in @e unit_prices.
    189    */
    190   size_t unit_price_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 @e next_restock was explicitly set.
    204    */
    205   bool have_next_restock;
    206 
    207   /**
    208    * Whether @e unit_allow_fraction was explicitly set.
    209    */
    210   bool have_unit_allow_fraction;
    211 
    212   /**
    213    * Precision level for fractions.
    214    */
    215   uint32_t unit_precision_level;
    216 
    217   /**
    218    * Whether @e unit_precision_level was explicitly set.
    219    */
    220   bool have_unit_precision_level;
    221 };
    222 
    223 
    224 /**
    225  * Function called when we're done processing the
    226  * HTTP PATCH /private/products/$PRODUCT_ID request.
    227  *
    228  * @param cls the `struct TALER_MERCHANT_PatchPrivateProductHandle`
    229  * @param response_code HTTP response code, 0 on error
    230  * @param response response body, NULL if not in JSON
    231  */
    232 static void
    233 handle_patch_product_finished (void *cls,
    234                                long response_code,
    235                                const void *response)
    236 {
    237   struct TALER_MERCHANT_PatchPrivateProductHandle *pph = cls;
    238   const json_t *json = response;
    239   struct TALER_MERCHANT_PatchPrivateProductResponse ppr = {
    240     .hr.http_status = (unsigned int) response_code,
    241     .hr.reply = json
    242   };
    243 
    244   pph->job = NULL;
    245   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    246               "PATCH /private/products/$PRODUCT_ID completed with response code %u\n",
    247               (unsigned int) response_code);
    248   switch (response_code)
    249   {
    250   case 0:
    251     ppr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    252     break;
    253   case MHD_HTTP_NO_CONTENT:
    254     break;
    255   case MHD_HTTP_BAD_REQUEST:
    256     ppr.hr.ec = TALER_JSON_get_error_code (json);
    257     ppr.hr.hint = TALER_JSON_get_error_hint (json);
    258     GNUNET_break_op (0);
    259     /* This should never happen, either us
    260      * or the merchant is buggy (or API version conflict);
    261      * just pass JSON reply to the application */
    262     break;
    263   case MHD_HTTP_UNAUTHORIZED:
    264     ppr.hr.ec = TALER_JSON_get_error_code (json);
    265     ppr.hr.hint = TALER_JSON_get_error_hint (json);
    266     /* Nothing really to verify, merchant says we need to authenticate. */
    267     break;
    268   case MHD_HTTP_FORBIDDEN:
    269     ppr.hr.ec = TALER_JSON_get_error_code (json);
    270     ppr.hr.hint = TALER_JSON_get_error_hint (json);
    271     break;
    272   case MHD_HTTP_NOT_FOUND:
    273     ppr.hr.ec = TALER_JSON_get_error_code (json);
    274     ppr.hr.hint = TALER_JSON_get_error_hint (json);
    275     break;
    276   case MHD_HTTP_CONFLICT:
    277     ppr.hr.ec = TALER_JSON_get_error_code (json);
    278     ppr.hr.hint = TALER_JSON_get_error_hint (json);
    279     break;
    280   case MHD_HTTP_INTERNAL_SERVER_ERROR:
    281     ppr.hr.ec = TALER_JSON_get_error_code (json);
    282     ppr.hr.hint = TALER_JSON_get_error_hint (json);
    283     /* Server had an internal issue; we should retry,
    284        but this API leaves this to the application */
    285     break;
    286   default:
    287     TALER_MERCHANT_parse_error_details_ (json,
    288                                          response_code,
    289                                          &ppr.hr);
    290     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    291                 "Unexpected response code %u/%d\n",
    292                 (unsigned int) response_code,
    293                 (int) ppr.hr.ec);
    294     GNUNET_break_op (0);
    295     break;
    296   }
    297   pph->cb (pph->cb_cls,
    298            &ppr);
    299   TALER_MERCHANT_patch_private_product_cancel (pph);
    300 }
    301 
    302 
    303 struct TALER_MERCHANT_PatchPrivateProductHandle *
    304 TALER_MERCHANT_patch_private_product_create (
    305   struct GNUNET_CURL_Context *ctx,
    306   const char *url,
    307   const char *product_id,
    308   const char *description,
    309   const char *unit,
    310   unsigned int num_prices,
    311   const struct TALER_Amount prices[static num_prices])
    312 {
    313   struct TALER_MERCHANT_PatchPrivateProductHandle *pph;
    314 
    315   pph = GNUNET_new (struct TALER_MERCHANT_PatchPrivateProductHandle);
    316   pph->ctx = ctx;
    317   pph->base_url = GNUNET_strdup (url);
    318   pph->product_id = GNUNET_strdup (product_id);
    319   pph->description = GNUNET_strdup (description);
    320   pph->unit = GNUNET_strdup (unit);
    321   pph->unit_price_len = num_prices;
    322   pph->unit_prices = GNUNET_new_array (num_prices,
    323                                        struct TALER_Amount);
    324   memcpy (pph->unit_prices,
    325           prices,
    326           num_prices * sizeof (struct TALER_Amount));
    327   return pph;
    328 }
    329 
    330 
    331 enum GNUNET_GenericReturnValue
    332 TALER_MERCHANT_patch_private_product_set_options_ (
    333   struct TALER_MERCHANT_PatchPrivateProductHandle *pph,
    334   unsigned int num_options,
    335   const struct TALER_MERCHANT_PatchPrivateProductOptionValue *options)
    336 {
    337   for (unsigned int i = 0; i < num_options; i++)
    338   {
    339     switch (options[i].option)
    340     {
    341     case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_END:
    342       return GNUNET_OK;
    343     case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_TOTAL_STOCK_FRAC:
    344       pph->total_stock_frac = options[i].details.total_stock.frac;
    345       continue;
    346     case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_TOTAL_STOCK_VAL:
    347       pph->total_stock = options[i].details.total_stock.val;
    348       continue;
    349     case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_TOTAL_STOCK:
    350       pph->total_stock = options[i].details.total_stock.val;
    351       pph->total_stock_frac = options[i].details.total_stock.frac;
    352       continue;
    353     case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_TOTAL_LOST:
    354       pph->total_lost = options[i].details.total_lost;
    355       continue;
    356     case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_UNIT_ALLOW_FRACTION:
    357       pph->unit_allow_fraction = options[i].details.unit_allow_fraction;
    358       pph->have_unit_allow_fraction = true;
    359       continue;
    360     case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_UNIT_PRECISION_LEVEL:
    361       pph->unit_precision_level = options[i].details.unit_precision_level;
    362       pph->have_unit_precision_level = true;
    363       continue;
    364     case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_DESCRIPTION_I18N:
    365       json_decref (pph->description_i18n);
    366       pph->description_i18n = json_incref (
    367         (json_t *) options[i].details.description_i18n);
    368       continue;
    369     case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_TAXES:
    370       json_decref (pph->taxes);
    371       pph->taxes = json_incref ((json_t *) options[i].details.taxes);
    372       continue;
    373     case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_ADDRESS:
    374       json_decref (pph->address);
    375       pph->address = json_incref ((json_t *) options[i].details.address);
    376       continue;
    377     case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_NEXT_RESTOCK:
    378       pph->next_restock = options[i].details.next_restock;
    379       pph->have_next_restock = true;
    380       continue;
    381     case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_MINIMUM_AGE:
    382       pph->minimum_age = options[i].details.minimum_age;
    383       pph->have_minimum_age = true;
    384       continue;
    385     case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_CATEGORIES:
    386       pph->num_cats = options[i].details.categories.num;
    387       GNUNET_free (pph->cats);
    388       pph->cats = GNUNET_new_array (pph->num_cats,
    389                                     uint64_t);
    390       memcpy (pph->cats,
    391               options[i].details.categories.cats,
    392               pph->num_cats * sizeof (uint64_t));
    393       continue;
    394     case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_PRODUCT_NAME:
    395       GNUNET_free (pph->product_name);
    396       pph->product_name = GNUNET_strdup (options[i].details.product_name);
    397       continue;
    398     case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_IMAGE:
    399       GNUNET_free (pph->image);
    400       pph->image = GNUNET_strdup (options[i].details.image);
    401       continue;
    402     case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_PRODUCT_GROUP_ID:
    403       pph->product_group_id = options[i].details.product_group_id;
    404       pph->have_product_group_id = true;
    405       continue;
    406     case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_MONEY_POT_ID:
    407       pph->money_pot_id = options[i].details.money_pot_id;
    408       pph->have_money_pot_id = true;
    409       continue;
    410     case TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_OPTION_PRICE_IS_NET:
    411       pph->price_is_net = options[i].details.price_is_net;
    412       pph->have_price_is_net = true;
    413       continue;
    414     }
    415     GNUNET_break (0);
    416     return GNUNET_SYSERR;
    417   }
    418   return GNUNET_OK;
    419 }
    420 
    421 
    422 enum TALER_ErrorCode
    423 TALER_MERCHANT_patch_private_product_start (
    424   struct TALER_MERCHANT_PatchPrivateProductHandle *pph,
    425   TALER_MERCHANT_PatchPrivateProductCallback cb,
    426   TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_RESULT_CLOSURE *cb_cls)
    427 {
    428   json_t *req_obj;
    429   CURL *eh;
    430   char unit_total_stock_buf[64];
    431 
    432   pph->cb = cb;
    433   pph->cb_cls = cb_cls;
    434   {
    435     char *path;
    436 
    437     GNUNET_asprintf (&path,
    438                      "private/products/%s",
    439                      pph->product_id);
    440     pph->url = TALER_url_join (pph->base_url,
    441                                path,
    442                                NULL);
    443     GNUNET_free (path);
    444   }
    445   if (NULL == pph->url)
    446     return TALER_EC_GENERIC_CONFIGURATION_INVALID;
    447   TALER_MERCHANT_format_stock_string (pph->total_stock,
    448                                       pph->total_stock_frac,
    449                                       unit_total_stock_buf,
    450                                       sizeof (unit_total_stock_buf));
    451 
    452   req_obj = GNUNET_JSON_PACK (
    453     GNUNET_JSON_pack_string ("product_name",
    454                              pph->description),
    455     GNUNET_JSON_pack_string ("description",
    456                              pph->description),
    457     GNUNET_JSON_pack_object_incref ("description_i18n",
    458                                     pph->description_i18n),
    459     GNUNET_JSON_pack_string ("unit",
    460                              pph->unit),
    461     TALER_JSON_pack_amount_array ("unit_price",
    462                                   pph->unit_price_len,
    463                                   pph->unit_prices),
    464     GNUNET_JSON_pack_string ("image",
    465                              pph->image),
    466     GNUNET_JSON_pack_array_incref ("taxes",
    467                                    pph->taxes),
    468     GNUNET_JSON_pack_string ("unit_total_stock",
    469                              unit_total_stock_buf),
    470     GNUNET_JSON_pack_uint64 ("total_lost",
    471                              pph->total_lost),
    472     GNUNET_JSON_pack_object_incref ("address",
    473                                     pph->address),
    474     GNUNET_JSON_pack_timestamp ("next_restock",
    475                                 pph->next_restock));
    476   if (pph->have_unit_allow_fraction &&
    477       pph->unit_allow_fraction)
    478   {
    479     GNUNET_assert (0 ==
    480                    json_object_set_new (req_obj,
    481                                         "unit_allow_fraction",
    482                                         json_boolean (
    483                                           pph->unit_allow_fraction)));
    484     if (pph->have_unit_precision_level)
    485     {
    486       GNUNET_assert (0 ==
    487                      json_object_set_new (req_obj,
    488                                           "unit_precision_level",
    489                                           json_integer (
    490                                             pph->unit_precision_level)));
    491     }
    492   }
    493   eh = TALER_MERCHANT_curl_easy_get_ (pph->url);
    494   if ( (NULL == eh) ||
    495        (GNUNET_OK !=
    496         TALER_curl_easy_post (&pph->post_ctx,
    497                               eh,
    498                               req_obj)) )
    499   {
    500     GNUNET_break (0);
    501     json_decref (req_obj);
    502     if (NULL != eh)
    503       curl_easy_cleanup (eh);
    504     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    505   }
    506   json_decref (req_obj);
    507   GNUNET_assert (CURLE_OK ==
    508                  curl_easy_setopt (eh,
    509                                    CURLOPT_CUSTOMREQUEST,
    510                                    MHD_HTTP_METHOD_PATCH));
    511   pph->job = GNUNET_CURL_job_add2 (pph->ctx,
    512                                    eh,
    513                                    pph->post_ctx.headers,
    514                                    &handle_patch_product_finished,
    515                                    pph);
    516   if (NULL == pph->job)
    517     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
    518   return TALER_EC_NONE;
    519 }
    520 
    521 
    522 void
    523 TALER_MERCHANT_patch_private_product_cancel (
    524   struct TALER_MERCHANT_PatchPrivateProductHandle *pph)
    525 {
    526   if (NULL != pph->job)
    527   {
    528     GNUNET_CURL_job_cancel (pph->job);
    529     pph->job = NULL;
    530   }
    531   TALER_curl_easy_post_finished (&pph->post_ctx);
    532   json_decref (pph->description_i18n);
    533   json_decref (pph->taxes);
    534   json_decref (pph->address);
    535   GNUNET_free (pph->cats);
    536   GNUNET_free (pph->unit_prices);
    537   GNUNET_free (pph->url);
    538   GNUNET_free (pph->base_url);
    539   GNUNET_free (pph->product_id);
    540   GNUNET_free (pph->description);
    541   GNUNET_free (pph->unit);
    542   GNUNET_free (pph->image);
    543   GNUNET_free (pph->product_name);
    544   GNUNET_free (pph);
    545 }
    546 
    547 
    548 /* end of merchant_api_patch-private-products-PRODUCT_ID.c */