merchant

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

taler-merchant-httpd_post-private-products.c (14817B)


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