merchant

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

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


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