merchant

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

taler-merchant-httpd_private-post-products.c (12791B)


      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-post-products.c
     22  * @brief implementing POST /products request handling
     23  * @author Christian Grothoff
     24  */
     25 #include "platform.h"
     26 #include "taler-merchant-httpd_private-post-products.h"
     27 #include "taler-merchant-httpd_helper.h"
     28 #include <taler/taler_json_lib.h>
     29 
     30 MHD_RESULT
     31 TMH_private_post_products (const struct TMH_RequestHandler *rh,
     32                            struct MHD_Connection *connection,
     33                            struct TMH_HandlerContext *hc)
     34 {
     35   struct TMH_MerchantInstance *mi = hc->instance;
     36   struct TALER_MERCHANTDB_ProductDetails pd = { 0 };
     37   const json_t *categories = NULL;
     38   const char *product_id;
     39   int64_t total_stock;
     40   const char *unit_total_stock = NULL;
     41   bool unit_total_stock_missing;
     42   bool total_stock_missing;
     43   bool unit_price_missing;
     44   bool unit_allow_fraction;
     45   bool unit_allow_fraction_missing;
     46   uint32_t unit_precision_level;
     47   bool unit_precision_missing;
     48   bool price_missing;
     49   struct GNUNET_JSON_Specification spec[] = {
     50     GNUNET_JSON_spec_string ("product_id",
     51                              &product_id),
     52     /* new in protocol v20, thus optional for backwards-compatibility */
     53     GNUNET_JSON_spec_mark_optional (
     54       GNUNET_JSON_spec_string ("product_name",
     55                                (const char **) &pd.product_name),
     56       NULL),
     57     GNUNET_JSON_spec_string ("description",
     58                              (const char **) &pd.description),
     59     GNUNET_JSON_spec_mark_optional (
     60       GNUNET_JSON_spec_json ("description_i18n",
     61                              &pd.description_i18n),
     62       NULL),
     63     GNUNET_JSON_spec_string ("unit",
     64                              (const char **) &pd.unit),
     65     GNUNET_JSON_spec_mark_optional (
     66       TALER_JSON_spec_amount_any ("price",
     67                                   &pd.price),
     68       &price_missing),
     69     GNUNET_JSON_spec_mark_optional (
     70       GNUNET_JSON_spec_string ("image",
     71                                (const char **) &pd.image),
     72       NULL),
     73     GNUNET_JSON_spec_mark_optional (
     74       GNUNET_JSON_spec_json ("taxes",
     75                              &pd.taxes),
     76       NULL),
     77     GNUNET_JSON_spec_mark_optional (
     78       GNUNET_JSON_spec_array_const ("categories",
     79                                     &categories),
     80       NULL),
     81     GNUNET_JSON_spec_mark_optional (
     82       GNUNET_JSON_spec_string ("unit_total_stock",
     83                                &unit_total_stock),
     84       &unit_total_stock_missing),
     85     GNUNET_JSON_spec_mark_optional (
     86       GNUNET_JSON_spec_int64 ("total_stock",
     87                               &total_stock),
     88       &total_stock_missing),
     89     GNUNET_JSON_spec_mark_optional (
     90       GNUNET_JSON_spec_bool ("unit_allow_fraction",
     91                              &unit_allow_fraction),
     92       &unit_allow_fraction_missing),
     93     GNUNET_JSON_spec_mark_optional (
     94       GNUNET_JSON_spec_uint32 ("unit_precision_level",
     95                                &unit_precision_level),
     96       &unit_precision_missing),
     97     GNUNET_JSON_spec_mark_optional (
     98       TALER_JSON_spec_amount_any_array ("unit_price",
     99                                         &pd.price_array_length,
    100                                         &pd.price_array),
    101       &unit_price_missing),
    102     GNUNET_JSON_spec_mark_optional (
    103       GNUNET_JSON_spec_json ("address",
    104                              &pd.address),
    105       NULL),
    106     GNUNET_JSON_spec_mark_optional (
    107       GNUNET_JSON_spec_timestamp ("next_restock",
    108                                   &pd.next_restock),
    109       NULL),
    110     GNUNET_JSON_spec_mark_optional (
    111       GNUNET_JSON_spec_uint32 ("minimum_age",
    112                                &pd.minimum_age),
    113       NULL),
    114     GNUNET_JSON_spec_end ()
    115   };
    116   size_t num_cats = 0;
    117   uint64_t *cats = NULL;
    118   bool conflict;
    119   bool no_instance;
    120   ssize_t no_cat;
    121   enum GNUNET_DB_QueryStatus qs;
    122   MHD_RESULT ret;
    123 
    124   GNUNET_assert (NULL != mi);
    125   {
    126     enum GNUNET_GenericReturnValue res;
    127 
    128     res = TALER_MHD_parse_json_data (connection,
    129                                      hc->request_body,
    130                                      spec);
    131     if (GNUNET_OK != res)
    132     {
    133       GNUNET_break_op (0);
    134       return (GNUNET_NO == res)
    135              ? MHD_YES
    136              : MHD_NO;
    137     }
    138     /* For pre-v20 clients, we use the description given as the
    139        product name; remove once we make product_name mandatory. */
    140     if (NULL == pd.product_name)
    141       pd.product_name = pd.description;
    142 
    143     if (! unit_price_missing)
    144     {
    145       if (! price_missing)
    146       {
    147         if (0 != TALER_amount_cmp (&pd.price,
    148                                    &pd.price_array[0]))
    149         {
    150           ret = TALER_MHD_reply_with_error (connection,
    151                                             MHD_HTTP_BAD_REQUEST,
    152                                             TALER_EC_GENERIC_PARAMETER_MALFORMED,
    153                                             "price,unit_price mismatch");
    154           goto cleanup;
    155         }
    156       }
    157       else
    158       {
    159         pd.price = pd.price_array[0];
    160         price_missing = false;
    161       }
    162     }
    163     else
    164     {
    165       if (price_missing)
    166       {
    167         ret = TALER_MHD_reply_with_error (connection,
    168                                           MHD_HTTP_BAD_REQUEST,
    169                                           TALER_EC_GENERIC_PARAMETER_MALFORMED,
    170                                           "price and unit_price missing");
    171         goto cleanup;
    172       }
    173       pd.price_array = GNUNET_new_array (1,
    174                                          struct TALER_Amount);
    175       pd.price_array[0] = pd.price;
    176       pd.price_array_length = 1;
    177     }
    178   }
    179   if (! unit_precision_missing)
    180   {
    181     if (unit_precision_level > TMH_MAX_FRACTIONAL_PRECISION_LEVEL)
    182     {
    183       ret = TALER_MHD_reply_with_error (connection,
    184                                         MHD_HTTP_BAD_REQUEST,
    185                                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    186                                         "unit_precision_level");
    187       goto cleanup;
    188     }
    189   }
    190   {
    191     bool default_allow_fractional;
    192     uint32_t default_precision_level;
    193 
    194     if (GNUNET_OK !=
    195         TMH_unit_defaults_for_instance (mi,
    196                                         pd.unit,
    197                                         &default_allow_fractional,
    198                                         &default_precision_level))
    199     {
    200       GNUNET_break (0);
    201       ret = TALER_MHD_reply_with_error (connection,
    202                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
    203                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
    204                                         "unit defaults");
    205       goto cleanup;
    206     }
    207     if (unit_allow_fraction_missing)
    208       unit_allow_fraction = default_allow_fractional;
    209     if (unit_precision_missing)
    210       unit_precision_level = default_precision_level;
    211   }
    212   if (! unit_allow_fraction)
    213     unit_precision_level = 0;
    214   pd.fractional_precision_level = unit_precision_level;
    215   {
    216     const char *eparam;
    217     if (GNUNET_OK !=
    218         TMH_process_quantity_inputs (TMH_VK_STOCK,
    219                                      unit_allow_fraction,
    220                                      total_stock_missing,
    221                                      total_stock,
    222                                      unit_total_stock_missing,
    223                                      unit_total_stock,
    224                                      &pd.total_stock,
    225                                      &pd.total_stock_frac,
    226                                      &eparam))
    227     {
    228       ret = TALER_MHD_reply_with_error (
    229         connection,
    230         MHD_HTTP_BAD_REQUEST,
    231         TALER_EC_GENERIC_PARAMETER_MALFORMED,
    232         eparam);
    233       goto cleanup;
    234     }
    235     pd.allow_fractional_quantity = unit_allow_fraction;
    236   }
    237   num_cats = json_array_size (categories);
    238   cats = GNUNET_new_array (num_cats,
    239                            uint64_t);
    240   {
    241     size_t idx;
    242     json_t *val;
    243 
    244     json_array_foreach (categories, idx, val)
    245     {
    246       if (! json_is_integer (val))
    247       {
    248         GNUNET_break_op (0);
    249         ret = TALER_MHD_reply_with_error (connection,
    250                                           MHD_HTTP_BAD_REQUEST,
    251                                           TALER_EC_GENERIC_PARAMETER_MALFORMED,
    252                                           "categories");
    253         goto cleanup;
    254       }
    255       cats[idx] = json_integer_value (val);
    256     }
    257   }
    258 
    259   if (NULL == pd.address)
    260     pd.address = json_object ();
    261   if (NULL == pd.description_i18n)
    262     pd.description_i18n = json_object ();
    263   if (NULL == pd.taxes)
    264     pd.taxes = json_array ();
    265 
    266   /* check taxes is well-formed */
    267   if (! TMH_taxes_array_valid (pd.taxes))
    268   {
    269     GNUNET_break_op (0);
    270     ret = TALER_MHD_reply_with_error (connection,
    271                                       MHD_HTTP_BAD_REQUEST,
    272                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
    273                                       "taxes");
    274     goto cleanup;
    275   }
    276 
    277   if (! TMH_location_object_valid (pd.address))
    278   {
    279     GNUNET_break_op (0);
    280     ret = TALER_MHD_reply_with_error (connection,
    281                                       MHD_HTTP_BAD_REQUEST,
    282                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
    283                                       "address");
    284     goto cleanup;
    285   }
    286 
    287   if (! TALER_JSON_check_i18n (pd.description_i18n))
    288   {
    289     GNUNET_break_op (0);
    290     ret = TALER_MHD_reply_with_error (connection,
    291                                       MHD_HTTP_BAD_REQUEST,
    292                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
    293                                       "description_i18n");
    294     goto cleanup;
    295   }
    296 
    297   if (NULL == pd.image)
    298     pd.image = (char *) "";
    299   if (! TMH_image_data_url_valid (pd.image))
    300   {
    301     GNUNET_break_op (0);
    302     ret = TALER_MHD_reply_with_error (connection,
    303                                       MHD_HTTP_BAD_REQUEST,
    304                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
    305                                       "image");
    306     goto cleanup;
    307   }
    308 
    309   qs = TMH_db->insert_product (TMH_db->cls,
    310                                mi->settings.id,
    311                                product_id,
    312                                &pd,
    313                                num_cats,
    314                                cats,
    315                                &no_instance,
    316                                &conflict,
    317                                &no_cat);
    318   switch (qs)
    319   {
    320   case GNUNET_DB_STATUS_HARD_ERROR:
    321   case GNUNET_DB_STATUS_SOFT_ERROR:
    322     ret = TALER_MHD_reply_with_error (
    323       connection,
    324       MHD_HTTP_INTERNAL_SERVER_ERROR,
    325       (GNUNET_DB_STATUS_SOFT_ERROR == qs)
    326       ? TALER_EC_GENERIC_DB_SOFT_FAILURE
    327       : TALER_EC_GENERIC_DB_COMMIT_FAILED,
    328       NULL);
    329     goto cleanup;
    330   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    331     ret = TALER_MHD_reply_with_error (
    332       connection,
    333       MHD_HTTP_INTERNAL_SERVER_ERROR,
    334       TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
    335       NULL);
    336     goto cleanup;
    337   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    338     break;
    339   }
    340   if (no_instance)
    341   {
    342     ret = TALER_MHD_reply_with_error (
    343       connection,
    344       MHD_HTTP_NOT_FOUND,
    345       TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
    346       mi->settings.id);
    347     goto cleanup;
    348   }
    349   if (conflict)
    350   {
    351     ret = TALER_MHD_reply_with_error (
    352       connection,
    353       MHD_HTTP_CONFLICT,
    354       TALER_EC_MERCHANT_PRIVATE_POST_PRODUCTS_CONFLICT_PRODUCT_EXISTS,
    355       product_id);
    356     goto cleanup;
    357   }
    358   if (-1 != no_cat)
    359   {
    360     char nocats[24];
    361 
    362     GNUNET_break_op (0);
    363     TMH_db->rollback (TMH_db->cls);
    364     GNUNET_snprintf (nocats,
    365                      sizeof (nocats),
    366                      "%llu",
    367                      (unsigned long long) no_cat);
    368     ret = TALER_MHD_reply_with_error (
    369       connection,
    370       MHD_HTTP_NOT_FOUND,
    371       TALER_EC_MERCHANT_GENERIC_CATEGORY_UNKNOWN,
    372       nocats);
    373     goto cleanup;
    374   }
    375   ret = TALER_MHD_reply_static (connection,
    376                                 MHD_HTTP_NO_CONTENT,
    377                                 NULL,
    378                                 NULL,
    379                                 0);
    380 cleanup:
    381   GNUNET_JSON_parse_free (spec);
    382   GNUNET_free (pd.price_array);
    383   GNUNET_free (cats);
    384   return ret;
    385 }
    386 
    387 
    388 /* end of taler-merchant-httpd_private-post-products.c */