merchant

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

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


      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_exchanges.h"
     24 #include "taler-merchant-httpd_helper.h"
     25 #include <taler/taler_json_lib.h>
     26 #include "merchant-database/iterate_categories_by_ids.h"
     27 #include "merchant-database/iterate_custom_units_by_names.h"
     28 #include "merchant-database/iterate_inventory_products.h"
     29 #include "merchant-database/iterate_inventory_products_filtered.h"
     30 #include "merchant-database/get_template.h"
     31 
     32 
     33 /**
     34  * How long do we wait at most for exchange /keys data?
     35  */
     36 #define MAX_KEYS_WAIT \
     37         GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 2500)
     38 
     39 
     40 struct GetTemplateContext;
     41 
     42 
     43 /**
     44  * Pending lookup of one exchange's keys.
     45  */
     46 struct ExchangeLookup
     47 {
     48   /**
     49    * Kept in a DLL.
     50    */
     51   struct ExchangeLookup *next;
     52 
     53   /**
     54    * Kept in a DLL.
     55    */
     56   struct ExchangeLookup *prev;
     57 
     58   /**
     59    * Parent request.
     60    */
     61   struct GetTemplateContext *gtc;
     62 
     63   /**
     64    * Exchange base URL.
     65    */
     66   char *url;
     67 
     68   /**
     69    * Pending keys operation.
     70    */
     71   struct TMH_EXCHANGES_KeysOperation *ko;
     72 };
     73 
     74 
     75 /**
     76  * Context for a wallet-facing template lookup.
     77  */
     78 struct GetTemplateContext
     79 {
     80   /**
     81    * Kept in a DLL while suspended.
     82    */
     83   struct GetTemplateContext *next;
     84 
     85   /**
     86    * Kept in a DLL while suspended.
     87    */
     88   struct GetTemplateContext *prev;
     89 
     90   /**
     91    * Request handler context.
     92    */
     93   struct TMH_HandlerContext *hc;
     94 
     95   /**
     96    * Connection handling this request.
     97    */
     98   struct MHD_Connection *connection;
     99 
    100   /**
    101    * Pending exchange lookups.
    102    */
    103   struct ExchangeLookup *lookup_head;
    104 
    105   /**
    106    * Pending exchange lookups.
    107    */
    108   struct ExchangeLookup *lookup_tail;
    109 
    110   /**
    111    * Task that limits exchange lookup latency.
    112    */
    113   struct GNUNET_SCHEDULER_Task *timeout_task;
    114 
    115   /**
    116    * Wallet-facing template contract.
    117    */
    118   json_t *template_contract;
    119 
    120   /**
    121    * Editable defaults, NULL if absent.
    122    */
    123   json_t *editable_defaults;
    124 
    125   /**
    126    * Merchant information snapshot.
    127    */
    128   json_t *merchant;
    129 
    130   /**
    131    * Compatible exchange candidates found so far.
    132    */
    133   json_t *exchange_candidates;
    134 
    135   /**
    136    * Merchant public key snapshot.
    137    */
    138   struct TALER_MerchantPublicKeyP merchant_pub;
    139 
    140   /**
    141    * #GNUNET_YES while suspended, #GNUNET_NO otherwise, and
    142    * #GNUNET_SYSERR when resumed for shutdown.
    143    */
    144   enum GNUNET_GenericReturnValue suspended;
    145 };
    146 
    147 
    148 /**
    149  * Head of suspended template requests.
    150  */
    151 static struct GetTemplateContext *gtc_head;
    152 
    153 /**
    154  * Tail of suspended template requests.
    155  */
    156 static struct GetTemplateContext *gtc_tail;
    157 
    158 
    159 /**
    160  * Determine the currency the client must pay in, if the template
    161  * requires a particular one without fixing the amount.
    162  *
    163  * @param template_contract contract of the template
    164  * @return currency to require, NULL if the template does not say
    165  */
    166 static const char *
    167 get_required_currency (const json_t *template_contract)
    168 {
    169   const json_t *currency;
    170 
    171   currency = json_object_get (template_contract,
    172                               "currency");
    173   if (NULL == currency)
    174     return NULL;
    175   if (! json_is_string (currency))
    176   {
    177     GNUNET_break (0);
    178     return NULL;
    179   }
    180   return json_string_value (currency);
    181 }
    182 
    183 
    184 /**
    185  * Test whether @a method is already in @a methods.
    186  *
    187  * @param methods JSON array of strings
    188  * @param method method to find
    189  * @return true if present
    190  */
    191 static bool
    192 wire_method_present (const json_t *methods,
    193                      const char *method)
    194 {
    195   const json_t *entry;
    196   size_t index;
    197 
    198   json_array_foreach (methods, index, entry)
    199   {
    200     if (0 == strcasecmp (json_string_value (entry),
    201                          method))
    202       return true;
    203   }
    204   return false;
    205 }
    206 
    207 
    208 /**
    209  * Test whether an exchange has an account using @a wire_method.
    210  *
    211  * @param keys exchange keys
    212  * @param wire_method wire method to find
    213  * @return true if the method is supported
    214  */
    215 static bool
    216 exchange_has_wire_method (const struct TALER_EXCHANGE_Keys *keys,
    217                           const char *wire_method)
    218 {
    219   for (unsigned int i = 0; i<keys->accounts_len; i++)
    220   {
    221     struct TALER_FullPayto full_payto = keys->accounts[i].fpayto_uri;
    222     char *method;
    223     bool matches;
    224 
    225     method = TALER_payto_get_method (full_payto.full_payto);
    226     matches = (0 == strcasecmp (method,
    227                                 wire_method));
    228     GNUNET_free (method);
    229     if (matches)
    230       return true;
    231   }
    232   return false;
    233 }
    234 
    235 
    236 /**
    237  * Add an exchange candidate based on current key data.
    238  *
    239  * @param gtc request context
    240  * @param url exchange base URL
    241  * @param keys exchange keys
    242  * @param exchange exchange handle
    243  */
    244 static void
    245 add_exchange_candidate (struct GetTemplateContext *gtc,
    246                         const char *url,
    247                         const struct TALER_EXCHANGE_Keys *keys,
    248                         const struct TMH_Exchange *exchange)
    249 {
    250   const char *currency;
    251   json_t *wire_methods;
    252 
    253   if (NULL == keys)
    254     return;
    255   currency = TMH_EXCHANGES_get_currency (exchange);
    256   if ( (NULL == currency) ||
    257        (0 != strcasecmp (currency,
    258                          keys->currency)) )
    259     return;
    260   wire_methods = json_array ();
    261   GNUNET_assert (NULL != wire_methods);
    262   for (const struct TMH_WireMethod *wm = gtc->hc->instance->wm_head;
    263        NULL != wm;
    264        wm = wm->next)
    265   {
    266     struct TALER_NormalizedPayto np;
    267     bool allowed;
    268 
    269     if (! wm->active)
    270       continue;
    271     if (! exchange_has_wire_method (keys,
    272                                     wm->wire_method))
    273       continue;
    274     np = TALER_payto_normalize (wm->payto_uri);
    275     allowed = TALER_EXCHANGE_keys_test_account_allowed (keys,
    276                                                         false,
    277                                                         np);
    278     GNUNET_free (np.normalized_payto);
    279     if ( (! allowed) ||
    280          wire_method_present (wire_methods,
    281                               wm->wire_method) )
    282       continue;
    283     GNUNET_assert (0 ==
    284                    json_array_append_new (wire_methods,
    285                                           json_string (wm->wire_method)));
    286   }
    287   if (0 == json_array_size (wire_methods))
    288   {
    289     json_decref (wire_methods);
    290     return;
    291   }
    292   GNUNET_assert (0 ==
    293                  json_array_append_new (
    294                    gtc->exchange_candidates,
    295                    GNUNET_JSON_PACK (
    296                      GNUNET_JSON_pack_string ("base_url",
    297                                               url),
    298                      GNUNET_JSON_pack_string ("currency",
    299                                               currency),
    300                      GNUNET_JSON_pack_data_auto (
    301                        "master_pub",
    302                        TMH_EXCHANGES_get_master_pub (exchange)),
    303                      GNUNET_JSON_pack_array_steal ("wire_methods",
    304                                                    wire_methods))));
    305 }
    306 
    307 
    308 /**
    309  * Resume a template request after exchange discovery.
    310  *
    311  * @param gtc request to resume
    312  */
    313 static void
    314 resume_template_request (struct GetTemplateContext *gtc)
    315 {
    316   if (GNUNET_YES != gtc->suspended)
    317     return;
    318   if (NULL != gtc->timeout_task)
    319   {
    320     GNUNET_SCHEDULER_cancel (gtc->timeout_task);
    321     gtc->timeout_task = NULL;
    322   }
    323   GNUNET_CONTAINER_DLL_remove (gtc_head,
    324                                gtc_tail,
    325                                gtc);
    326   gtc->suspended = GNUNET_NO;
    327   MHD_resume_connection (gtc->connection);
    328   TALER_MHD_daemon_trigger ();
    329 }
    330 
    331 
    332 /**
    333  * Exchange keys lookup completed.
    334  *
    335  * @param cls a `struct ExchangeLookup *`
    336  * @param keys exchange keys, NULL on failure
    337  * @param exchange exchange handle
    338  */
    339 static void
    340 exchange_keys_cb (void *cls,
    341                   struct TALER_EXCHANGE_Keys *keys,
    342                   struct TMH_Exchange *exchange)
    343 {
    344   struct ExchangeLookup *lookup = cls;
    345   struct GetTemplateContext *gtc = lookup->gtc;
    346 
    347   lookup->ko = NULL;
    348   GNUNET_CONTAINER_DLL_remove (gtc->lookup_head,
    349                                gtc->lookup_tail,
    350                                lookup);
    351   add_exchange_candidate (gtc,
    352                           lookup->url,
    353                           keys,
    354                           exchange);
    355   GNUNET_free (lookup->url);
    356   GNUNET_free (lookup);
    357   if (NULL == gtc->lookup_head)
    358     resume_template_request (gtc);
    359 }
    360 
    361 
    362 /**
    363  * Start key discovery for one trusted exchange.
    364  *
    365  * @param cls a `struct GetTemplateContext *`
    366  * @param url exchange base URL
    367  * @param exchange exchange handle, unused
    368  */
    369 static void
    370 start_exchange_lookup (void *cls,
    371                        const char *url,
    372                        const struct TMH_Exchange *exchange)
    373 {
    374   struct GetTemplateContext *gtc = cls;
    375   struct ExchangeLookup *lookup;
    376 
    377   (void) exchange;
    378   lookup = GNUNET_new (struct ExchangeLookup);
    379   lookup->gtc = gtc;
    380   lookup->url = GNUNET_strdup (url);
    381   lookup->ko = TMH_EXCHANGES_keys4exchange (url,
    382                                             false,
    383                                             &exchange_keys_cb,
    384                                             lookup);
    385   if (NULL == lookup->ko)
    386   {
    387     GNUNET_free (lookup->url);
    388     GNUNET_free (lookup);
    389     return;
    390   }
    391   GNUNET_CONTAINER_DLL_insert (gtc->lookup_head,
    392                                gtc->lookup_tail,
    393                                lookup);
    394 }
    395 
    396 
    397 /**
    398  * Stop waiting for exchange keys and return the partial candidate list.
    399  *
    400  * @param cls a `struct GetTemplateContext *`
    401  */
    402 static void
    403 exchange_lookup_timeout (void *cls)
    404 {
    405   struct GetTemplateContext *gtc = cls;
    406 
    407   gtc->timeout_task = NULL;
    408   while (NULL != gtc->lookup_head)
    409   {
    410     struct ExchangeLookup *lookup = gtc->lookup_head;
    411 
    412     GNUNET_CONTAINER_DLL_remove (gtc->lookup_head,
    413                                  gtc->lookup_tail,
    414                                  lookup);
    415     TMH_EXCHANGES_keys4exchange_cancel (lookup->ko);
    416     GNUNET_free (lookup->url);
    417     GNUNET_free (lookup);
    418   }
    419   resume_template_request (gtc);
    420 }
    421 
    422 
    423 /**
    424  * Cleanup a template request context.
    425  *
    426  * @param cls a `struct GetTemplateContext *`
    427  */
    428 static void
    429 get_template_cleanup (void *cls)
    430 {
    431   struct GetTemplateContext *gtc = cls;
    432 
    433   if (GNUNET_YES == gtc->suspended)
    434     GNUNET_CONTAINER_DLL_remove (gtc_head,
    435                                  gtc_tail,
    436                                  gtc);
    437   if (NULL != gtc->timeout_task)
    438     GNUNET_SCHEDULER_cancel (gtc->timeout_task);
    439   while (NULL != gtc->lookup_head)
    440   {
    441     struct ExchangeLookup *lookup = gtc->lookup_head;
    442 
    443     GNUNET_CONTAINER_DLL_remove (gtc->lookup_head,
    444                                  gtc->lookup_tail,
    445                                  lookup);
    446     TMH_EXCHANGES_keys4exchange_cancel (lookup->ko);
    447     GNUNET_free (lookup->url);
    448     GNUNET_free (lookup);
    449   }
    450   if (NULL != gtc->template_contract)
    451     json_decref (gtc->template_contract);
    452   if (NULL != gtc->editable_defaults)
    453     json_decref (gtc->editable_defaults);
    454   if (NULL != gtc->merchant)
    455     json_decref (gtc->merchant);
    456   if (NULL != gtc->exchange_candidates)
    457     json_decref (gtc->exchange_candidates);
    458   GNUNET_free (gtc);
    459 }
    460 
    461 
    462 void
    463 TMH_force_get_templates_ID_resume (void)
    464 {
    465   while (NULL != gtc_head)
    466   {
    467     struct GetTemplateContext *gtc = gtc_head;
    468 
    469     GNUNET_CONTAINER_DLL_remove (gtc_head,
    470                                  gtc_tail,
    471                                  gtc);
    472     gtc->suspended = GNUNET_SYSERR;
    473     MHD_resume_connection (gtc->connection);
    474     TALER_MHD_daemon_trigger ();
    475   }
    476 }
    477 
    478 
    479 /**
    480  * Context for building inventory template payloads.
    481  */
    482 struct InventoryPayloadContext
    483 {
    484   /**
    485    * Selected category IDs (as JSON array).
    486    */
    487   const json_t *selected_categories;
    488 
    489   /**
    490    * Selected product IDs (as JSON array) from contract_template.
    491    */
    492   const json_t *selected_products;
    493 
    494   /**
    495    * Whether all products are selected.
    496    */
    497   bool selected_all;
    498 
    499   /**
    500    * JSON array of products to build.
    501    */
    502   json_t *products;
    503 
    504   /**
    505    * JSON array of categories to build.
    506    */
    507   json_t *category_payload;
    508 
    509   /**
    510    * JSON array of units to build.
    511    */
    512   json_t *unit_payload;
    513 
    514   /**
    515    * Set of categories referenced by the products.
    516    */
    517   struct TMH_CategorySet category_set;
    518 
    519   /**
    520    * Set of unit identifiers referenced by the products.
    521    */
    522   struct TMH_UnitSet unit_set;
    523 };
    524 
    525 
    526 /**
    527  * Release resources associated with an inventory payload context.
    528  *
    529  * @param ipc inventory payload context
    530  */
    531 static void
    532 inventory_payload_cleanup (struct InventoryPayloadContext *ipc)
    533 {
    534   if (NULL != ipc->products)
    535     json_decref (ipc->products);
    536   if (NULL != ipc->category_payload)
    537     json_decref (ipc->category_payload);
    538   if (NULL != ipc->unit_payload)
    539     json_decref (ipc->unit_payload);
    540   GNUNET_free (ipc->category_set.ids);
    541   TMH_unit_set_clear (&ipc->unit_set);
    542   ipc->products = NULL;
    543   ipc->category_payload = NULL;
    544   ipc->unit_payload = NULL;
    545   ipc->category_set.len = 0;
    546 }
    547 
    548 
    549 /**
    550  * Add inventory product to JSON payload.
    551  *
    552  * @param cls inventory payload context
    553  * @param product_id product identifier
    554  * @param pd product details
    555  * @param num_categories number of categories
    556  * @param categories category IDs
    557  */
    558 static void
    559 add_inventory_product (
    560   void *cls,
    561   const char *product_id,
    562   const struct TALER_MERCHANTDB_InventoryProductDetails *pd,
    563   size_t num_categories,
    564   const uint64_t *categories)
    565 {
    566   struct InventoryPayloadContext *ipc = cls;
    567   json_t *jcategories;
    568   json_t *product;
    569   char remaining_stock_buf[64];
    570 
    571   jcategories = json_array ();
    572   GNUNET_assert (NULL != jcategories);
    573   for (size_t i = 0; i < num_categories; i++)
    574   {
    575     /* Adding per product category */
    576     TMH_category_set_add (&ipc->category_set,
    577                           categories[i]);
    578     GNUNET_assert (0 ==
    579                    json_array_append_new (jcategories,
    580                                           json_integer (categories[i])));
    581   }
    582   GNUNET_assert (0 < pd->price_array_length);
    583   TALER_MERCHANT_vk_format_fractional_string (
    584     TALER_MERCHANT_VK_STOCK,
    585     pd->remaining_stock,
    586     pd->remaining_stock_frac,
    587     sizeof (remaining_stock_buf),
    588     remaining_stock_buf);
    589   product = GNUNET_JSON_PACK (
    590     GNUNET_JSON_pack_string ("product_id",
    591                              product_id),
    592     GNUNET_JSON_pack_string ("product_name",
    593                              pd->product_name),
    594     GNUNET_JSON_pack_string ("description",
    595                              pd->description),
    596     GNUNET_JSON_pack_allow_null (
    597       GNUNET_JSON_pack_object_incref ("description_i18n",
    598                                       pd->description_i18n)),
    599     GNUNET_JSON_pack_string ("unit",
    600                              pd->unit),
    601     TALER_JSON_pack_amount_array ("unit_prices",
    602                                   pd->price_array_length,
    603                                   pd->price_array),
    604     GNUNET_JSON_pack_bool ("unit_allow_fraction",
    605                            pd->allow_fractional_quantity),
    606     GNUNET_JSON_pack_uint64 ("unit_precision_level",
    607                              pd->fractional_precision_level),
    608     GNUNET_JSON_pack_string ("remaining_stock",
    609                              remaining_stock_buf),
    610     GNUNET_JSON_pack_array_steal ("categories",
    611                                   jcategories),
    612     GNUNET_JSON_pack_allow_null (
    613       GNUNET_JSON_pack_array_incref ("taxes",
    614                                      pd->taxes)),
    615     GNUNET_JSON_pack_allow_null (
    616       GNUNET_JSON_pack_string ("image_hash",
    617                                pd->image_hash)));
    618 
    619   /* Adding per product unit */
    620   TMH_unit_set_add (&ipc->unit_set,
    621                     pd->unit);
    622 
    623   GNUNET_assert (0 ==
    624                  json_array_append_new (ipc->products,
    625                                         product));
    626 }
    627 
    628 
    629 /**
    630  * Add an inventory category to the payload if referenced.
    631  *
    632  * @param cls category payload context
    633  * @param category_id category identifier
    634  * @param category_name category name
    635  * @param category_name_i18n translated names
    636  * @param product_count number of products (unused)
    637  */
    638 static void
    639 add_inventory_category (void *cls,
    640                         uint64_t category_id,
    641                         const char *category_name,
    642                         const json_t *category_name_i18n,
    643                         uint64_t product_count)
    644 {
    645   struct InventoryPayloadContext *ipc = cls;
    646   json_t *category;
    647 
    648   (void) product_count;
    649   category = GNUNET_JSON_PACK (
    650     GNUNET_JSON_pack_uint64 ("category_id",
    651                              category_id),
    652     GNUNET_JSON_pack_string ("category_name",
    653                              category_name),
    654     GNUNET_JSON_pack_allow_null (
    655       GNUNET_JSON_pack_object_incref ("category_name_i18n",
    656                                       (json_t *) category_name_i18n)));
    657   GNUNET_assert (0 ==
    658                  json_array_append_new (ipc->category_payload,
    659                                         category));
    660 }
    661 
    662 
    663 /**
    664  * Add an inventory unit to the payload if referenced and non-builtin.
    665  *
    666  * @param cls unit payload context
    667  * @param unit_serial unit identifier
    668  * @param ud unit details
    669  */
    670 static void
    671 add_inventory_unit (void *cls,
    672                     uint64_t unit_serial,
    673                     const struct TALER_MERCHANTDB_UnitDetails *ud)
    674 {
    675   struct InventoryPayloadContext *ipc = cls;
    676   json_t *unit;
    677 
    678   (void) unit_serial;
    679 
    680   unit = GNUNET_JSON_PACK (
    681     GNUNET_JSON_pack_string ("unit",
    682                              ud->unit),
    683     GNUNET_JSON_pack_string ("unit_name_long",
    684                              ud->unit_name_long),
    685     GNUNET_JSON_pack_allow_null (
    686       GNUNET_JSON_pack_object_incref ("unit_name_long_i18n",
    687                                       ud->unit_name_long_i18n)),
    688     GNUNET_JSON_pack_string ("unit_name_short",
    689                              ud->unit_name_short),
    690     GNUNET_JSON_pack_allow_null (
    691       GNUNET_JSON_pack_object_incref ("unit_name_short_i18n",
    692                                       ud->unit_name_short_i18n)),
    693     GNUNET_JSON_pack_bool ("unit_allow_fraction",
    694                            ud->unit_allow_fraction),
    695     GNUNET_JSON_pack_uint64 ("unit_precision_level",
    696                              ud->unit_precision_level));
    697   GNUNET_assert (0 ==
    698                  json_array_append_new (ipc->unit_payload,
    699                                         unit));
    700 }
    701 
    702 
    703 /**
    704  * Build wallet-facing payload for inventory templates.
    705  *
    706  * @param connection HTTP connection
    707  * @param mi merchant instance
    708  * @param tp template details
    709  * @return newly allocated template contract, NULL if an error was queued
    710  */
    711 static json_t *
    712 build_inventory_template_contract (
    713   struct MHD_Connection *connection,
    714   const struct TMH_MerchantInstance *mi,
    715   const struct TALER_MERCHANTDB_TemplateDetails *tp)
    716 {
    717   struct InventoryPayloadContext ipc;
    718   const char **product_ids = NULL;
    719   uint64_t *category_ids = NULL;
    720   size_t num_product_ids = 0;
    721   size_t num_category_ids = 0;
    722   json_t *inventory_payload;
    723   json_t *template_contract;
    724 
    725   memset (&ipc,
    726           0,
    727           sizeof (ipc));
    728   ipc.products = json_array ();
    729   {
    730     struct GNUNET_JSON_Specification spec[] = {
    731       GNUNET_JSON_spec_mark_optional (
    732         GNUNET_JSON_spec_array_const ("selected_categories",
    733                                       &ipc.selected_categories),
    734         NULL),
    735       GNUNET_JSON_spec_mark_optional (
    736         GNUNET_JSON_spec_array_const ("selected_products",
    737                                       &ipc.selected_products),
    738         NULL),
    739       GNUNET_JSON_spec_mark_optional (
    740         GNUNET_JSON_spec_bool ("selected_all",
    741                                &ipc.selected_all),
    742         NULL),
    743       GNUNET_JSON_spec_end ()
    744     };
    745     const char *err_name;
    746     unsigned int err_line;
    747 
    748     if (GNUNET_OK !=
    749         GNUNET_JSON_parse (tp->template_contract,
    750                            spec,
    751                            &err_name,
    752                            &err_line))
    753     {
    754       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    755                   "Invalid inventory template_contract for field %s\n",
    756                   err_name);
    757       inventory_payload_cleanup (&ipc);
    758       (void) TALER_MHD_reply_with_error (
    759         connection,
    760         MHD_HTTP_INTERNAL_SERVER_ERROR,
    761         TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    762         err_name);
    763       return NULL;
    764     }
    765   }
    766 
    767   if (! ipc.selected_all)
    768   {
    769     if (NULL != ipc.selected_products)
    770     {
    771       size_t max_ids;
    772 
    773       max_ids = json_array_size (ipc.selected_products);
    774       if (0 < max_ids)
    775         product_ids = GNUNET_new_array (max_ids,
    776                                         const char *);
    777       for (size_t i = 0; i < max_ids; i++)
    778       {
    779         const json_t *entry = json_array_get (ipc.selected_products,
    780                                               i);
    781 
    782         if (json_is_string (entry))
    783           product_ids[num_product_ids++] = json_string_value (entry);
    784         else
    785           GNUNET_break (0);
    786       }
    787     }
    788     if (NULL != ipc.selected_categories)
    789     {
    790       size_t max_categories;
    791 
    792       max_categories = json_array_size (ipc.selected_categories);
    793       if (0 < max_categories)
    794         category_ids = GNUNET_new_array (max_categories,
    795                                          uint64_t);
    796       for (size_t i = 0; i < max_categories; i++)
    797       {
    798         const json_t *entry = json_array_get (ipc.selected_categories,
    799                                               i);
    800 
    801         if (json_is_integer (entry) &&
    802             (0 < json_integer_value (entry)))
    803           category_ids[num_category_ids++]
    804             = (uint64_t) json_integer_value (entry);
    805         else
    806           GNUNET_break (0);
    807       }
    808     }
    809   }
    810 
    811   if (ipc.selected_all)
    812   {
    813     enum GNUNET_DB_QueryStatus qs;
    814 
    815     qs = TALER_MERCHANTDB_iterate_inventory_products (TMH_db,
    816                                                       mi->settings.id,
    817                                                       &add_inventory_product,
    818                                                       &ipc);
    819     if (0 > qs)
    820     {
    821       GNUNET_break (0);
    822       inventory_payload_cleanup (&ipc);
    823       (void) TALER_MHD_reply_with_error (
    824         connection,
    825         MHD_HTTP_INTERNAL_SERVER_ERROR,
    826         TALER_EC_GENERIC_DB_FETCH_FAILED,
    827         "iterate_inventory_products");
    828       return NULL;
    829     }
    830   }
    831   else if ( (0 < num_product_ids) ||
    832             (0 < num_category_ids) )
    833   {
    834     enum GNUNET_DB_QueryStatus qs;
    835 
    836     qs = TALER_MERCHANTDB_iterate_inventory_products_filtered (
    837       TMH_db,
    838       mi->settings.id,
    839       product_ids,
    840       num_product_ids,
    841       category_ids,
    842       num_category_ids,
    843       &add_inventory_product,
    844       &ipc);
    845     if (0 > qs)
    846     {
    847       GNUNET_break (0);
    848       inventory_payload_cleanup (&ipc);
    849       GNUNET_free (product_ids);
    850       GNUNET_free (category_ids);
    851       (void) TALER_MHD_reply_with_error (
    852         connection,
    853         MHD_HTTP_INTERNAL_SERVER_ERROR,
    854         TALER_EC_GENERIC_DB_FETCH_FAILED,
    855         "iterate_inventory_products_filtered");
    856       return NULL;
    857     }
    858   }
    859   GNUNET_free (product_ids);
    860   GNUNET_free (category_ids);
    861 
    862   ipc.category_payload = json_array ();
    863   GNUNET_assert (NULL != ipc.category_payload);
    864   if (0 < ipc.category_set.len)
    865   {
    866     enum GNUNET_DB_QueryStatus qs;
    867 
    868     qs = TALER_MERCHANTDB_iterate_categories_by_ids (
    869       TMH_db,
    870       mi->settings.id,
    871       ipc.category_set.ids,
    872       ipc.category_set.len,
    873       &add_inventory_category,
    874       &ipc);
    875     if (0 > qs)
    876     {
    877       GNUNET_break (0);
    878       inventory_payload_cleanup (&ipc);
    879       (void) TALER_MHD_reply_with_error (
    880         connection,
    881         MHD_HTTP_INTERNAL_SERVER_ERROR,
    882         TALER_EC_GENERIC_DB_FETCH_FAILED,
    883         "iterate_categories_by_ids");
    884       return NULL;
    885     }
    886   }
    887 
    888   ipc.unit_payload = json_array ();
    889   GNUNET_assert (NULL != ipc.unit_payload);
    890   if (0 < ipc.unit_set.len)
    891   {
    892     enum GNUNET_DB_QueryStatus qs;
    893 
    894     qs = TALER_MERCHANTDB_iterate_custom_units_by_names (
    895       TMH_db,
    896       mi->settings.id,
    897       (const char *const *) ipc.unit_set.units,
    898       ipc.unit_set.len,
    899       &add_inventory_unit,
    900       &ipc);
    901     if (0 > qs)
    902     {
    903       GNUNET_break (0);
    904       inventory_payload_cleanup (&ipc);
    905       (void) TALER_MHD_reply_with_error (
    906         connection,
    907         MHD_HTTP_INTERNAL_SERVER_ERROR,
    908         TALER_EC_GENERIC_DB_FETCH_FAILED,
    909         "iterate_custom_units_by_names");
    910       return NULL;
    911     }
    912   }
    913 
    914   inventory_payload = GNUNET_JSON_PACK (
    915     GNUNET_JSON_pack_array_steal ("products",
    916                                   ipc.products),
    917     GNUNET_JSON_pack_array_steal ("categories",
    918                                   ipc.category_payload),
    919     GNUNET_JSON_pack_array_steal ("units",
    920                                   ipc.unit_payload));
    921   ipc.products = NULL;
    922   ipc.category_payload = NULL;
    923   ipc.unit_payload = NULL;
    924 
    925   template_contract = json_deep_copy (tp->template_contract);
    926   GNUNET_assert (NULL != template_contract);
    927   /* remove internal fields */
    928   (void) json_object_del (template_contract,
    929                           "selected_categories");
    930   (void) json_object_del (template_contract,
    931                           "selected_products");
    932   (void) json_object_del (template_contract,
    933                           "selected_all");
    934   /* add inventory data */
    935   GNUNET_assert (0 ==
    936                  json_object_set_new (template_contract,
    937                                       "inventory_payload",
    938                                       inventory_payload));
    939   inventory_payload_cleanup (&ipc);
    940   return template_contract;
    941 }
    942 
    943 
    944 enum MHD_Result
    945 TMH_get_templates_ID (
    946   const struct TMH_RequestHandler *rh,
    947   struct MHD_Connection *connection,
    948   struct TMH_HandlerContext *hc)
    949 {
    950   struct GetTemplateContext *gtc = hc->ctx;
    951   struct TMH_MerchantInstance *mi = hc->instance;
    952 
    953   (void) rh;
    954   GNUNET_assert (NULL != mi);
    955   if (NULL == gtc)
    956   {
    957     struct TALER_MERCHANTDB_TemplateDetails tp = { 0 };
    958     enum GNUNET_DB_QueryStatus qs;
    959 
    960     gtc = GNUNET_new (struct GetTemplateContext);
    961     gtc->hc = hc;
    962     gtc->connection = connection;
    963     gtc->exchange_candidates = json_array ();
    964     GNUNET_assert (NULL != gtc->exchange_candidates);
    965     gtc->merchant = TMH_instance_metadata_to_json (mi);
    966     GNUNET_assert (NULL != gtc->merchant);
    967     gtc->merchant_pub = mi->merchant_pub;
    968     hc->ctx = gtc;
    969     hc->cc = &get_template_cleanup;
    970 
    971     qs = TALER_MERCHANTDB_get_template (TMH_db,
    972                                         mi->settings.id,
    973                                         hc->infix,
    974                                         &tp);
    975     if (0 > qs)
    976     {
    977       GNUNET_break (0);
    978       return TALER_MHD_reply_with_error (
    979         connection,
    980         MHD_HTTP_INTERNAL_SERVER_ERROR,
    981         TALER_EC_GENERIC_DB_FETCH_FAILED,
    982         "get_template");
    983     }
    984     if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    985     {
    986       return TALER_MHD_reply_with_error (
    987         connection,
    988         MHD_HTTP_NOT_FOUND,
    989         TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN,
    990         hc->infix);
    991     }
    992     if (NULL != tp.editable_defaults)
    993       gtc->editable_defaults = json_incref (tp.editable_defaults);
    994     switch (TALER_MERCHANT_template_type_from_contract (tp.template_contract))
    995     {
    996     case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
    997       gtc->template_contract = build_inventory_template_contract (connection,
    998                                                                   mi,
    999                                                                   &tp);
   1000       break;
   1001     case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
   1002     case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
   1003       gtc->template_contract = json_incref (tp.template_contract);
   1004       break;
   1005     case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
   1006       GNUNET_break_op (0);
   1007       (void) TALER_MHD_reply_with_error (
   1008         connection,
   1009         MHD_HTTP_INTERNAL_SERVER_ERROR,
   1010         TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
   1011         "template_type");
   1012       break;
   1013     }
   1014     TALER_MERCHANTDB_template_details_free (&tp);
   1015     if (NULL == gtc->template_contract)
   1016       return MHD_YES;
   1017 
   1018     TMH_exchange_get_trusted (&start_exchange_lookup,
   1019                               gtc);
   1020     if (NULL != gtc->lookup_head)
   1021     {
   1022       gtc->timeout_task = GNUNET_SCHEDULER_add_delayed (
   1023         MAX_KEYS_WAIT,
   1024         &exchange_lookup_timeout,
   1025         gtc);
   1026       GNUNET_CONTAINER_DLL_insert (gtc_head,
   1027                                    gtc_tail,
   1028                                    gtc);
   1029       gtc->suspended = GNUNET_YES;
   1030       MHD_suspend_connection (connection);
   1031       return MHD_YES;
   1032     }
   1033   }
   1034   if (GNUNET_SYSERR == gtc->suspended)
   1035     return MHD_NO;
   1036   return TALER_MHD_REPLY_JSON_PACK (
   1037     connection,
   1038     MHD_HTTP_OK,
   1039     GNUNET_JSON_pack_object_incref ("template_contract",
   1040                                     gtc->template_contract),
   1041     GNUNET_JSON_pack_allow_null (
   1042       GNUNET_JSON_pack_object_incref ("editable_defaults",
   1043                                       gtc->editable_defaults)),
   1044     GNUNET_JSON_pack_allow_null (
   1045       GNUNET_JSON_pack_string ("required_currency",
   1046                                get_required_currency (
   1047                                  gtc->template_contract))),
   1048     GNUNET_JSON_pack_object_incref ("merchant",
   1049                                     gtc->merchant),
   1050     GNUNET_JSON_pack_data_auto ("merchant_pub",
   1051                                 &gtc->merchant_pub),
   1052     GNUNET_JSON_pack_array_incref ("exchange_candidates",
   1053                                    gtc->exchange_candidates));
   1054 }
   1055 
   1056 
   1057 /* end of taler-merchant-httpd_get-templates-TEMPLATE_ID.c */