merchant

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

taler-merchant-httpd_private-patch-products-ID.c (14975B)


      1 /*
      2   This file is part of TALER
      3   (C) 2020--2025 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU Affero General Public License as
      7   published by the Free Software Foundation; either version 3,
      8   or (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful, but
     11   WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public
     16   License along with TALER; see the file COPYING.  If not,
     17   see <http://www.gnu.org/licenses/>
     18 */
     19 
     20 /**
     21  * @file taler-merchant-httpd_private-patch-products-ID.c
     22  * @brief implementing PATCH /products/$ID request handling
     23  * @author Christian Grothoff
     24  */
     25 #include "platform.h"
     26 #include "taler-merchant-httpd_private-patch-products-ID.h"
     27 #include "taler-merchant-httpd_helper.h"
     28 #include <taler/taler_json_lib.h>
     29 
     30 
     31 /**
     32  * PATCH configuration of an existing instance, given its configuration.
     33  *
     34  * @param rh context of the handler
     35  * @param connection the MHD connection to handle
     36  * @param[in,out] hc context with further information about the request
     37  * @return MHD result code
     38  */
     39 MHD_RESULT
     40 TMH_private_patch_products_ID (
     41   const struct TMH_RequestHandler *rh,
     42   struct MHD_Connection *connection,
     43   struct TMH_HandlerContext *hc)
     44 {
     45   struct TMH_MerchantInstance *mi = hc->instance;
     46   const char *product_id = hc->infix;
     47   struct TALER_MERCHANTDB_ProductDetails pd = {0};
     48   const json_t *categories = NULL;
     49   int64_t total_stock;
     50   const char *unit_total_stock = NULL;
     51   bool unit_total_stock_missing;
     52   bool total_stock_missing;
     53   bool price_missing;
     54   bool unit_price_missing;
     55   bool unit_allow_fraction;
     56   bool unit_allow_fraction_missing;
     57   uint32_t unit_precision_level;
     58   bool unit_precision_missing;
     59   enum GNUNET_DB_QueryStatus qs;
     60   struct GNUNET_JSON_Specification spec[] = {
     61     /* new in protocol v20, thus optional for backwards-compatibility */
     62     GNUNET_JSON_spec_mark_optional (
     63       GNUNET_JSON_spec_string ("product_name",
     64                                (const char **) &pd.product_name),
     65       NULL),
     66     GNUNET_JSON_spec_string ("description",
     67                              (const char **) &pd.description),
     68     GNUNET_JSON_spec_mark_optional (
     69       GNUNET_JSON_spec_json ("description_i18n",
     70                              &pd.description_i18n),
     71       NULL),
     72     GNUNET_JSON_spec_string ("unit",
     73                              (const char **) &pd.unit),
     74     GNUNET_JSON_spec_mark_optional (
     75       TALER_JSON_spec_amount_any ("price",
     76                                   &pd.price),
     77       &price_missing),
     78     GNUNET_JSON_spec_mark_optional (
     79       GNUNET_JSON_spec_string ("image",
     80                                (const char **) &pd.image),
     81       NULL),
     82     GNUNET_JSON_spec_mark_optional (
     83       GNUNET_JSON_spec_json ("taxes",
     84                              &pd.taxes),
     85       NULL),
     86     GNUNET_JSON_spec_mark_optional (
     87       GNUNET_JSON_spec_array_const ("categories",
     88                                     &categories),
     89       NULL),
     90     GNUNET_JSON_spec_mark_optional (
     91       GNUNET_JSON_spec_string ("unit_total_stock",
     92                                &unit_total_stock),
     93       &unit_total_stock_missing),
     94     GNUNET_JSON_spec_mark_optional (
     95       GNUNET_JSON_spec_int64 ("total_stock",
     96                               &total_stock),
     97       &total_stock_missing),
     98     GNUNET_JSON_spec_mark_optional (
     99       GNUNET_JSON_spec_bool ("unit_allow_fraction",
    100                              &unit_allow_fraction),
    101       &unit_allow_fraction_missing),
    102     GNUNET_JSON_spec_mark_optional (
    103       GNUNET_JSON_spec_uint32 ("unit_precision_level",
    104                                &unit_precision_level),
    105       &unit_precision_missing),
    106     GNUNET_JSON_spec_mark_optional (
    107       TALER_JSON_spec_amount_any_array ("unit_price",
    108                                         &pd.price_array_length,
    109                                         &pd.price_array),
    110       &unit_price_missing),
    111     GNUNET_JSON_spec_mark_optional (
    112       GNUNET_JSON_spec_uint64 ("total_lost",
    113                                &pd.total_lost),
    114       NULL),
    115     GNUNET_JSON_spec_mark_optional (
    116       GNUNET_JSON_spec_json ("address",
    117                              &pd.address),
    118       NULL),
    119     GNUNET_JSON_spec_mark_optional (
    120       GNUNET_JSON_spec_timestamp ("next_restock",
    121                                   &pd.next_restock),
    122       NULL),
    123     GNUNET_JSON_spec_mark_optional (
    124       GNUNET_JSON_spec_uint32 ("minimum_age",
    125                                &pd.minimum_age),
    126       NULL),
    127     GNUNET_JSON_spec_end ()
    128   };
    129   MHD_RESULT ret;
    130   size_t num_cats = 0;
    131   uint64_t *cats = NULL;
    132   bool no_instance;
    133   ssize_t no_cat;
    134   bool no_product;
    135   bool lost_reduced;
    136   bool sold_reduced;
    137   bool stock_reduced;
    138 
    139   pd.total_sold = 0; /* will be ignored anyway */
    140   GNUNET_assert (NULL != mi);
    141   GNUNET_assert (NULL != product_id);
    142   {
    143     enum GNUNET_GenericReturnValue res;
    144 
    145     res = TALER_MHD_parse_json_data (connection,
    146                                      hc->request_body,
    147                                      spec);
    148     if (GNUNET_OK != res)
    149       return (GNUNET_NO == res)
    150              ? MHD_YES
    151              : MHD_NO;
    152     /* For pre-v20 clients, we use the description given as the
    153        product name; remove once we make product_name mandatory. */
    154     if (NULL == pd.product_name)
    155       pd.product_name = pd.description;
    156   }
    157   if (! unit_price_missing)
    158   {
    159     if (! price_missing)
    160     {
    161       if (0 != TALER_amount_cmp (&pd.price,
    162                                  &pd.price_array[0]))
    163       {
    164         ret = TALER_MHD_reply_with_error (connection,
    165                                           MHD_HTTP_BAD_REQUEST,
    166                                           TALER_EC_GENERIC_PARAMETER_MALFORMED,
    167                                           "price,unit_price mismatch");
    168         goto cleanup;
    169       }
    170     }
    171     else
    172     {
    173       pd.price = pd.price_array[0];
    174       price_missing = false;
    175     }
    176   }
    177   else
    178   {
    179     if (price_missing)
    180     {
    181       ret = TALER_MHD_reply_with_error (connection,
    182                                         MHD_HTTP_BAD_REQUEST,
    183                                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    184                                         "price missing");
    185       goto cleanup;
    186     }
    187     pd.price_array = GNUNET_new_array (1,
    188                                        struct TALER_Amount);
    189     pd.price_array[0] = pd.price;
    190     pd.price_array_length = 1;
    191   }
    192   if (! unit_precision_missing)
    193   {
    194     if (unit_precision_level > TMH_MAX_FRACTIONAL_PRECISION_LEVEL)
    195     {
    196       ret = TALER_MHD_reply_with_error (connection,
    197                                         MHD_HTTP_BAD_REQUEST,
    198                                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    199                                         "unit_precision_level");
    200       goto cleanup;
    201     }
    202   }
    203   {
    204     bool default_allow_fractional;
    205     uint32_t default_precision_level;
    206 
    207     if (GNUNET_OK !=
    208         TMH_unit_defaults_for_instance (mi,
    209                                         pd.unit,
    210                                         &default_allow_fractional,
    211                                         &default_precision_level))
    212     {
    213       GNUNET_break (0);
    214       ret = TALER_MHD_reply_with_error (connection,
    215                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    216                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
    217                                         "unit defaults");
    218       goto cleanup;
    219     }
    220     if (unit_allow_fraction_missing)
    221       unit_allow_fraction = default_allow_fractional;
    222     if (unit_precision_missing)
    223       unit_precision_level = default_precision_level;
    224 
    225     if (! unit_allow_fraction)
    226       unit_precision_level = 0;
    227     pd.fractional_precision_level = unit_precision_level;
    228   }
    229   {
    230     const char *eparam;
    231     if (GNUNET_OK !=
    232         TMH_process_quantity_inputs (TMH_VK_STOCK,
    233                                      unit_allow_fraction,
    234                                      total_stock_missing,
    235                                      total_stock,
    236                                      unit_total_stock_missing,
    237                                      unit_total_stock,
    238                                      &pd.total_stock,
    239                                      &pd.total_stock_frac,
    240                                      &eparam))
    241     {
    242       ret = TALER_MHD_reply_with_error (
    243         connection,
    244         MHD_HTTP_BAD_REQUEST,
    245         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    246         eparam);
    247       goto cleanup;
    248     }
    249     pd.allow_fractional_quantity = unit_allow_fraction;
    250   }
    251   if (NULL == pd.address)
    252     pd.address = json_object ();
    253 
    254   if (! TMH_location_object_valid (pd.address))
    255   {
    256     GNUNET_break_op (0);
    257     ret = TALER_MHD_reply_with_error (connection,
    258                                       MHD_HTTP_BAD_REQUEST,
    259                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
    260                                       "address");
    261     goto cleanup;
    262   }
    263   num_cats = json_array_size (categories);
    264   cats = GNUNET_new_array (num_cats,
    265                            uint64_t);
    266   {
    267     size_t idx;
    268     json_t *val;
    269 
    270     json_array_foreach (categories, idx, val)
    271     {
    272       if (! json_is_integer (val))
    273       {
    274         GNUNET_break_op (0);
    275         ret = TALER_MHD_reply_with_error (connection,
    276                                           MHD_HTTP_BAD_REQUEST,
    277                                           TALER_EC_GENERIC_PARAMETER_MALFORMED,
    278                                           "categories");
    279         goto cleanup;
    280       }
    281       cats[idx] = json_integer_value (val);
    282     }
    283   }
    284 
    285   if (NULL == pd.description_i18n)
    286     pd.description_i18n = json_object ();
    287 
    288   if (! TALER_JSON_check_i18n (pd.description_i18n))
    289   {
    290     GNUNET_break_op (0);
    291     ret = TALER_MHD_reply_with_error (connection,
    292                                       MHD_HTTP_BAD_REQUEST,
    293                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
    294                                       "description_i18n");
    295     goto cleanup;
    296   }
    297 
    298   if (NULL == pd.taxes)
    299     pd.taxes = json_array ();
    300   /* check taxes is well-formed */
    301   if (! TMH_taxes_array_valid (pd.taxes))
    302   {
    303     GNUNET_break_op (0);
    304     ret = TALER_MHD_reply_with_error (connection,
    305                                       MHD_HTTP_BAD_REQUEST,
    306                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
    307                                       "taxes");
    308     goto cleanup;
    309   }
    310 
    311   if (NULL == pd.image)
    312     pd.image = (char *) "";
    313   if (! TMH_image_data_url_valid (pd.image))
    314   {
    315     GNUNET_break_op (0);
    316     ret = TALER_MHD_reply_with_error (connection,
    317                                       MHD_HTTP_BAD_REQUEST,
    318                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
    319                                       "image");
    320     goto cleanup;
    321   }
    322 
    323   if ( (pd.total_stock < pd.total_sold + pd.total_lost) ||
    324        (pd.total_sold + pd.total_lost < pd.total_sold) /* integer overflow */)
    325   {
    326     GNUNET_break_op (0);
    327     ret = TALER_MHD_reply_with_error (
    328       connection,
    329       MHD_HTTP_BAD_REQUEST,
    330       TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_LOST_EXCEEDS_STOCKS,
    331       NULL);
    332     goto cleanup;
    333   }
    334 
    335   qs = TMH_db->update_product (TMH_db->cls,
    336                                mi->settings.id,
    337                                product_id,
    338                                &pd,
    339                                num_cats,
    340                                cats,
    341                                &no_instance,
    342                                &no_cat,
    343                                &no_product,
    344                                &lost_reduced,
    345                                &sold_reduced,
    346                                &stock_reduced);
    347   switch (qs)
    348   {
    349   case GNUNET_DB_STATUS_HARD_ERROR:
    350     GNUNET_break (0);
    351     ret = TALER_MHD_reply_with_error (connection,
    352                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
    353                                       TALER_EC_GENERIC_DB_STORE_FAILED,
    354                                       NULL);
    355     goto cleanup;
    356   case GNUNET_DB_STATUS_SOFT_ERROR:
    357     GNUNET_break (0);
    358     ret = TALER_MHD_reply_with_error (connection,
    359                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
    360                                       TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    361                                       "unexpected serialization problem");
    362     goto cleanup;
    363   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    364     GNUNET_break (0);
    365     ret = TALER_MHD_reply_with_error (connection,
    366                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
    367                                       TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    368                                       "unexpected problem in stored procedure");
    369     goto cleanup;
    370   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    371     break;
    372   }
    373 
    374   if (no_instance)
    375   {
    376     ret = TALER_MHD_reply_with_error (connection,
    377                                       MHD_HTTP_NOT_FOUND,
    378                                       TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
    379                                       mi->settings.id);
    380     goto cleanup;
    381   }
    382   if (-1 != no_cat)
    383   {
    384     char cat_str[24];
    385 
    386     GNUNET_snprintf (cat_str,
    387                      sizeof (cat_str),
    388                      "%llu",
    389                      (unsigned long long) no_cat);
    390     ret = TALER_MHD_reply_with_error (connection,
    391                                       MHD_HTTP_NOT_FOUND,
    392                                       TALER_EC_MERCHANT_GENERIC_CATEGORY_UNKNOWN,
    393                                       cat_str);
    394     goto cleanup;
    395   }
    396   if (no_product)
    397   {
    398     ret = TALER_MHD_reply_with_error (connection,
    399                                       MHD_HTTP_NOT_FOUND,
    400                                       TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN,
    401                                       product_id);
    402     goto cleanup;
    403   }
    404   if (lost_reduced)
    405   {
    406     ret = TALER_MHD_reply_with_error (
    407       connection,
    408       MHD_HTTP_CONFLICT,
    409       TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_LOST_REDUCED,
    410       NULL);
    411     goto cleanup;
    412   }
    413   if (sold_reduced)
    414   {
    415     ret = TALER_MHD_reply_with_error (
    416       connection,
    417       MHD_HTTP_CONFLICT,
    418       TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_SOLD_REDUCED,
    419       NULL);
    420     goto cleanup;
    421   }
    422   if (stock_reduced)
    423   {
    424     ret = TALER_MHD_reply_with_error (
    425       connection,
    426       MHD_HTTP_CONFLICT,
    427       TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_STOCKED_REDUCED,
    428       NULL);
    429     goto cleanup;
    430   }
    431   /* success! */
    432   ret = TALER_MHD_reply_static (connection,
    433                                 MHD_HTTP_NO_CONTENT,
    434                                 NULL,
    435                                 NULL,
    436                                 0);
    437 cleanup:
    438   GNUNET_free (cats);
    439   GNUNET_free (pd.price_array);
    440   GNUNET_JSON_parse_free (spec);
    441   return ret;
    442 }
    443 
    444 
    445 /* end of taler-merchant-httpd_private-patch-products-ID.c */