merchant

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

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


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