merchant

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

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


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