merchant

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

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


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