merchant

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

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


      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     if (0 > qs)
    397     {
    398       GNUNET_break (0);
    399       inventory_payload_cleanup (&ipc);
    400       GNUNET_free (product_ids);
    401       GNUNET_free (category_ids);
    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   GNUNET_free (product_ids);
    410   GNUNET_free (category_ids);
    411 
    412   ipc.category_payload = json_array ();
    413   GNUNET_assert (NULL != ipc.category_payload);
    414   if (0 < ipc.category_set.len)
    415   {
    416     enum GNUNET_DB_QueryStatus qs;
    417 
    418     qs = TALER_MERCHANTDB_lookup_categories_by_ids (
    419       TMH_db,
    420       mi->settings.id,
    421       ipc.category_set.ids,
    422       ipc.category_set.len,
    423       &add_inventory_category,
    424       &ipc);
    425     if (0 > qs)
    426     {
    427       GNUNET_break (0);
    428       inventory_payload_cleanup (&ipc);
    429       return TALER_MHD_reply_with_error (
    430         connection,
    431         MHD_HTTP_INTERNAL_SERVER_ERROR,
    432         TALER_EC_GENERIC_DB_FETCH_FAILED,
    433         "lookup_categories_by_ids");
    434     }
    435   }
    436 
    437   ipc.unit_payload = json_array ();
    438   GNUNET_assert (NULL != ipc.unit_payload);
    439   if (0 < ipc.unit_set.len)
    440   {
    441     enum GNUNET_DB_QueryStatus qs;
    442 
    443     qs = TALER_MERCHANTDB_lookup_custom_units_by_names (
    444       TMH_db,
    445       mi->settings.id,
    446       (const char *const *) ipc.unit_set.units,
    447       ipc.unit_set.len,
    448       &add_inventory_unit,
    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         "lookup_custom_units_by_names");
    459     }
    460   }
    461 
    462   inventory_payload = GNUNET_JSON_PACK (
    463     GNUNET_JSON_pack_array_steal ("products",
    464                                   ipc.products),
    465     GNUNET_JSON_pack_array_steal ("categories",
    466                                   ipc.category_payload),
    467     GNUNET_JSON_pack_array_steal ("units",
    468                                   ipc.unit_payload));
    469   ipc.products = NULL;
    470   ipc.category_payload = NULL;
    471   ipc.unit_payload = NULL;
    472 
    473   template_contract = json_deep_copy (tp->template_contract);
    474   GNUNET_assert (NULL != template_contract);
    475   /* remove internal fields */
    476   (void) json_object_del (template_contract,
    477                           "selected_categories");
    478   (void) json_object_del (template_contract,
    479                           "selected_products");
    480   (void) json_object_del (template_contract,
    481                           "selected_all");
    482   /* add inventory data */
    483   GNUNET_assert (0 ==
    484                  json_object_set_new (template_contract,
    485                                       "inventory_payload",
    486                                       inventory_payload));
    487   {
    488     enum MHD_Result ret;
    489 
    490     ret = TALER_MHD_REPLY_JSON_PACK (
    491       connection,
    492       MHD_HTTP_OK,
    493       GNUNET_JSON_pack_allow_null (
    494         GNUNET_JSON_pack_object_incref ("editable_defaults",
    495                                         tp->editable_defaults)),
    496       GNUNET_JSON_pack_allow_null (
    497         GNUNET_JSON_pack_string ("required_currency",
    498                                  NULL /* FIXME: add support */)),
    499       GNUNET_JSON_pack_object_steal ("template_contract",
    500                                      template_contract));
    501     inventory_payload_cleanup (&ipc);
    502     return ret;
    503   }
    504 }
    505 
    506 
    507 enum MHD_Result
    508 TMH_get_templates_ID (
    509   const struct TMH_RequestHandler *rh,
    510   struct MHD_Connection *connection,
    511   struct TMH_HandlerContext *hc)
    512 {
    513   struct TMH_MerchantInstance *mi = hc->instance;
    514   struct TALER_MERCHANTDB_TemplateDetails tp = { 0 };
    515   enum GNUNET_DB_QueryStatus qs;
    516 
    517   GNUNET_assert (NULL != mi);
    518   qs = TALER_MERCHANTDB_lookup_template (TMH_db,
    519                                          mi->settings.id,
    520                                          hc->infix,
    521                                          &tp);
    522   if (0 > qs)
    523   {
    524     GNUNET_break (0);
    525     return TALER_MHD_reply_with_error (
    526       connection,
    527       MHD_HTTP_INTERNAL_SERVER_ERROR,
    528       TALER_EC_GENERIC_DB_FETCH_FAILED,
    529       "lookup_template");
    530   }
    531   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    532   {
    533     return TALER_MHD_reply_with_error (
    534       connection,
    535       MHD_HTTP_NOT_FOUND,
    536       TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN,
    537       hc->infix);
    538   }
    539   {
    540     enum MHD_Result ret;
    541 
    542     switch (TALER_MERCHANT_template_type_from_contract (tp.template_contract))
    543     {
    544     case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
    545       ret = handle_get_templates_inventory (connection,
    546                                             mi,
    547                                             &tp);
    548       TALER_MERCHANTDB_template_details_free (&tp);
    549       return ret;
    550     case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
    551     case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
    552       ret = TALER_MHD_REPLY_JSON_PACK (
    553         connection,
    554         MHD_HTTP_OK,
    555         GNUNET_JSON_pack_allow_null (
    556           GNUNET_JSON_pack_object_incref ("editable_defaults",
    557                                           tp.editable_defaults)),
    558         GNUNET_JSON_pack_allow_null (
    559           GNUNET_JSON_pack_string ("required_currency",
    560                                    NULL /* FIXME: add support */)),
    561         GNUNET_JSON_pack_object_incref ("template_contract",
    562                                         tp.template_contract));
    563       TALER_MERCHANTDB_template_details_free (&tp);
    564       return ret;
    565     case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
    566       break;
    567     }
    568     GNUNET_break_op (0);
    569     ret = TALER_MHD_reply_with_error (
    570       connection,
    571       MHD_HTTP_INTERNAL_SERVER_ERROR,
    572       TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    573       "template_type");
    574     TALER_MERCHANTDB_template_details_free (&tp);
    575     return ret;
    576   }
    577 }
    578 
    579 
    580 /* end of taler-merchant-httpd_get-templates-TEMPLATE_ID.c */