merchant

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

taler-merchant-httpd_get-templates-TEMPLATE_ID.c (18671B)


      1 /*
      2   This file is part of TALER
      3   (C) 2022-2025 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file src/backend/taler-merchant-httpd_get-templates-TEMPLATE_ID.c
     18  * @brief implement GET /templates/$ID
     19  * @author Priscilla HUANG
     20  */
     21 #include "platform.h"
     22 #include "taler-merchant-httpd_get-templates-TEMPLATE_ID.h"
     23 #include "taler-merchant-httpd_helper.h"
     24 #include <taler/taler_json_lib.h>
     25 #include "merchant-database/iterate_categories_by_ids.h"
     26 #include "merchant-database/iterate_custom_units_by_names.h"
     27 #include "merchant-database/iterate_inventory_products.h"
     28 #include "merchant-database/iterate_inventory_products_filtered.h"
     29 #include "merchant-database/get_template.h"
     30 
     31 
     32 /**
     33  * Determine the currency the client must pay in, if the template
     34  * requires a particular one without fixing the amount.
     35  *
     36  * @param template_contract contract of the template
     37  * @return currency to require, NULL if the template does not say
     38  */
     39 static const char *
     40 get_required_currency (const json_t *template_contract)
     41 {
     42   const json_t *currency;
     43 
     44   currency = json_object_get (template_contract,
     45                               "currency");
     46   if (NULL == currency)
     47     return NULL;
     48   if (! json_is_string (currency))
     49   {
     50     GNUNET_break (0);
     51     return NULL;
     52   }
     53   return json_string_value (currency);
     54 }
     55 
     56 
     57 /**
     58  * Context for building inventory template payloads.
     59  */
     60 struct InventoryPayloadContext
     61 {
     62   /**
     63    * Selected category IDs (as JSON array).
     64    */
     65   const json_t *selected_categories;
     66 
     67   /**
     68    * Selected product IDs (as JSON array) from contract_template.
     69    */
     70   const json_t *selected_products;
     71 
     72   /**
     73    * Whether all products are selected.
     74    */
     75   bool selected_all;
     76 
     77   /**
     78    * JSON array of products to build.
     79    */
     80   json_t *products;
     81 
     82   /**
     83    * JSON array of categories to build.
     84    */
     85   json_t *category_payload;
     86 
     87   /**
     88    * JSON array of units to build.
     89    */
     90   json_t *unit_payload;
     91 
     92   /**
     93    * Set of categories referenced by the products.
     94    */
     95   struct TMH_CategorySet category_set;
     96 
     97   /**
     98    * Set of unit identifiers referenced by the products.
     99    */
    100   struct TMH_UnitSet unit_set;
    101 };
    102 
    103 
    104 /**
    105  * Release resources associated with an inventory payload context.
    106  *
    107  * @param ipc inventory payload context
    108  */
    109 static void
    110 inventory_payload_cleanup (struct InventoryPayloadContext *ipc)
    111 {
    112   if (NULL != ipc->products)
    113     json_decref (ipc->products);
    114   if (NULL != ipc->category_payload)
    115     json_decref (ipc->category_payload);
    116   if (NULL != ipc->unit_payload)
    117     json_decref (ipc->unit_payload);
    118   GNUNET_free (ipc->category_set.ids);
    119   TMH_unit_set_clear (&ipc->unit_set);
    120   ipc->products = NULL;
    121   ipc->category_payload = NULL;
    122   ipc->unit_payload = NULL;
    123   ipc->category_set.len = 0;
    124 }
    125 
    126 
    127 /**
    128  * Add inventory product to JSON payload.
    129  *
    130  * @param cls inventory payload context
    131  * @param product_id product identifier
    132  * @param pd product details
    133  * @param num_categories number of categories
    134  * @param categories category IDs
    135  */
    136 static void
    137 add_inventory_product (
    138   void *cls,
    139   const char *product_id,
    140   const struct TALER_MERCHANTDB_InventoryProductDetails *pd,
    141   size_t num_categories,
    142   const uint64_t *categories)
    143 {
    144   struct InventoryPayloadContext *ipc = cls;
    145   json_t *jcategories;
    146   json_t *product;
    147   char remaining_stock_buf[64];
    148 
    149   jcategories = json_array ();
    150   GNUNET_assert (NULL != jcategories);
    151   for (size_t i = 0; i < num_categories; i++)
    152   {
    153     /* Adding per product category */
    154     TMH_category_set_add (&ipc->category_set,
    155                           categories[i]);
    156     GNUNET_assert (0 ==
    157                    json_array_append_new (jcategories,
    158                                           json_integer (categories[i])));
    159   }
    160   GNUNET_assert (0 < pd->price_array_length);
    161   TALER_MERCHANT_vk_format_fractional_string (
    162     TALER_MERCHANT_VK_STOCK,
    163     pd->remaining_stock,
    164     pd->remaining_stock_frac,
    165     sizeof (remaining_stock_buf),
    166     remaining_stock_buf);
    167   product = GNUNET_JSON_PACK (
    168     GNUNET_JSON_pack_string ("product_id",
    169                              product_id),
    170     GNUNET_JSON_pack_string ("product_name",
    171                              pd->product_name),
    172     GNUNET_JSON_pack_string ("description",
    173                              pd->description),
    174     GNUNET_JSON_pack_allow_null (
    175       GNUNET_JSON_pack_object_incref ("description_i18n",
    176                                       pd->description_i18n)),
    177     GNUNET_JSON_pack_string ("unit",
    178                              pd->unit),
    179     TALER_JSON_pack_amount_array ("unit_prices",
    180                                   pd->price_array_length,
    181                                   pd->price_array),
    182     GNUNET_JSON_pack_bool ("unit_allow_fraction",
    183                            pd->allow_fractional_quantity),
    184     GNUNET_JSON_pack_uint64 ("unit_precision_level",
    185                              pd->fractional_precision_level),
    186     GNUNET_JSON_pack_string ("remaining_stock",
    187                              remaining_stock_buf),
    188     GNUNET_JSON_pack_array_steal ("categories",
    189                                   jcategories),
    190     GNUNET_JSON_pack_allow_null (
    191       GNUNET_JSON_pack_array_incref ("taxes",
    192                                      pd->taxes)),
    193     GNUNET_JSON_pack_allow_null (
    194       GNUNET_JSON_pack_string ("image_hash",
    195                                pd->image_hash)));
    196 
    197   /* Adding per product unit */
    198   TMH_unit_set_add (&ipc->unit_set,
    199                     pd->unit);
    200 
    201   GNUNET_assert (0 ==
    202                  json_array_append_new (ipc->products,
    203                                         product));
    204 }
    205 
    206 
    207 /**
    208  * Add an inventory category to the payload if referenced.
    209  *
    210  * @param cls category payload context
    211  * @param category_id category identifier
    212  * @param category_name category name
    213  * @param category_name_i18n translated names
    214  * @param product_count number of products (unused)
    215  */
    216 static void
    217 add_inventory_category (void *cls,
    218                         uint64_t category_id,
    219                         const char *category_name,
    220                         const json_t *category_name_i18n,
    221                         uint64_t product_count)
    222 {
    223   struct InventoryPayloadContext *ipc = cls;
    224   json_t *category;
    225 
    226   (void) product_count;
    227   category = GNUNET_JSON_PACK (
    228     GNUNET_JSON_pack_uint64 ("category_id",
    229                              category_id),
    230     GNUNET_JSON_pack_string ("category_name",
    231                              category_name),
    232     GNUNET_JSON_pack_allow_null (
    233       GNUNET_JSON_pack_object_incref ("category_name_i18n",
    234                                       (json_t *) category_name_i18n)));
    235   GNUNET_assert (0 ==
    236                  json_array_append_new (ipc->category_payload,
    237                                         category));
    238 }
    239 
    240 
    241 /**
    242  * Add an inventory unit to the payload if referenced and non-builtin.
    243  *
    244  * @param cls unit payload context
    245  * @param unit_serial unit identifier
    246  * @param ud unit details
    247  */
    248 static void
    249 add_inventory_unit (void *cls,
    250                     uint64_t unit_serial,
    251                     const struct TALER_MERCHANTDB_UnitDetails *ud)
    252 {
    253   struct InventoryPayloadContext *ipc = cls;
    254   json_t *unit;
    255 
    256   (void) unit_serial;
    257 
    258   unit = GNUNET_JSON_PACK (
    259     GNUNET_JSON_pack_string ("unit",
    260                              ud->unit),
    261     GNUNET_JSON_pack_string ("unit_name_long",
    262                              ud->unit_name_long),
    263     GNUNET_JSON_pack_allow_null (
    264       GNUNET_JSON_pack_object_incref ("unit_name_long_i18n",
    265                                       ud->unit_name_long_i18n)),
    266     GNUNET_JSON_pack_string ("unit_name_short",
    267                              ud->unit_name_short),
    268     GNUNET_JSON_pack_allow_null (
    269       GNUNET_JSON_pack_object_incref ("unit_name_short_i18n",
    270                                       ud->unit_name_short_i18n)),
    271     GNUNET_JSON_pack_bool ("unit_allow_fraction",
    272                            ud->unit_allow_fraction),
    273     GNUNET_JSON_pack_uint64 ("unit_precision_level",
    274                              ud->unit_precision_level));
    275   GNUNET_assert (0 ==
    276                  json_array_append_new (ipc->unit_payload,
    277                                         unit));
    278 }
    279 
    280 
    281 /**
    282  * Build wallet-facing payload for inventory templates.
    283  *
    284  * @param connection HTTP connection
    285  * @param mi merchant instance
    286  * @param tp template details
    287  * @return MHD result
    288  */
    289 static enum MHD_Result
    290 handle_get_templates_inventory (
    291   struct MHD_Connection *connection,
    292   const struct TMH_MerchantInstance *mi,
    293   const struct TALER_MERCHANTDB_TemplateDetails *tp)
    294 {
    295   struct InventoryPayloadContext ipc;
    296   const char **product_ids = NULL;
    297   uint64_t *category_ids = NULL;
    298   size_t num_product_ids = 0;
    299   size_t num_category_ids = 0;
    300   json_t *inventory_payload;
    301   json_t *template_contract;
    302 
    303   memset (&ipc,
    304           0,
    305           sizeof (ipc));
    306   ipc.products = json_array ();
    307   {
    308     struct GNUNET_JSON_Specification spec[] = {
    309       GNUNET_JSON_spec_mark_optional (
    310         GNUNET_JSON_spec_array_const ("selected_categories",
    311                                       &ipc.selected_categories),
    312         NULL),
    313       GNUNET_JSON_spec_mark_optional (
    314         GNUNET_JSON_spec_array_const ("selected_products",
    315                                       &ipc.selected_products),
    316         NULL),
    317       GNUNET_JSON_spec_mark_optional (
    318         GNUNET_JSON_spec_bool ("selected_all",
    319                                &ipc.selected_all),
    320         NULL),
    321       GNUNET_JSON_spec_end ()
    322     };
    323     const char *err_name;
    324     unsigned int err_line;
    325 
    326     if (GNUNET_OK !=
    327         GNUNET_JSON_parse (tp->template_contract,
    328                            spec,
    329                            &err_name,
    330                            &err_line))
    331     {
    332       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    333                   "Invalid inventory template_contract for field %s\n",
    334                   err_name);
    335       inventory_payload_cleanup (&ipc);
    336       return TALER_MHD_reply_with_error (
    337         connection,
    338         MHD_HTTP_INTERNAL_SERVER_ERROR,
    339         TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    340         err_name);
    341     }
    342   }
    343 
    344   if (! ipc.selected_all)
    345   {
    346     if (NULL != ipc.selected_products)
    347     {
    348       size_t max_ids;
    349 
    350       max_ids = json_array_size (ipc.selected_products);
    351       if (0 < max_ids)
    352         product_ids = GNUNET_new_array (max_ids,
    353                                         const char *);
    354       for (size_t i = 0; i < max_ids; i++)
    355       {
    356         const json_t *entry = json_array_get (ipc.selected_products,
    357                                               i);
    358 
    359         if (json_is_string (entry))
    360           product_ids[num_product_ids++] = json_string_value (entry);
    361         else
    362           GNUNET_break (0);
    363       }
    364     }
    365     if (NULL != ipc.selected_categories)
    366     {
    367       size_t max_categories;
    368 
    369       max_categories = json_array_size (ipc.selected_categories);
    370       if (0 < max_categories)
    371         category_ids = GNUNET_new_array (max_categories,
    372                                          uint64_t);
    373       for (size_t i = 0; i < max_categories; i++)
    374       {
    375         const json_t *entry = json_array_get (ipc.selected_categories,
    376                                               i);
    377 
    378         if (json_is_integer (entry) &&
    379             (0 < json_integer_value (entry)))
    380           category_ids[num_category_ids++]
    381             = (uint64_t) json_integer_value (entry);
    382         else
    383           GNUNET_break (0);
    384       }
    385     }
    386   }
    387 
    388   if (ipc.selected_all)
    389   {
    390     enum GNUNET_DB_QueryStatus qs;
    391 
    392     qs = TALER_MERCHANTDB_iterate_inventory_products (TMH_db,
    393                                                       mi->settings.id,
    394                                                       &add_inventory_product,
    395                                                       &ipc);
    396     if (0 > qs)
    397     {
    398       GNUNET_break (0);
    399       inventory_payload_cleanup (&ipc);
    400       return TALER_MHD_reply_with_error (
    401         connection,
    402         MHD_HTTP_INTERNAL_SERVER_ERROR,
    403         TALER_EC_GENERIC_DB_FETCH_FAILED,
    404         "iterate_inventory_products");
    405     }
    406   }
    407   else if ( (0 < num_product_ids) ||
    408             (0 < num_category_ids) )
    409   {
    410     enum GNUNET_DB_QueryStatus qs;
    411 
    412     qs = TALER_MERCHANTDB_iterate_inventory_products_filtered (
    413       TMH_db,
    414       mi->settings.id,
    415       product_ids,
    416       num_product_ids,
    417       category_ids,
    418       num_category_ids,
    419       &add_inventory_product,
    420       &ipc);
    421     if (0 > qs)
    422     {
    423       GNUNET_break (0);
    424       inventory_payload_cleanup (&ipc);
    425       GNUNET_free (product_ids);
    426       GNUNET_free (category_ids);
    427       return TALER_MHD_reply_with_error (
    428         connection,
    429         MHD_HTTP_INTERNAL_SERVER_ERROR,
    430         TALER_EC_GENERIC_DB_FETCH_FAILED,
    431         "iterate_inventory_products_filtered");
    432     }
    433   }
    434   GNUNET_free (product_ids);
    435   GNUNET_free (category_ids);
    436 
    437   ipc.category_payload = json_array ();
    438   GNUNET_assert (NULL != ipc.category_payload);
    439   if (0 < ipc.category_set.len)
    440   {
    441     enum GNUNET_DB_QueryStatus qs;
    442 
    443     qs = TALER_MERCHANTDB_iterate_categories_by_ids (
    444       TMH_db,
    445       mi->settings.id,
    446       ipc.category_set.ids,
    447       ipc.category_set.len,
    448       &add_inventory_category,
    449       &ipc);
    450     if (0 > qs)
    451     {
    452       GNUNET_break (0);
    453       inventory_payload_cleanup (&ipc);
    454       return TALER_MHD_reply_with_error (
    455         connection,
    456         MHD_HTTP_INTERNAL_SERVER_ERROR,
    457         TALER_EC_GENERIC_DB_FETCH_FAILED,
    458         "iterate_categories_by_ids");
    459     }
    460   }
    461 
    462   ipc.unit_payload = json_array ();
    463   GNUNET_assert (NULL != ipc.unit_payload);
    464   if (0 < ipc.unit_set.len)
    465   {
    466     enum GNUNET_DB_QueryStatus qs;
    467 
    468     qs = TALER_MERCHANTDB_iterate_custom_units_by_names (
    469       TMH_db,
    470       mi->settings.id,
    471       (const char *const *) ipc.unit_set.units,
    472       ipc.unit_set.len,
    473       &add_inventory_unit,
    474       &ipc);
    475     if (0 > qs)
    476     {
    477       GNUNET_break (0);
    478       inventory_payload_cleanup (&ipc);
    479       return TALER_MHD_reply_with_error (
    480         connection,
    481         MHD_HTTP_INTERNAL_SERVER_ERROR,
    482         TALER_EC_GENERIC_DB_FETCH_FAILED,
    483         "iterate_custom_units_by_names");
    484     }
    485   }
    486 
    487   inventory_payload = GNUNET_JSON_PACK (
    488     GNUNET_JSON_pack_array_steal ("products",
    489                                   ipc.products),
    490     GNUNET_JSON_pack_array_steal ("categories",
    491                                   ipc.category_payload),
    492     GNUNET_JSON_pack_array_steal ("units",
    493                                   ipc.unit_payload));
    494   ipc.products = NULL;
    495   ipc.category_payload = NULL;
    496   ipc.unit_payload = NULL;
    497 
    498   template_contract = json_deep_copy (tp->template_contract);
    499   GNUNET_assert (NULL != template_contract);
    500   /* remove internal fields */
    501   (void) json_object_del (template_contract,
    502                           "selected_categories");
    503   (void) json_object_del (template_contract,
    504                           "selected_products");
    505   (void) json_object_del (template_contract,
    506                           "selected_all");
    507   /* add inventory data */
    508   GNUNET_assert (0 ==
    509                  json_object_set_new (template_contract,
    510                                       "inventory_payload",
    511                                       inventory_payload));
    512   {
    513     enum MHD_Result ret;
    514 
    515     ret = TALER_MHD_REPLY_JSON_PACK (
    516       connection,
    517       MHD_HTTP_OK,
    518       GNUNET_JSON_pack_allow_null (
    519         GNUNET_JSON_pack_object_incref ("editable_defaults",
    520                                         tp->editable_defaults)),
    521       GNUNET_JSON_pack_allow_null (
    522         GNUNET_JSON_pack_string ("required_currency",
    523                                  get_required_currency (
    524                                    tp->template_contract))),
    525       GNUNET_JSON_pack_object_steal ("template_contract",
    526                                      template_contract));
    527     inventory_payload_cleanup (&ipc);
    528     return ret;
    529   }
    530 }
    531 
    532 
    533 enum MHD_Result
    534 TMH_get_templates_ID (
    535   const struct TMH_RequestHandler *rh,
    536   struct MHD_Connection *connection,
    537   struct TMH_HandlerContext *hc)
    538 {
    539   struct TMH_MerchantInstance *mi = hc->instance;
    540   struct TALER_MERCHANTDB_TemplateDetails tp = { 0 };
    541   enum GNUNET_DB_QueryStatus qs;
    542 
    543   GNUNET_assert (NULL != mi);
    544   qs = TALER_MERCHANTDB_get_template (TMH_db,
    545                                       mi->settings.id,
    546                                       hc->infix,
    547                                       &tp);
    548   if (0 > qs)
    549   {
    550     GNUNET_break (0);
    551     return TALER_MHD_reply_with_error (
    552       connection,
    553       MHD_HTTP_INTERNAL_SERVER_ERROR,
    554       TALER_EC_GENERIC_DB_FETCH_FAILED,
    555       "get_template");
    556   }
    557   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    558   {
    559     return TALER_MHD_reply_with_error (
    560       connection,
    561       MHD_HTTP_NOT_FOUND,
    562       TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN,
    563       hc->infix);
    564   }
    565   {
    566     enum MHD_Result ret;
    567 
    568     switch (TALER_MERCHANT_template_type_from_contract (tp.template_contract))
    569     {
    570     case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
    571       ret = handle_get_templates_inventory (connection,
    572                                             mi,
    573                                             &tp);
    574       TALER_MERCHANTDB_template_details_free (&tp);
    575       return ret;
    576     case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
    577     case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
    578       ret = TALER_MHD_REPLY_JSON_PACK (
    579         connection,
    580         MHD_HTTP_OK,
    581         GNUNET_JSON_pack_allow_null (
    582           GNUNET_JSON_pack_object_incref ("editable_defaults",
    583                                           tp.editable_defaults)),
    584         GNUNET_JSON_pack_allow_null (
    585           GNUNET_JSON_pack_string ("required_currency",
    586                                    get_required_currency (
    587                                      tp.template_contract))),
    588         GNUNET_JSON_pack_object_incref ("template_contract",
    589                                         tp.template_contract));
    590       TALER_MERCHANTDB_template_details_free (&tp);
    591       return ret;
    592     case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
    593       break;
    594     }
    595     GNUNET_break_op (0);
    596     ret = TALER_MHD_reply_with_error (
    597       connection,
    598       MHD_HTTP_INTERNAL_SERVER_ERROR,
    599       TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    600       "template_type");
    601     TALER_MERCHANTDB_template_details_free (&tp);
    602     return ret;
    603   }
    604 }
    605 
    606 
    607 /* end of taler-merchant-httpd_get-templates-TEMPLATE_ID.c */