LCOV - code coverage report
Current view: top level - backend - taler-merchant-httpd_post-templates-TEMPLATE_ID.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 68.5 % 759 520
Test Date: 2026-09-04 23:42:01 Functions: 93.8 % 32 30

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   (C) 2022-2026 Taler Systems SA
       4              : 
       5              :   TALER is free software; you can redistribute it and/or modify
       6              :   it under the terms of the GNU Affero General Public License as
       7              :   published by the Free Software Foundation; either version 3,
       8              :   or (at your option) any later version.
       9              : 
      10              :   TALER is distributed in the hope that it will be useful, but
      11              :   WITHOUT ANY WARRANTY; without even the implied warranty of
      12              :   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13              :   GNU General Public License for more details.
      14              : 
      15              :   You should have received a copy of the GNU General Public
      16              :   License along with TALER; see the file COPYING.  If not,
      17              :   see <http://www.gnu.org/licenses/>
      18              : */
      19              : 
      20              : /**
      21              :  * @file src/backend/taler-merchant-httpd_post-templates-TEMPLATE_ID.c
      22              :  * @brief implementing POST /using-templates request handling
      23              :  * @author Priscilla HUANG
      24              :  * @author Christian Grothoff
      25              :  */
      26              : #include "platform.h"
      27              : #include "taler-merchant-httpd_exchanges.h"
      28              : #include "taler-merchant-httpd_post-templates-TEMPLATE_ID.h"
      29              : #include "taler-merchant-httpd_post-private-orders.h"
      30              : #include "taler-merchant-httpd_helper.h"
      31              : #include "taler-merchant-httpd_get-exchanges.h"
      32              : #include "taler/taler_merchant_util.h"
      33              : #include <taler/taler_json_lib.h>
      34              : #include <regex.h>
      35              : #include "merchant-database/get_product.h"
      36              : #include "merchant-database/get_template.h"
      37              : 
      38              : 
      39              : /**
      40              :  * Maximum number of entries we accept in the @e inventory_selection
      41              :  * array of a request.  Each entry costs us one database round-trip
      42              :  * and a full copy of the product details (including the base64-encoded
      43              :  * product image), so this must be bounded independently of the maximum
      44              :  * upload size.
      45              :  */
      46              : #define MAX_INVENTORY_SELECTION 1024
      47              : 
      48              : 
      49              : /**
      50              :  * Amount the client chose for one of the choices of a paivana template.
      51              :  */
      52              : struct PaivanaChoiceAmount
      53              : {
      54              :   /**
      55              :    * Index into the @e choices array of the template contract.
      56              :    */
      57              :   uint32_t choice_index;
      58              : 
      59              :   /**
      60              :    * Amount to use for that choice, excluding any tip.
      61              :    */
      62              :   struct TALER_Amount amount;
      63              : };
      64              : 
      65              : 
      66              : /**
      67              :  * Item selected from inventory_selection.
      68              :  */
      69              : struct InventoryTemplateItemContext
      70              : {
      71              :   /**
      72              :    * Product ID as referenced in inventory.
      73              :    */
      74              :   const char *product_id;
      75              : 
      76              :   /**
      77              :    * Unit quantity string as provided by the client.
      78              :    */
      79              :   const char *unit_quantity;
      80              : 
      81              :   /**
      82              :    * Parsed integer quantity.
      83              :    */
      84              :   uint64_t quantity_value;
      85              : 
      86              :   /**
      87              :    * Parsed fractional quantity.
      88              :    */
      89              :   uint32_t quantity_frac;
      90              : 
      91              :   /**
      92              :    * Product details from the DB (includes price array).
      93              :    */
      94              :   struct TALER_MERCHANTDB_ProductDetails pd;
      95              : 
      96              :   /**
      97              :    * Categories referenced by the product.
      98              :    */
      99              :   uint64_t *categories;
     100              : 
     101              :   /**
     102              :    * Length of @e categories.
     103              :    */
     104              :   size_t num_categories;
     105              : };
     106              : 
     107              : 
     108              : /**
     109              :  * Our context.
     110              :  */
     111              : enum UsePhase
     112              : {
     113              :   /**
     114              :    * Parse request payload into context fields.
     115              :    */
     116              :   USE_PHASE_PARSE_REQUEST,
     117              : 
     118              :   /**
     119              :    * Fetch template details from the database.
     120              :    */
     121              :   USE_PHASE_LOOKUP_TEMPLATE,
     122              : 
     123              :   /**
     124              :    * Parse template.
     125              :    */
     126              :   USE_PHASE_PARSE_TEMPLATE,
     127              : 
     128              :   /**
     129              :    * Load additional details (like products and
     130              :    * categories) needed for verification and
     131              :    * price computation.
     132              :    */
     133              :   USE_PHASE_DB_FETCH,
     134              : 
     135              :   /**
     136              :    * Validate request and template compatibility.
     137              :    */
     138              :   USE_PHASE_VERIFY,
     139              : 
     140              :   /**
     141              :    * Compute price of the order.
     142              :    */
     143              :   USE_PHASE_COMPUTE_PRICE,
     144              : 
     145              :   /**
     146              :    * Handle tip.
     147              :    */
     148              :   USE_PHASE_CHECK_TIP,
     149              : 
     150              :   /**
     151              :    * Check if client-supplied total amount matches
     152              :    * our calculation (if we did any).
     153              :    */
     154              :   USE_PHASE_CHECK_TOTAL,
     155              : 
     156              :   /**
     157              :    * Construct the internal order request body.
     158              :    */
     159              :   USE_PHASE_CREATE_ORDER,
     160              : 
     161              :   /**
     162              :    * Submit the order to the shared order handler.
     163              :    */
     164              :   USE_PHASE_SUBMIT_ORDER,
     165              : 
     166              :   /**
     167              :    * Finished successfully with MHD_YES.
     168              :    */
     169              :   USE_PHASE_FINISHED_MHD_YES,
     170              : 
     171              :   /**
     172              :    * Finished with MHD_NO.
     173              :    */
     174              :   USE_PHASE_FINISHED_MHD_NO
     175              : };
     176              : 
     177              : struct UseContext
     178              : {
     179              :   /**
     180              :    * Context for our handler.
     181              :    */
     182              :   struct TMH_HandlerContext *hc;
     183              : 
     184              :   /**
     185              :    * Internal handler context we are passing into the
     186              :    * POST /private/orders handler.
     187              :    */
     188              :   struct TMH_HandlerContext ihc;
     189              : 
     190              :   /**
     191              :    * Phase we are currently in.
     192              :    */
     193              :   enum UsePhase phase;
     194              : 
     195              :   /**
     196              :    * Template type from the contract.
     197              :    */
     198              :   enum TALER_MERCHANT_TemplateType template_type;
     199              : 
     200              :   /**
     201              :    * Information set in the #USE_PHASE_PARSE_REQUEST phase.
     202              :    */
     203              :   struct
     204              :   {
     205              :     /**
     206              :      * Summary override from request, if any.
     207              :      */
     208              :     const char *summary;
     209              : 
     210              :     /**
     211              :      * Amount provided by the client.
     212              :      */
     213              :     struct TALER_Amount amount;
     214              : 
     215              :     /**
     216              :      * Tip provided by the client.
     217              :      */
     218              :     struct TALER_Amount tip;
     219              : 
     220              :     /**
     221              :      * True if @e amount was not provided.
     222              :      */
     223              :     bool no_amount;
     224              : 
     225              :     /**
     226              :      * True if @e tip was not provided.
     227              :      */
     228              :     bool no_tip;
     229              : 
     230              :     /**
     231              :      * Parsed fields for inventory templates.
     232              :      */
     233              :     struct
     234              :     {
     235              :       /**
     236              :        * Selected products from inventory_selection.
     237              :        */
     238              :       struct InventoryTemplateItemContext *items;
     239              : 
     240              :       /**
     241              :        * Length of @e items.
     242              :        */
     243              :       unsigned int items_len;
     244              : 
     245              :     } inventory;
     246              : 
     247              :     /**
     248              :      * Request details if this is a paivana instantiation.
     249              :      */
     250              :     struct
     251              :     {
     252              : 
     253              :       /**
     254              :        * Target website for the request.
     255              :        */
     256              :       const char *website;
     257              : 
     258              :       /**
     259              :        * Unique client identifier, consisting of
     260              :        * current time, "-", and the hash of a nonce,
     261              :        * the website and the current time.
     262              :        */
     263              :       const char *paivana_id;
     264              : 
     265              :       /**
     266              :        * Amounts the client picked for those choices of the
     267              :        * template that allow the amount to be edited.
     268              :        */
     269              :       struct PaivanaChoiceAmount *choice_amounts;
     270              : 
     271              :       /**
     272              :        * Length of the @e choice_amounts array.
     273              :        */
     274              :       unsigned int choice_amounts_len;
     275              : 
     276              :     } paivana;
     277              : 
     278              :   } parse_request;
     279              : 
     280              :   /**
     281              :    * Information set in the #USE_PHASE_LOOKUP_TEMPLATE phase.
     282              :    */
     283              :   struct
     284              :   {
     285              : 
     286              :     /**
     287              :      * Our template details from the DB.
     288              :      */
     289              :     struct TALER_MERCHANTDB_TemplateDetails etp;
     290              : 
     291              :   } lookup_template;
     292              : 
     293              :   /**
     294              :    * Information set in the #USE_PHASE_PARSE_TEMPLATE phase.
     295              :    */
     296              :   struct TALER_MERCHANT_TemplateContract template_contract;
     297              : 
     298              :   /**
     299              :    * Information set in the #USE_PHASE_COMPUTE_PRICE phase.
     300              :    */
     301              :   struct
     302              :   {
     303              : 
     304              :     /**
     305              :      * Per-currency totals across selected products (without tips).
     306              :      */
     307              :     struct TALER_Amount *totals;
     308              : 
     309              :     /**
     310              :      * Length of @e totals.
     311              :      */
     312              :     unsigned int totals_len;
     313              : 
     314              :     /**
     315              :      * Array of payment choices, used with Paviana.
     316              :      */
     317              :     json_t *choices;
     318              : 
     319              :   } compute_price;
     320              : 
     321              : };
     322              : 
     323              : 
     324              : /**
     325              :  * Clean up inventory items.
     326              :  *
     327              :  * @param items_len length of @a items
     328              :  * @param[in] items item array to free
     329              :  */
     330              : static void
     331           18 : cleanup_inventory_items (
     332              :   unsigned int items_len,
     333              :   struct InventoryTemplateItemContext items[static items_len])
     334           18 : {
     335           44 :   for (unsigned int i = 0; i < items_len; i++)
     336              :   {
     337           26 :     struct InventoryTemplateItemContext *item = &items[i];
     338              : 
     339           26 :     TALER_MERCHANTDB_product_details_free (&item->pd);
     340           26 :     GNUNET_free (item->categories);
     341              :   }
     342           18 :   GNUNET_free (items);
     343           18 : }
     344              : 
     345              : 
     346              : /**
     347              :  * Clean up a `struct UseContext *`
     348              :  *
     349              :  * @param[in] cls a `struct UseContext *`
     350              :  */
     351              : static void
     352           46 : cleanup_use_context (void *cls)
     353              : {
     354           46 :   struct UseContext *uc = cls;
     355              : 
     356           46 :   TALER_MERCHANTDB_template_details_free (&uc->lookup_template.etp);
     357           46 :   if (NULL !=
     358           46 :       uc->parse_request.inventory.items)
     359           18 :     cleanup_inventory_items (uc->parse_request.inventory.items_len,
     360              :                              uc->parse_request.inventory.items);
     361           46 :   GNUNET_array_grow (uc->parse_request.paivana.choice_amounts,
     362              :                      uc->parse_request.paivana.choice_amounts_len,
     363              :                      0);
     364           46 :   TALER_MERCHANT_template_contract_free (&uc->template_contract);
     365           46 :   GNUNET_free (uc->compute_price.totals);
     366           46 :   uc->compute_price.totals_len = 0;
     367           46 :   json_decref (uc->compute_price.choices);
     368           46 :   if (NULL != uc->ihc.cc)
     369           19 :     uc->ihc.cc (uc->ihc.ctx);
     370           46 :   GNUNET_free (uc->ihc.infix);
     371           46 :   json_decref (uc->ihc.request_body);
     372           46 :   GNUNET_free (uc);
     373           46 : }
     374              : 
     375              : 
     376              : /**
     377              :  * Finalize a template use request.
     378              :  *
     379              :  * @param[in,out] uc use context
     380              :  * @param ret handler return value
     381              :  */
     382              : static void
     383           27 : use_finalize (struct UseContext *uc,
     384              :               enum MHD_Result ret)
     385              : {
     386           27 :   uc->phase = (MHD_YES == ret)
     387              :     ? USE_PHASE_FINISHED_MHD_YES
     388           27 :     : USE_PHASE_FINISHED_MHD_NO;
     389           27 : }
     390              : 
     391              : 
     392              : /**
     393              :  * Finalize after JSON parsing result.
     394              :  *
     395              :  * @param[in,out] uc use context
     396              :  * @param res parse result
     397              :  */
     398              : static void
     399            0 : use_finalize_parse (struct UseContext *uc,
     400              :                     enum GNUNET_GenericReturnValue res)
     401              : {
     402            0 :   GNUNET_assert (GNUNET_OK != res);
     403            0 :   use_finalize (uc,
     404              :                 (GNUNET_NO == res)
     405              :                 ? MHD_YES
     406              :                 : MHD_NO);
     407            0 : }
     408              : 
     409              : 
     410              : /**
     411              :  * Reply with error and finalize the request.
     412              :  *
     413              :  * @param[in,out] uc use context
     414              :  * @param http_status HTTP status code
     415              :  * @param ec error code
     416              :  * @param detail error detail
     417              :  */
     418              : static void
     419           27 : use_reply_with_error (struct UseContext *uc,
     420              :                       unsigned int http_status,
     421              :                       enum TALER_ErrorCode ec,
     422              :                       const char *detail)
     423              : {
     424              :   enum MHD_Result mret;
     425              : 
     426           27 :   mret = TALER_MHD_reply_with_error (uc->hc->connection,
     427              :                                      http_status,
     428              :                                      ec,
     429              :                                      detail);
     430           27 :   use_finalize (uc,
     431              :                 mret);
     432           27 : }
     433              : 
     434              : 
     435              : /* ***************** USE_PHASE_PARSE_REQUEST **************** */
     436              : 
     437              : /**
     438              :  * Parse request data for inventory templates.
     439              :  *
     440              :  * @param[in,out] uc use context
     441              :  * @return #GNUNET_OK on success
     442              :  */
     443              : static enum GNUNET_GenericReturnValue
     444           20 : parse_using_templates_inventory_request (
     445              :   struct UseContext *uc)
     446              : {
     447              :   const json_t *inventory_selection;
     448              :   struct GNUNET_JSON_Specification spec[] = {
     449           20 :     GNUNET_JSON_spec_array_const ("inventory_selection",
     450              :                                   &inventory_selection),
     451           20 :     GNUNET_JSON_spec_end ()
     452              :   };
     453              :   enum GNUNET_GenericReturnValue res;
     454              : 
     455           20 :   GNUNET_assert (NULL == uc->ihc.request_body);
     456           20 :   res = TALER_MHD_parse_json_data (uc->hc->connection,
     457           20 :                                    uc->hc->request_body,
     458              :                                    spec);
     459           20 :   if (GNUNET_OK != res)
     460              :   {
     461            0 :     GNUNET_break_op (0);
     462            0 :     use_finalize_parse (uc,
     463              :                         res);
     464            0 :     return GNUNET_SYSERR;
     465              :   }
     466              : 
     467           20 :   if ( (! uc->parse_request.no_amount) &&
     468           20 :        (! TMH_test_exchange_configured_for_currency (
     469           20 :           uc->parse_request.amount.currency)) )
     470              :   {
     471            0 :     GNUNET_break_op (0);
     472            0 :     use_reply_with_error (uc,
     473              :                           MHD_HTTP_CONFLICT,
     474              :                           TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
     475              :                           "Currency is not supported by backend");
     476            0 :     return GNUNET_SYSERR;
     477              :   }
     478              : 
     479           20 :   if (MAX_INVENTORY_SELECTION < json_array_size (inventory_selection))
     480              :   {
     481            0 :     GNUNET_break_op (0);
     482            0 :     use_reply_with_error (uc,
     483              :                           MHD_HTTP_BAD_REQUEST,
     484              :                           TALER_EC_GENERIC_PARAMETER_MALFORMED,
     485              :                           "inventory_selection (too many entries)");
     486            0 :     return GNUNET_SYSERR;
     487              :   }
     488           46 :   for (size_t i = 0; i < json_array_size (inventory_selection); i++)
     489              :   {
     490           26 :     struct InventoryTemplateItemContext item = { 0 };
     491              :     struct GNUNET_JSON_Specification ispec[] = {
     492           26 :       TALER_JSON_spec_slug ("product_id",
     493              :                             &item.product_id),
     494           26 :       GNUNET_JSON_spec_string ("quantity",
     495              :                                &item.unit_quantity),
     496           26 :       GNUNET_JSON_spec_end ()
     497              :     };
     498              :     const char *err_name;
     499              :     unsigned int err_line;
     500              : 
     501           26 :     res = GNUNET_JSON_parse (json_array_get (inventory_selection,
     502              :                                              i),
     503              :                              ispec,
     504              :                              &err_name,
     505              :                              &err_line);
     506           26 :     if (GNUNET_OK != res)
     507              :     {
     508            0 :       GNUNET_break_op (0);
     509            0 :       use_reply_with_error (uc,
     510              :                             MHD_HTTP_BAD_REQUEST,
     511              :                             TALER_EC_GENERIC_PARAMETER_MALFORMED,
     512              :                             "inventory_selection");
     513            0 :       return GNUNET_SYSERR;
     514              :     }
     515              : 
     516           26 :     GNUNET_array_append (uc->parse_request.inventory.items,
     517              :                          uc->parse_request.inventory.items_len,
     518              :                          item);
     519              :   }
     520           20 :   return GNUNET_OK;
     521              : }
     522              : 
     523              : 
     524              : /**
     525              :  * Parse request data for paivana templates.
     526              :  *
     527              :  * @param[in,out] uc use context
     528              :  * @return #GNUNET_OK on success
     529              :  */
     530              : static enum GNUNET_GenericReturnValue
     531            8 : parse_using_templates_paivana_request (
     532              :   struct UseContext *uc)
     533              : {
     534            8 :   const json_t *choice_amounts = NULL;
     535              :   struct GNUNET_JSON_Specification spec[] = {
     536            8 :     TALER_JSON_spec_web_url ("website",
     537              :                              &uc->parse_request.paivana.website),
     538            8 :     GNUNET_JSON_spec_string ("paivana_id",
     539              :                              &uc->parse_request.paivana.paivana_id),
     540            8 :     GNUNET_JSON_spec_mark_optional (
     541              :       GNUNET_JSON_spec_array_const ("choice_amounts",
     542              :                                     &choice_amounts),
     543              :       NULL),
     544            8 :     GNUNET_JSON_spec_end ()
     545              :   };
     546              :   enum GNUNET_GenericReturnValue res;
     547              :   unsigned long long tv;
     548              :   const char *dash;
     549              : 
     550            8 :   GNUNET_assert (NULL == uc->ihc.request_body);
     551            8 :   res = TALER_MHD_parse_json_data (uc->hc->connection,
     552            8 :                                    uc->hc->request_body,
     553              :                                    spec);
     554            8 :   if (GNUNET_OK != res)
     555              :   {
     556            0 :     GNUNET_break_op (0);
     557            0 :     use_finalize_parse (uc,
     558              :                         res);
     559            0 :     return GNUNET_SYSERR;
     560              :   }
     561            8 :   if (NULL != choice_amounts)
     562              :   {
     563            3 :     if (! uc->parse_request.no_amount)
     564              :     {
     565              :       /* 'amount' is the shorthand for a template with a single
     566              :          editable choice; using both is ambiguous. */
     567            1 :       GNUNET_break_op (0);
     568            1 :       use_reply_with_error (
     569              :         uc,
     570              :         MHD_HTTP_CONFLICT,
     571              :         TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
     572              :         "amount and choice_amounts are mutually exclusive");
     573            1 :       return GNUNET_SYSERR;
     574              :     }
     575            4 :     for (size_t i = 0; i < json_array_size (choice_amounts); i++)
     576              :     {
     577              :       struct PaivanaChoiceAmount ca;
     578              :       struct GNUNET_JSON_Specification ispec[] = {
     579            2 :         GNUNET_JSON_spec_uint32 ("choice_index",
     580              :                                  &ca.choice_index),
     581            2 :         TALER_JSON_spec_amount_any ("amount",
     582              :                                     &ca.amount),
     583            2 :         GNUNET_JSON_spec_end ()
     584              :       };
     585              :       const char *err_name;
     586              :       unsigned int err_line;
     587              : 
     588            2 :       if (GNUNET_OK !=
     589            2 :           GNUNET_JSON_parse (json_array_get (choice_amounts,
     590              :                                              i),
     591              :                              ispec,
     592              :                              &err_name,
     593              :                              &err_line))
     594              :       {
     595            0 :         GNUNET_break_op (0);
     596            0 :         use_reply_with_error (uc,
     597              :                               MHD_HTTP_BAD_REQUEST,
     598              :                               TALER_EC_GENERIC_PARAMETER_MALFORMED,
     599              :                               "choice_amounts");
     600            0 :         return GNUNET_SYSERR;
     601              :       }
     602            2 :       for (unsigned int j = 0;
     603            2 :            j < uc->parse_request.paivana.choice_amounts_len;
     604            0 :            j++)
     605              :       {
     606            0 :         if (uc->parse_request.paivana.choice_amounts[j].choice_index !=
     607            0 :             ca.choice_index)
     608            0 :           continue;
     609            0 :         GNUNET_break_op (0);
     610            0 :         use_reply_with_error (uc,
     611              :                               MHD_HTTP_BAD_REQUEST,
     612              :                               TALER_EC_GENERIC_PARAMETER_MALFORMED,
     613              :                               "choice_amounts::choice_index is not unique");
     614            0 :         return GNUNET_SYSERR;
     615              :       }
     616              :       /* The currency does not need to be checked against our
     617              :          configuration here: it must match the currency of the
     618              :          choice in the template, which the merchant picked. */
     619            2 :       GNUNET_array_append (uc->parse_request.paivana.choice_amounts,
     620              :                            uc->parse_request.paivana.choice_amounts_len,
     621              :                            ca);
     622              :     }
     623              :   }
     624            7 :   if (! TALER_is_session_id (uc->parse_request.paivana.paivana_id))
     625              :   {
     626              :     /* The Paivana ID becomes the session ID of the order, and thus
     627              :        ends up as a path component of the "taler://pay/" URI; the
     628              :        base64url decoding below is too lenient to ensure this. */
     629            0 :     GNUNET_break_op (0);
     630            0 :     use_reply_with_error (uc,
     631              :                           MHD_HTTP_BAD_REQUEST,
     632              :                           TALER_EC_GENERIC_PARAMETER_MALFORMED,
     633              :                           "paivana_id");
     634            0 :     return GNUNET_SYSERR;
     635              :   }
     636            7 :   if (1 !=
     637            7 :       sscanf (uc->parse_request.paivana.paivana_id,
     638              :               "%llu-",
     639              :               &tv))
     640              :   {
     641            0 :     GNUNET_break_op (0);
     642            0 :     use_reply_with_error (uc,
     643              :                           MHD_HTTP_BAD_REQUEST,
     644              :                           TALER_EC_GENERIC_PARAMETER_MALFORMED,
     645              :                           "paivana_id");
     646            0 :     return GNUNET_SYSERR;
     647              :   }
     648            7 :   dash = strchr (uc->parse_request.paivana.paivana_id,
     649              :                  '-');
     650            7 :   if (NULL == dash)
     651              :   {
     652            0 :     GNUNET_break_op (0);
     653            0 :     use_reply_with_error (uc,
     654              :                           MHD_HTTP_BAD_REQUEST,
     655              :                           TALER_EC_GENERIC_PARAMETER_MALFORMED,
     656              :                           "paivana_id");
     657            0 :     return GNUNET_SYSERR;
     658              :   }
     659              :   {
     660              :     size_t olen;
     661            7 :     void *out = NULL;
     662              : 
     663            7 :     olen = GNUNET_STRINGS_base64url_decode (dash + 1,
     664              :                                             strlen (dash + 1),
     665              :                                             &out);
     666            7 :     GNUNET_free (out);
     667            7 :     if (sizeof (struct GNUNET_ShortHashCode) != olen)
     668              :     {
     669            0 :       GNUNET_break_op (0);
     670            0 :       use_reply_with_error (uc,
     671              :                             MHD_HTTP_BAD_REQUEST,
     672              :                             TALER_EC_GENERIC_PARAMETER_MALFORMED,
     673              :                             "paivana_id");
     674            0 :       return GNUNET_SYSERR;
     675              :     }
     676              :   }
     677            7 :   return GNUNET_OK;
     678              : }
     679              : 
     680              : 
     681              : /**
     682              :  * Main function for the #USE_PHASE_PARSE_REQUEST.
     683              :  *
     684              :  * @param[in,out] uc context to update
     685              :  */
     686              : static void
     687           46 : handle_phase_parse_request (
     688              :   struct UseContext *uc)
     689              : {
     690           46 :   const char *template_type = NULL;
     691              :   struct GNUNET_JSON_Specification spec[] = {
     692           46 :     GNUNET_JSON_spec_mark_optional (
     693              :       GNUNET_JSON_spec_string ("template_type",
     694              :                                &template_type),
     695              :       NULL),
     696           46 :     GNUNET_JSON_spec_mark_optional (
     697              :       TALER_JSON_spec_amount_any ("tip",
     698              :                                   &uc->parse_request.tip),
     699              :       &uc->parse_request.no_tip),
     700           46 :     GNUNET_JSON_spec_mark_optional (
     701              :       GNUNET_JSON_spec_string ("summary",
     702              :                                &uc->parse_request.summary),
     703              :       NULL),
     704           46 :     GNUNET_JSON_spec_mark_optional (
     705              :       TALER_JSON_spec_amount_any ("amount",
     706              :                                   &uc->parse_request.amount),
     707              :       &uc->parse_request.no_amount),
     708           46 :     GNUNET_JSON_spec_end ()
     709              :   };
     710              :   enum GNUNET_GenericReturnValue res;
     711              : 
     712           46 :   res = TALER_MHD_parse_json_data (uc->hc->connection,
     713           46 :                                    uc->hc->request_body,
     714              :                                    spec);
     715           46 :   if (GNUNET_OK != res)
     716              :   {
     717            0 :     GNUNET_break_op (0);
     718            0 :     use_finalize_parse (uc,
     719              :                         res);
     720           46 :     return;
     721              :   }
     722           46 :   if (NULL == template_type)
     723            0 :     template_type = "fixed-order";
     724              :   uc->template_type
     725           46 :     = TALER_MERCHANT_template_type_from_string (
     726              :         template_type);
     727           46 :   switch (uc->template_type)
     728              :   {
     729           18 :   case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
     730              :     /* nothig left to do */
     731           18 :     uc->phase++;
     732           18 :     return;
     733            8 :   case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
     734            8 :     res = parse_using_templates_paivana_request (uc);
     735            8 :     if (GNUNET_OK == res)
     736            7 :       uc->phase++;
     737            8 :     return;
     738           20 :   case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
     739           20 :     res = parse_using_templates_inventory_request (uc);
     740           20 :     if (GNUNET_OK == res)
     741           20 :       uc->phase++;
     742           20 :     return;
     743            0 :   case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
     744            0 :     break;
     745              :   }
     746            0 :   GNUNET_break (0);
     747            0 :   use_reply_with_error (
     748              :     uc,
     749              :     MHD_HTTP_BAD_REQUEST,
     750              :     TALER_EC_GENERIC_PARAMETER_MALFORMED,
     751              :     "template_type");
     752              : }
     753              : 
     754              : 
     755              : /* ***************** USE_PHASE_LOOKUP_TEMPLATE **************** */
     756              : 
     757              : /**
     758              :  * Main function for the #USE_PHASE_LOOKUP_TEMPLATE.
     759              :  *
     760              :  * @param[in,out] uc context to update
     761              :  */
     762              : static void
     763           45 : handle_phase_lookup_template (
     764              :   struct UseContext *uc)
     765              : {
     766           45 :   struct TMH_MerchantInstance *mi = uc->hc->instance;
     767           45 :   const char *template_id = uc->hc->infix;
     768              :   enum GNUNET_DB_QueryStatus qs;
     769              : 
     770           45 :   qs = TALER_MERCHANTDB_get_template (TMH_db,
     771           45 :                                       mi->settings.id,
     772              :                                       template_id,
     773              :                                       &uc->lookup_template.etp);
     774           45 :   switch (qs)
     775              :   {
     776            0 :   case GNUNET_DB_STATUS_HARD_ERROR:
     777              :     /* Clean up and fail hard */
     778            0 :     GNUNET_break (0);
     779            0 :     use_reply_with_error (uc,
     780              :                           MHD_HTTP_INTERNAL_SERVER_ERROR,
     781              :                           TALER_EC_GENERIC_DB_FETCH_FAILED,
     782              :                           "get_template");
     783            0 :     return;
     784            0 :   case GNUNET_DB_STATUS_SOFT_ERROR:
     785              :     /* this should be impossible (single select) */
     786            0 :     GNUNET_break (0);
     787            0 :     use_reply_with_error (uc,
     788              :                           MHD_HTTP_INTERNAL_SERVER_ERROR,
     789              :                           TALER_EC_GENERIC_DB_FETCH_FAILED,
     790              :                           "get_template");
     791            0 :     return;
     792            2 :   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     793              :     /* template not found! */
     794            2 :     use_reply_with_error (uc,
     795              :                           MHD_HTTP_NOT_FOUND,
     796              :                           TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN,
     797              :                           template_id);
     798            2 :     return;
     799           43 :   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     800              :     /* all good */
     801           43 :     break;
     802              :   }
     803           43 :   if (uc->template_type !=
     804           43 :       TALER_MERCHANT_template_type_from_contract (
     805           43 :         uc->lookup_template.etp.template_contract))
     806              :   {
     807            0 :     GNUNET_break_op (0);
     808            0 :     use_reply_with_error (
     809              :       uc,
     810              :       MHD_HTTP_CONFLICT,
     811              :       TALER_EC_MERCHANT_POST_USING_TEMPLATES_WRONG_TYPE,
     812              :       "template_contract has different type");
     813            0 :     return;
     814              :   }
     815           43 :   uc->phase++;
     816              : }
     817              : 
     818              : 
     819              : /* ***************** USE_PHASE_PARSE_TEMPLATE **************** */
     820              : 
     821              : 
     822              : /**
     823              :  * Parse template.
     824              :  *
     825              :  * @param[in,out] uc use context
     826              :  */
     827              : static void
     828           43 : handle_phase_template_contract (struct UseContext *uc)
     829              : {
     830              :   const char *err_name;
     831              :   enum GNUNET_GenericReturnValue res;
     832              : 
     833           43 :   res = TALER_MERCHANT_template_contract_parse (
     834           43 :     uc->lookup_template.etp.template_contract,
     835              :     &uc->template_contract,
     836              :     &err_name);
     837           43 :   if (GNUNET_OK != res)
     838              :   {
     839            0 :     GNUNET_break (0);
     840            0 :     use_reply_with_error (uc,
     841              :                           MHD_HTTP_INTERNAL_SERVER_ERROR,
     842              :                           TALER_EC_GENERIC_DB_FETCH_FAILED,
     843              :                           err_name);
     844            0 :     return;
     845              :   }
     846           43 :   uc->phase++;
     847              : }
     848              : 
     849              : 
     850              : /* ***************** USE_PHASE_DB_FETCH **************** */
     851              : 
     852              : /**
     853              :  * Fetch DB data for inventory templates.
     854              :  *
     855              :  * @param[in,out] uc use context
     856              :  */
     857              : static void
     858           43 : handle_phase_db_fetch (struct UseContext *uc)
     859              : {
     860           43 :   struct TMH_MerchantInstance *mi = uc->hc->instance;
     861              : 
     862           43 :   switch (uc->template_type)
     863              :   {
     864           16 :   case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
     865           16 :     uc->phase++;
     866           16 :     return;
     867            7 :   case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
     868            7 :     uc->phase++;
     869            7 :     return;
     870           20 :   case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
     871           20 :     break;
     872            0 :   case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
     873            0 :     GNUNET_assert (0);
     874              :   }
     875              : 
     876           20 :   for (unsigned int i = 0;
     877           44 :        i < uc->parse_request.inventory.items_len;
     878           24 :        i++)
     879              :   {
     880           26 :     struct InventoryTemplateItemContext *item =
     881           26 :       &uc->parse_request.inventory.items[i];
     882              :     enum GNUNET_DB_QueryStatus qs;
     883              : 
     884           26 :     qs = TALER_MERCHANTDB_get_product (TMH_db,
     885           26 :                                        mi->settings.id,
     886              :                                        item->product_id,
     887              :                                        &item->pd,
     888              :                                        &item->num_categories,
     889              :                                        &item->categories);
     890           26 :     switch (qs)
     891              :     {
     892            0 :     case GNUNET_DB_STATUS_HARD_ERROR:
     893            0 :       GNUNET_break (0);
     894            0 :       use_reply_with_error (uc,
     895              :                             MHD_HTTP_INTERNAL_SERVER_ERROR,
     896              :                             TALER_EC_GENERIC_DB_FETCH_FAILED,
     897              :                             "get_product");
     898            0 :       return;
     899            0 :     case GNUNET_DB_STATUS_SOFT_ERROR:
     900            0 :       GNUNET_break (0);
     901            0 :       use_reply_with_error (uc,
     902              :                             MHD_HTTP_INTERNAL_SERVER_ERROR,
     903              :                             TALER_EC_GENERIC_DB_FETCH_FAILED,
     904              :                             "get_product");
     905            0 :       return;
     906            2 :     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
     907            2 :       use_reply_with_error (uc,
     908              :                             MHD_HTTP_NOT_FOUND,
     909              :                             TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN,
     910              :                             item->product_id);
     911            2 :       return;
     912           24 :     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
     913           24 :       break;
     914              :     }
     915              :   }
     916           18 :   uc->phase++;
     917              : }
     918              : 
     919              : 
     920              : /* *************** Helpers for USE_PHASE_VERIFY ***************** */
     921              : 
     922              : /**
     923              :  * Check that @a amount is within the limits (if any) the template
     924              :  * imposes on amounts the client had an influence on.  Amounts the
     925              :  * merchant hard-coded in the template are not subject to these limits.
     926              :  * Replies with an error if the check fails.
     927              :  *
     928              :  * @param[in,out] uc use context
     929              :  * @param amount amount to check, excluding any tip
     930              :  * @param detail hint to return to the client on failure
     931              :  * @return #GNUNET_OK if @a amount is acceptable
     932              :  */
     933              : static enum GNUNET_GenericReturnValue
     934           20 : check_amount_limits (struct UseContext *uc,
     935              :                      const struct TALER_Amount *amount,
     936              :                      const char *detail)
     937              : {
     938           20 :   const struct TALER_MERCHANT_TemplateContract *tc = &uc->template_contract;
     939              :   const struct TALER_Amount *limit;
     940              :   char *msg;
     941              : 
     942           20 :   if (tc->no_min_amount &&
     943           16 :       tc->no_max_amount)
     944           16 :     return GNUNET_OK;
     945              :   /* Parsing the template guarantees both limits to be in the same
     946              :      currency, so it suffices to check @a amount against either one.
     947              :      We must check against a limit (and not merely against the
     948              :      currency of the template) as it is the limits that @a amount is
     949              :      compared to below, and comparing amounts of different currencies
     950              :      fails an assertion. */
     951            8 :   limit = tc->no_min_amount
     952              :           ? &tc->max_amount
     953            4 :           : &tc->min_amount;
     954            4 :   if (GNUNET_YES !=
     955            4 :       TALER_amount_cmp_currency (amount,
     956              :                                  limit))
     957              :   {
     958            0 :     GNUNET_break_op (0);
     959            0 :     use_reply_with_error (uc,
     960              :                           MHD_HTTP_CONFLICT,
     961              :                           TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
     962            0 :                           limit->currency);
     963            0 :     return GNUNET_SYSERR;
     964              :   }
     965            8 :   if ( (! tc->no_min_amount) &&
     966            4 :        (0 > TALER_amount_cmp (amount,
     967              :                               &tc->min_amount)) )
     968              :   {
     969            1 :     GNUNET_break_op (0);
     970            1 :     GNUNET_asprintf (&msg,
     971              :                      "%s is below the min_amount of %s",
     972              :                      detail,
     973              :                      TALER_amount2s (&tc->min_amount));
     974              :   }
     975            6 :   else if ( (! tc->no_max_amount) &&
     976            3 :             (0 < TALER_amount_cmp (amount,
     977              :                                    &tc->max_amount)) )
     978              :   {
     979            1 :     GNUNET_break_op (0);
     980            1 :     GNUNET_asprintf (&msg,
     981              :                      "%s is above the max_amount of %s",
     982              :                      detail,
     983              :                      TALER_amount2s (&tc->max_amount));
     984              :   }
     985              :   else
     986              :   {
     987            2 :     return GNUNET_OK;
     988              :   }
     989            2 :   use_reply_with_error (
     990              :     uc,
     991              :     MHD_HTTP_CONFLICT,
     992              :     TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
     993              :     msg);
     994            2 :   GNUNET_free (msg);
     995            2 :   return GNUNET_SYSERR;
     996              : }
     997              : 
     998              : 
     999              : /**
    1000              :  * Check if the given product ID appears in the array of allowed_products.
    1001              :  *
    1002              :  * @param allowed_products JSON array of product IDs allowed by the template, may be NULL
    1003              :  * @param product_id product ID to check
    1004              :  * @return true if the product ID is in the list
    1005              :  */
    1006              : static bool
    1007           20 : product_id_allowed (const json_t *allowed_products,
    1008              :                     const char *product_id)
    1009              : {
    1010              :   const json_t *entry;
    1011              :   size_t idx;
    1012              : 
    1013           20 :   if (NULL == allowed_products)
    1014            0 :     return false;
    1015           30 :   json_array_foreach ((json_t *) allowed_products, idx, entry)
    1016              :   {
    1017           26 :     if (! json_is_string (entry))
    1018              :     {
    1019            0 :       GNUNET_break (0);
    1020            0 :       continue;
    1021              :     }
    1022           26 :     if (0 == strcmp (json_string_value (entry),
    1023              :                      product_id))
    1024           16 :       return true;
    1025              :   }
    1026            4 :   return false;
    1027              : }
    1028              : 
    1029              : 
    1030              : /**
    1031              :  * Check if any product category is in the selected_categories list.
    1032              :  *
    1033              :  * @param allowed_categories JSON array of categories allowed by the template, may be NULL
    1034              :  * @param num_categories length of @a categories
    1035              :  * @param categories list of categories of the selected product
    1036              :  * @return true if any category of the product is in the list of allowed categories matches
    1037              :  */
    1038              : static bool
    1039            4 : category_allowed (const json_t *allowed_categories,
    1040              :                   size_t num_categories,
    1041              :                   const uint64_t categories[num_categories])
    1042            4 : {
    1043              :   const json_t *entry;
    1044              :   size_t idx;
    1045              : 
    1046            4 :   if (NULL == allowed_categories)
    1047            2 :     return false;
    1048            2 :   json_array_foreach ((json_t *) allowed_categories,
    1049              :                       idx,
    1050              :                       entry)
    1051              :   {
    1052              :     uint64_t selected_id;
    1053              : 
    1054            2 :     if (! json_is_integer (entry))
    1055              :     {
    1056            0 :       GNUNET_break (0);
    1057            0 :       continue;
    1058              :     }
    1059            2 :     if (0 > json_integer_value (entry))
    1060              :     {
    1061            0 :       GNUNET_break (0);
    1062            0 :       continue;
    1063              :     }
    1064            2 :     selected_id = (uint64_t) json_integer_value (entry);
    1065            2 :     for (size_t i = 0; i < num_categories; i++)
    1066              :     {
    1067            2 :       if (categories[i] == selected_id)
    1068            2 :         return true;
    1069              :     }
    1070              :   }
    1071            0 :   return false;
    1072              : }
    1073              : 
    1074              : 
    1075              : /**
    1076              :  * Verify request data for inventory templates.
    1077              :  * Checks that the selected products are allowed
    1078              :  * for this template.
    1079              :  *
    1080              :  * @param[in,out] uc use context
    1081              :  * @return #GNUNET_OK on success
    1082              :  */
    1083              : static enum GNUNET_GenericReturnValue
    1084           18 : verify_using_templates_inventory (struct UseContext *uc)
    1085              : {
    1086           18 :   if (uc->template_contract.details.inventory.choose_one &&
    1087           10 :       (1 != uc->parse_request.inventory.items_len))
    1088              :   {
    1089            4 :     GNUNET_break_op (0);
    1090            4 :     use_reply_with_error (uc,
    1091              :                           MHD_HTTP_CONFLICT,
    1092              :                           TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1093              :                           "inventory_selection");
    1094            4 :     return GNUNET_SYSERR;
    1095              :   }
    1096           14 :   if (uc->template_contract.details.inventory.selected_all)
    1097            0 :     return GNUNET_OK;
    1098           14 :   for (unsigned int i = 0;
    1099           34 :        i < uc->parse_request.inventory.items_len;
    1100           20 :        i++)
    1101              :   {
    1102           20 :     struct InventoryTemplateItemContext *item =
    1103           20 :       &uc->parse_request.inventory.items[i];
    1104           20 :     const char *eparam = NULL;
    1105              : 
    1106           20 :     if (GNUNET_OK !=
    1107           20 :         TALER_MERCHANT_vk_process_quantity_inputs (
    1108              :           TALER_MERCHANT_VK_QUANTITY,
    1109           20 :           item->pd.allow_fractional_quantity,
    1110              :           true,
    1111              :           0,
    1112              :           false,
    1113              :           item->unit_quantity,
    1114              :           &item->quantity_value,
    1115              :           &item->quantity_frac,
    1116              :           &eparam))
    1117              :     {
    1118            0 :       GNUNET_break_op (0);
    1119            0 :       use_reply_with_error (uc,
    1120              :                             MHD_HTTP_BAD_REQUEST,
    1121              :                             TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1122              :                             eparam);
    1123            0 :       return GNUNET_SYSERR;
    1124              :     }
    1125              : 
    1126           20 :     if (0 == item->pd.price_array_length)
    1127              :     {
    1128            0 :       GNUNET_break (0);
    1129            0 :       use_reply_with_error (uc,
    1130              :                             MHD_HTTP_INTERNAL_SERVER_ERROR,
    1131              :                             TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    1132              :                             "price_array");
    1133            0 :       return GNUNET_SYSERR;
    1134              :     }
    1135              : 
    1136              :     /* The line-total computation multiplies the unit price by the
    1137              :        integer quantity using TALER_amount_multiply(), whose factor
    1138              :        argument is only a uint32_t.  A quantity that does not fit into
    1139              :        32 bits would be silently truncated by the (uint32_t) cast in
    1140              :        compute_line_total() (e.g. quantity == 2^32 truncates to 0),
    1141              :        producing a wrong -- and attacker-controllable, much too low --
    1142              :        price.  Reject such quantities rather than truncate them. */
    1143           20 :     if (item->quantity_value > UINT32_MAX)
    1144              :     {
    1145            0 :       GNUNET_break_op (0);
    1146            0 :       use_reply_with_error (uc,
    1147              :                             MHD_HTTP_BAD_REQUEST,
    1148              :                             TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1149              :                             "unit_quantity");
    1150            0 :       return GNUNET_SYSERR;
    1151              :     }
    1152              :   }
    1153              : 
    1154           14 :   for (unsigned int i = 0;
    1155           32 :        i < uc->parse_request.inventory.items_len;
    1156           18 :        i++)
    1157              :   {
    1158           20 :     struct InventoryTemplateItemContext *item =
    1159           20 :       &uc->parse_request.inventory.items[i];
    1160              : 
    1161           20 :     if (product_id_allowed (uc->template_contract.details.inventory.
    1162              :                             selected_products,
    1163              :                             item->product_id))
    1164           16 :       continue;
    1165            4 :     if (category_allowed (
    1166              :           uc->template_contract.details.inventory.selected_categories,
    1167              :           item->num_categories,
    1168            4 :           item->categories))
    1169            2 :       continue;
    1170            2 :     GNUNET_break_op (0);
    1171            2 :     use_reply_with_error (
    1172              :       uc,
    1173              :       MHD_HTTP_CONFLICT,
    1174              :       TALER_EC_MERCHANT_POST_USING_TEMPLATES_WRONG_PRODUCT,
    1175              :       item->product_id);
    1176            2 :     return GNUNET_SYSERR;
    1177              :   }
    1178           12 :   return GNUNET_OK;
    1179              : }
    1180              : 
    1181              : 
    1182              : /**
    1183              :  * Verify request data for fixed-order templates.
    1184              :  * As here we cannot compute the total amount, either
    1185              :  * the template or the client request must provide it.
    1186              :  *
    1187              :  * @param[in,out] uc use context
    1188              :  * @return #GNUNET_OK on success
    1189              :  */
    1190              : static enum GNUNET_GenericReturnValue
    1191           14 : verify_using_templates_fixed (
    1192              :   struct UseContext *uc)
    1193              : {
    1194           14 :   if ( (! uc->parse_request.no_amount) &&
    1195            8 :        (! uc->template_contract.no_amount) )
    1196              :   {
    1197            4 :     GNUNET_break_op (0);
    1198            4 :     use_reply_with_error (uc,
    1199              :                           MHD_HTTP_CONFLICT,
    1200              :                           TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
    1201              :                           NULL);
    1202            4 :     return GNUNET_SYSERR;
    1203              :   }
    1204           10 :   if (uc->parse_request.no_amount &&
    1205            6 :       uc->template_contract.no_amount)
    1206              :   {
    1207            2 :     GNUNET_break_op (0);
    1208            2 :     use_reply_with_error (uc,
    1209              :                           MHD_HTTP_CONFLICT,
    1210              :                           TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_AMOUNT,
    1211              :                           NULL);
    1212            2 :     return GNUNET_SYSERR;
    1213              :   }
    1214            8 :   return GNUNET_OK;
    1215              : }
    1216              : 
    1217              : 
    1218              : /**
    1219              :  * Turn a plain @e amount given by the client into an entry in the
    1220              :  * @e choice_amounts array.  This shorthand is what the generic
    1221              :  * template flow (``editable_defaults`` and ``taler://pay-template``
    1222              :  * URIs) can express, and thus only works if the template has exactly
    1223              :  * one choice with an editable amount.
    1224              :  *
    1225              :  * @param[in,out] uc use context
    1226              :  * @return #GNUNET_OK on success
    1227              :  */
    1228              : static enum GNUNET_GenericReturnValue
    1229            4 : resolve_paivana_amount_shorthand (
    1230              :   struct UseContext *uc)
    1231              : {
    1232            4 :   const struct TALER_MERCHANT_TemplateContractPaivana *tcp
    1233              :     = &uc->template_contract.details.paivana;
    1234            4 :   struct PaivanaChoiceAmount ca = {
    1235              :     .amount = uc->parse_request.amount
    1236              :   };
    1237            4 :   unsigned int editable = 0;
    1238              : 
    1239            8 :   for (unsigned int i = 0; i < tcp->choices_len; i++)
    1240              :   {
    1241            4 :     if (! tcp->choices[i].editable_amount)
    1242            1 :       continue;
    1243            3 :     ca.choice_index = i;
    1244            3 :     editable++;
    1245              :   }
    1246            4 :   if (0 == editable)
    1247              :   {
    1248            1 :     GNUNET_break_op (0);
    1249            1 :     use_reply_with_error (
    1250              :       uc,
    1251              :       MHD_HTTP_CONFLICT,
    1252              :       TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
    1253              :       "template does not allow the amount to be edited");
    1254            1 :     return GNUNET_SYSERR;
    1255              :   }
    1256            3 :   if (1 != editable)
    1257              :   {
    1258            0 :     GNUNET_break_op (0);
    1259            0 :     use_reply_with_error (
    1260              :       uc,
    1261              :       MHD_HTTP_CONFLICT,
    1262              :       TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
    1263              :       "template has more than one editable choice, use choice_amounts");
    1264            0 :     return GNUNET_SYSERR;
    1265              :   }
    1266            3 :   GNUNET_array_append (uc->parse_request.paivana.choice_amounts,
    1267              :                        uc->parse_request.paivana.choice_amounts_len,
    1268              :                        ca);
    1269            3 :   return GNUNET_OK;
    1270              : }
    1271              : 
    1272              : 
    1273              : /**
    1274              :  * Verify request data for paivana templates.
    1275              :  *
    1276              :  * @param[in,out] uc use context
    1277              :  * @return #GNUNET_OK on success
    1278              :  */
    1279              : static enum GNUNET_GenericReturnValue
    1280            7 : verify_using_templates_paivana (
    1281              :   struct UseContext *uc)
    1282              : {
    1283            7 :   const struct TALER_MERCHANT_TemplateContractPaivana *tcp
    1284              :     = &uc->template_contract.details.paivana;
    1285              : 
    1286           11 :   if ( (! uc->parse_request.no_amount) &&
    1287              :        (GNUNET_OK !=
    1288            4 :         resolve_paivana_amount_shorthand (uc)) )
    1289            1 :     return GNUNET_SYSERR;
    1290            6 :   for (unsigned int i = 0;
    1291            8 :        i < uc->parse_request.paivana.choice_amounts_len;
    1292            2 :        i++)
    1293              :   {
    1294            5 :     const struct PaivanaChoiceAmount *ca
    1295            5 :       = &uc->parse_request.paivana.choice_amounts[i];
    1296              :     const struct TALER_MERCHANT_OrderChoice *choice;
    1297              : 
    1298            5 :     if (ca->choice_index >= tcp->choices_len)
    1299              :     {
    1300            1 :       GNUNET_break_op (0);
    1301            1 :       use_reply_with_error (uc,
    1302              :                             MHD_HTTP_BAD_REQUEST,
    1303              :                             TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1304              :                             "choice_amounts::choice_index out of range");
    1305            1 :       return GNUNET_SYSERR;
    1306              :     }
    1307            4 :     choice = &tcp->choices[ca->choice_index];
    1308            4 :     if (! choice->editable_amount)
    1309              :     {
    1310            0 :       GNUNET_break_op (0);
    1311            0 :       use_reply_with_error (
    1312              :         uc,
    1313              :         MHD_HTTP_CONFLICT,
    1314              :         TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
    1315              :         "selected choice has a fixed amount");
    1316            0 :       return GNUNET_SYSERR;
    1317              :     }
    1318              :     /* The currency is baked into the choice (say via 'max_fee'),
    1319              :        so the client may only change the value, not the currency. */
    1320            4 :     if (GNUNET_YES !=
    1321            4 :         TALER_amount_cmp_currency (&ca->amount,
    1322              :                                    &choice->amount))
    1323              :     {
    1324            0 :       GNUNET_break_op (0);
    1325            0 :       use_reply_with_error (uc,
    1326              :                             MHD_HTTP_CONFLICT,
    1327              :                             TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
    1328            0 :                             choice->amount.currency);
    1329            0 :       return GNUNET_SYSERR;
    1330              :     }
    1331            4 :     if (GNUNET_OK !=
    1332            4 :         check_amount_limits (uc,
    1333              :                              &ca->amount,
    1334              :                              "amount of the selected choice"))
    1335            2 :       return GNUNET_SYSERR;
    1336              :   }
    1337            3 :   if (NULL != uc->template_contract.details.paivana.website_regex)
    1338              :   {
    1339              :     regex_t ex;
    1340            0 :     bool allowed = false;
    1341              : 
    1342            0 :     if (0 != regcomp (&ex,
    1343              :                       uc->template_contract.details.paivana.website_regex,
    1344              :                       REG_NOSUB | REG_EXTENDED))
    1345              :     {
    1346            0 :       GNUNET_break_op (0);
    1347            0 :       return GNUNET_SYSERR;
    1348              :     }
    1349            0 :     if (0 ==
    1350            0 :         regexec (&ex,
    1351              :                  uc->parse_request.paivana.website,
    1352              :                  0, NULL,
    1353              :                  0))
    1354              :     {
    1355            0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1356              :                   "Website `%s' allowed by template\n",
    1357              :                   uc->parse_request.paivana.website);
    1358            0 :       allowed = true;
    1359              :     }
    1360            0 :     regfree (&ex);
    1361            0 :     if (! allowed)
    1362              :     {
    1363            0 :       GNUNET_break_op (0);
    1364            0 :       use_reply_with_error (uc,
    1365              :                             MHD_HTTP_BAD_REQUEST,
    1366              :                             TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1367              :                             "website_regex");
    1368            0 :       return GNUNET_SYSERR;
    1369              :     }
    1370              :   }
    1371            3 :   return GNUNET_OK;
    1372              : }
    1373              : 
    1374              : 
    1375              : /**
    1376              :  * Verify that the client request is structurally acceptable for the specified
    1377              :  * template.  Does NOT check the total amount being reasonable.
    1378              :  *
    1379              :  * @param[in,out] uc use context
    1380              :  */
    1381              : static void
    1382           41 : handle_phase_verify (
    1383              :   struct UseContext *uc)
    1384              : {
    1385           41 :   enum GNUNET_GenericReturnValue res = GNUNET_SYSERR;
    1386              : 
    1387           41 :   if ( (NULL != uc->parse_request.summary) &&
    1388           10 :        (NULL != uc->template_contract.summary) )
    1389              :   {
    1390            0 :     GNUNET_break_op (0);
    1391            0 :     use_reply_with_error (uc,
    1392              :                           MHD_HTTP_CONFLICT,
    1393              :                           TALER_EC_MERCHANT_POST_USING_TEMPLATES_SUMMARY_CONFLICT_TEMPLATES_CONTRACT_SUBJECT,
    1394              :                           NULL);
    1395            0 :     return;
    1396              :   }
    1397           41 :   if ( (NULL == uc->parse_request.summary) &&
    1398           31 :        (NULL == uc->template_contract.summary) )
    1399              :   {
    1400            2 :     GNUNET_break_op (0);
    1401            2 :     use_reply_with_error (uc,
    1402              :                           MHD_HTTP_CONFLICT,
    1403              :                           TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_SUMMARY,
    1404              :                           NULL);
    1405            2 :     return;
    1406              :   }
    1407           39 :   if ( (! uc->parse_request.no_amount) &&
    1408           30 :        (NULL != uc->template_contract.currency) &&
    1409            3 :        (0 != strcasecmp (uc->template_contract.currency,
    1410            3 :                          uc->parse_request.amount.currency)) )
    1411              :   {
    1412            0 :     GNUNET_break_op (0);
    1413            0 :     use_reply_with_error (uc,
    1414              :                           MHD_HTTP_CONFLICT,
    1415              :                           TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
    1416              :                           uc->template_contract.currency);
    1417            0 :     return;
    1418              :   }
    1419           39 :   switch (uc->template_type)
    1420              :   {
    1421           14 :   case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
    1422           14 :     res = verify_using_templates_fixed (uc);
    1423           14 :     break;
    1424            7 :   case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
    1425            7 :     res = verify_using_templates_paivana (uc);
    1426            7 :     break;
    1427           18 :   case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
    1428           18 :     res = verify_using_templates_inventory (uc);
    1429           18 :     break;
    1430            0 :   case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
    1431            0 :     GNUNET_assert (0);
    1432              :   }
    1433           39 :   if (GNUNET_OK == res)
    1434           23 :     uc->phase++;
    1435              : }
    1436              : 
    1437              : 
    1438              : /* ***************** USE_PHASE_COMPUTE_PRICE **************** */
    1439              : 
    1440              : 
    1441              : /**
    1442              :  * Find the amount the client picked for the choice at @a choice_index
    1443              :  * of a paivana template.
    1444              :  *
    1445              :  * @param uc use context
    1446              :  * @param choice_index index into the choices of the template contract
    1447              :  * @return NULL if the client did not pick an amount for this choice,
    1448              :  *         in which case the amount from the template applies
    1449              :  */
    1450              : static const struct TALER_Amount *
    1451            3 : find_paivana_choice_amount (const struct UseContext *uc,
    1452              :                             unsigned int choice_index)
    1453              : {
    1454            3 :   for (unsigned int i = 0;
    1455            3 :        i < uc->parse_request.paivana.choice_amounts_len;
    1456            0 :        i++)
    1457              :   {
    1458            2 :     const struct PaivanaChoiceAmount *ca
    1459            2 :       = &uc->parse_request.paivana.choice_amounts[i];
    1460              : 
    1461            2 :     if (choice_index == ca->choice_index)
    1462            2 :       return &ca->amount;
    1463              :   }
    1464            1 :   return NULL;
    1465              : }
    1466              : 
    1467              : 
    1468              : /**
    1469              :  * Compute the line total for a product based on quantity.
    1470              :  *
    1471              :  * @param unit_price price per unit
    1472              :  * @param quantity integer quantity
    1473              :  * @param quantity_frac fractional quantity (0..TALER_MERCHANT_UNIT_FRAC_BASE-1)
    1474              :  * @param[out] line_total resulting line total
    1475              :  * @return #GNUNET_OK on success
    1476              :  */
    1477              : static enum GNUNET_GenericReturnValue
    1478           18 : compute_line_total (const struct TALER_Amount *unit_price,
    1479              :                     uint64_t quantity,
    1480              :                     uint32_t quantity_frac,
    1481              :                     struct TALER_Amount *line_total)
    1482              : {
    1483              :   struct TALER_Amount tmp;
    1484              : 
    1485           18 :   GNUNET_assert (GNUNET_OK ==
    1486              :                  TALER_amount_set_zero (unit_price->currency,
    1487              :                                         line_total));
    1488           34 :   if ( (0 != quantity) &&
    1489              :        (0 >
    1490           16 :         TALER_amount_multiply (line_total,
    1491              :                                unit_price,
    1492              :                                (uint32_t) quantity)) )
    1493              :   {
    1494            0 :     GNUNET_break (0);
    1495            0 :     return GNUNET_SYSERR;
    1496              :   }
    1497           18 :   if (0 == quantity_frac)
    1498           16 :     return GNUNET_OK;
    1499            2 :   if (0 >
    1500            2 :       TALER_amount_multiply (&tmp,
    1501              :                              unit_price,
    1502              :                              quantity_frac))
    1503              :   {
    1504            0 :     GNUNET_break (0);
    1505            0 :     return GNUNET_SYSERR;
    1506              :   }
    1507            2 :   TALER_amount_divide (&tmp,
    1508              :                        &tmp,
    1509              :                        TALER_MERCHANT_UNIT_FRAC_BASE);
    1510            2 :   if (0 >
    1511            2 :       TALER_amount_add (line_total,
    1512              :                         line_total,
    1513              :                         &tmp))
    1514              :   {
    1515            0 :     GNUNET_break (0);
    1516            0 :     return GNUNET_SYSERR;
    1517              :   }
    1518            2 :   return GNUNET_OK;
    1519              : }
    1520              : 
    1521              : 
    1522              : /**
    1523              :  * Find the price of the given @a item in the specified
    1524              :  * @a currency.
    1525              :  *
    1526              :  * @param currency currency to search price in
    1527              :  * @param item item to check prices of
    1528              :  * @return NULL if a suitable price was not found
    1529              :  */
    1530              : static const struct TALER_Amount *
    1531           18 : find_item_price_in_currency (
    1532              :   const char *currency,
    1533              :   const struct InventoryTemplateItemContext *item)
    1534              : {
    1535           18 :   for (size_t j = 0; j < item->pd.price_array_length; j++)
    1536              :   {
    1537           18 :     if (0 == strcasecmp (item->pd.price_array[j].currency,
    1538              :                          currency))
    1539           18 :       return &item->pd.price_array[j];
    1540              :   }
    1541            0 :   return NULL;
    1542              : }
    1543              : 
    1544              : 
    1545              : /**
    1546              :  * Compute totals for all currencies shared across selected products.
    1547              :  *
    1548              :  * @param[in,out] uc use context
    1549              :  * @return #GNUNET_OK on success (including no price due to no items)
    1550              :  *         #GNUNET_NO if we could not find a price in any accepted currency
    1551              :  *                    for all selected products
    1552              :  *         #GNUNET_SYSERR on arithmetic issues (internal error)
    1553              :  */
    1554              : static enum GNUNET_GenericReturnValue
    1555            0 : compute_totals_per_currency (struct UseContext *uc)
    1556              : {
    1557            0 :   const struct InventoryTemplateItemContext *items
    1558              :     = uc->parse_request.inventory.items;
    1559            0 :   unsigned int items_len = uc->parse_request.inventory.items_len;
    1560              : 
    1561            0 :   if (0 == items_len)
    1562            0 :     return GNUNET_NO;
    1563            0 :   for (size_t i = 0; i < items[0].pd.price_array_length; i++)
    1564              :   {
    1565            0 :     const struct TALER_Amount *price
    1566            0 :       = &items[0].pd.price_array[i];
    1567              :     struct TALER_Amount zero;
    1568              : 
    1569            0 :     if (! TMH_test_exchange_configured_for_currency (price->currency))
    1570            0 :       continue;
    1571            0 :     GNUNET_assert (GNUNET_OK ==
    1572              :                    TALER_amount_set_zero (price->currency,
    1573              :                                           &zero));
    1574            0 :     GNUNET_array_append (uc->compute_price.totals,
    1575              :                          uc->compute_price.totals_len,
    1576              :                          zero);
    1577              :   }
    1578            0 :   if (0 == uc->compute_price.totals_len)
    1579              :   {
    1580            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1581              :                 "No currency supported by our configuration in which we have prices for first selected product!\n");
    1582            0 :     return GNUNET_NO;
    1583              :   }
    1584              :   /* Loop through items, ensure each currency exists and sum totals. */
    1585            0 :   for (unsigned int i = 0; i < items_len; i++)
    1586              :   {
    1587            0 :     const struct InventoryTemplateItemContext *item = &items[i];
    1588            0 :     unsigned int c = 0;
    1589              : 
    1590            0 :     while (c < uc->compute_price.totals_len)
    1591              :     {
    1592            0 :       struct TALER_Amount *total = &uc->compute_price.totals[c];
    1593              :       const struct TALER_Amount *unit_price;
    1594              :       struct TALER_Amount line_total;
    1595              : 
    1596            0 :       unit_price = find_item_price_in_currency (total->currency,
    1597              :                                                 item);
    1598            0 :       if (NULL == unit_price)
    1599              :       {
    1600              :         /* Drop the currency: we have no price in one of
    1601              :            the selected products */
    1602            0 :         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1603              :                     "Product `%s' has no price in %s: dropping currency\n",
    1604              :                     item->product_id,
    1605              :                     total->currency);
    1606            0 :         *total = uc->compute_price.totals[--uc->compute_price.totals_len];
    1607            0 :         continue;
    1608              :       }
    1609            0 :       if (GNUNET_OK !=
    1610            0 :           compute_line_total (unit_price,
    1611            0 :                               item->quantity_value,
    1612            0 :                               item->quantity_frac,
    1613              :                               &line_total))
    1614              :       {
    1615            0 :         GNUNET_break (0);
    1616            0 :         return GNUNET_SYSERR;
    1617              :       }
    1618            0 :       if (0 >
    1619            0 :           TALER_amount_add (total,
    1620              :                             total,
    1621              :                             &line_total))
    1622              :       {
    1623            0 :         GNUNET_break (0);
    1624            0 :         return GNUNET_SYSERR;
    1625              :       }
    1626            0 :       c++;
    1627              :     }
    1628              :   }
    1629            0 :   if (0 == uc->compute_price.totals_len)
    1630              :   {
    1631            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1632              :                 "No currency available in which we have prices for all selected products!\n");
    1633            0 :     GNUNET_free (uc->compute_price.totals);
    1634              :   }
    1635            0 :   return (0 == uc->compute_price.totals_len)
    1636              :     ? GNUNET_NO
    1637            0 :     : GNUNET_OK;
    1638              : }
    1639              : 
    1640              : 
    1641              : /**
    1642              :  * Compute total for only the given @a currency.
    1643              :  *
    1644              :  * @param items_len length of @a items
    1645              :  * @param items inventory items
    1646              :  * @param currency currency to total
    1647              :  * @param[out] total computed total
    1648              :  * @return #GNUNET_OK on success
    1649              :  *         #GNUNET_NO if we could not find a price in any accepted currency
    1650              :  *                    for all selected products
    1651              :  *         #GNUNET_SYSERR on arithmetic issues (internal error)
    1652              :  */
    1653              : static enum GNUNET_GenericReturnValue
    1654           12 : compute_inventory_total (unsigned int items_len,
    1655              :                          const struct InventoryTemplateItemContext *items,
    1656              :                          const char *currency,
    1657              :                          struct TALER_Amount *total)
    1658              : {
    1659           12 :   GNUNET_assert (NULL != currency);
    1660           12 :   GNUNET_assert (GNUNET_OK ==
    1661              :                  TALER_amount_set_zero (currency,
    1662              :                                         total));
    1663           30 :   for (unsigned int i = 0; i < items_len; i++)
    1664              :   {
    1665           18 :     const struct InventoryTemplateItemContext *item = &items[i];
    1666              :     const struct TALER_Amount *unit_price;
    1667              :     struct TALER_Amount line_total;
    1668              : 
    1669           18 :     unit_price = find_item_price_in_currency (currency,
    1670              :                                               item);
    1671           18 :     if (NULL == unit_price)
    1672              :     {
    1673            0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1674              :                   "compute_inventory_total: no price in %s for product `%s'\n",
    1675              :                   currency,
    1676              :                   item->product_id);
    1677            0 :       return GNUNET_NO;
    1678              :     }
    1679           18 :     if (GNUNET_OK !=
    1680           18 :         compute_line_total (unit_price,
    1681           18 :                             item->quantity_value,
    1682           18 :                             item->quantity_frac,
    1683              :                             &line_total))
    1684              :     {
    1685            0 :       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    1686              :                   "compute_inventory_total: line total failed for %s in %s\n",
    1687              :                   item->product_id,
    1688              :                   currency);
    1689            0 :       return GNUNET_SYSERR;
    1690              :     }
    1691           18 :     if (0 >
    1692           18 :         TALER_amount_add (total,
    1693              :                           total,
    1694              :                           &line_total))
    1695              :     {
    1696            0 :       GNUNET_break (0);
    1697            0 :       return GNUNET_SYSERR;
    1698              :     }
    1699              :   }
    1700           12 :   return GNUNET_OK;
    1701              : }
    1702              : 
    1703              : 
    1704              : /**
    1705              :  * Compute total price.
    1706              :  *
    1707              :  * @param[in,out] uc use context
    1708              :  */
    1709              : static void
    1710           23 : handle_phase_compute_price (struct UseContext *uc)
    1711              : {
    1712              :   const char *primary_currency;
    1713              :   enum GNUNET_GenericReturnValue ret;
    1714              : 
    1715           23 :   switch (uc->template_type)
    1716              :   {
    1717            8 :   case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
    1718              :     uc->compute_price.totals
    1719            8 :       = GNUNET_new (struct TALER_Amount);
    1720              :     uc->compute_price.totals_len
    1721            8 :       = 1;
    1722            8 :     if (uc->parse_request.no_amount)
    1723              :     {
    1724            4 :       GNUNET_assert (! uc->template_contract.no_amount);
    1725            4 :       *uc->compute_price.totals
    1726            4 :         = uc->template_contract.amount;
    1727              :     }
    1728              :     else
    1729              :     {
    1730            4 :       GNUNET_assert (uc->template_contract.no_amount);
    1731            4 :       *uc->compute_price.totals
    1732            4 :         = uc->parse_request.amount;
    1733            4 :       if (! uc->parse_request.no_tip)
    1734              :       {
    1735              :         /* Per the API specification, the client's 'amount' is "the
    1736              :            amount to be paid, including tip", while the total we compute
    1737              :            in this phase excludes the tip: #handle_phase_check_tip()
    1738              :            adds it back on top.  Without removing it here, the total
    1739              :            would end up being 'amount' + 'tip' and could thus never
    1740              :            match the 'amount' that #handle_phase_check_total() compares
    1741              :            it against. */
    1742            0 :         if (GNUNET_YES !=
    1743            0 :             TALER_amount_cmp_currency (&uc->parse_request.tip,
    1744            0 :                                        uc->compute_price.totals))
    1745              :         {
    1746            0 :           GNUNET_break_op (0);
    1747            0 :           use_reply_with_error (uc,
    1748              :                                 MHD_HTTP_CONFLICT,
    1749              :                                 TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
    1750            0 :                                 uc->parse_request.tip.currency);
    1751            0 :           return;
    1752              :         }
    1753            0 :         if (0 >
    1754            0 :             TALER_amount_subtract (uc->compute_price.totals,
    1755            0 :                                    uc->compute_price.totals,
    1756            0 :                                    &uc->parse_request.tip))
    1757              :         {
    1758            0 :           GNUNET_break_op (0);
    1759            0 :           use_reply_with_error (
    1760              :             uc,
    1761              :             MHD_HTTP_CONFLICT,
    1762              :             TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
    1763              :             "tip exceeds amount");
    1764            0 :           return;
    1765              :         }
    1766              :       }
    1767              :       /* Only an amount the client chose is subject to the limits. */
    1768            4 :       if (GNUNET_OK !=
    1769            4 :           check_amount_limits (uc,
    1770            4 :                                uc->compute_price.totals,
    1771              :                                "amount"))
    1772            0 :         return;
    1773              :     }
    1774            8 :     uc->phase++;
    1775            8 :     return;
    1776           12 :   case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
    1777              :     /* handled below */
    1778           12 :     break;
    1779            3 :   case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
    1780              :     {
    1781            3 :       const struct TALER_MERCHANT_TemplateContractPaivana *tcp
    1782              :         = &uc->template_contract.details.paivana;
    1783              :       json_t *choices;
    1784              : 
    1785            3 :       choices = json_array ();
    1786            3 :       GNUNET_assert (NULL != choices);
    1787            6 :       for (size_t i = 0; i < tcp->choices_len; i++)
    1788              :       {
    1789              :         /* Make deep copy, we're going to MODIFY it! */
    1790            3 :         struct TALER_MERCHANT_OrderChoice choice
    1791            3 :           = tcp->choices[i];
    1792              :         const struct TALER_Amount *ca;
    1793              : 
    1794            3 :         ca = find_paivana_choice_amount (uc,
    1795              :                                          i);
    1796            3 :         if (NULL != ca)
    1797            2 :           choice.amount = *ca;
    1798            3 :         choice.no_tip = uc->parse_request.no_tip;
    1799            3 :         if (! uc->parse_request.no_tip)
    1800              :         {
    1801            2 :           if (GNUNET_YES !=
    1802            2 :               TALER_amount_cmp_currency (&choice.amount,
    1803            2 :                                          &uc->parse_request.tip))
    1804            0 :             continue; /* tip does not match choice currency */
    1805            2 :           choice.tip = uc->parse_request.tip;
    1806            2 :           if (0 >
    1807            2 :               TALER_amount_add (&choice.amount,
    1808              :                                 &choice.amount,
    1809            2 :                                 &uc->parse_request.tip))
    1810              :           {
    1811            0 :             GNUNET_break (0);
    1812            0 :             use_reply_with_error (uc,
    1813              :                                   MHD_HTTP_INTERNAL_SERVER_ERROR,
    1814              :                                   TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
    1815              :                                   "tip");
    1816            0 :             return;
    1817              :           }
    1818              :         }
    1819            3 :         GNUNET_assert (0 ==
    1820              :                        json_array_append_new (
    1821              :                          choices,
    1822              :                          TALER_MERCHANT_json_from_order_choice (&choice)));
    1823              :       }
    1824            3 :       if (0 == json_array_size (choices))
    1825              :       {
    1826            0 :         GNUNET_break_op (0);
    1827            0 :         json_decref (choices);
    1828            0 :         use_reply_with_error (uc,
    1829              :                               MHD_HTTP_CONFLICT,
    1830              :                               TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_CURRENCY,
    1831              :                               "tip");
    1832            0 :         return;
    1833              :       }
    1834            3 :       uc->compute_price.choices = choices;
    1835              :     }
    1836              :     /* Note: we already did the tip and pricing
    1837              :        fully here, so we skip these phases. */
    1838            3 :     uc->phase = USE_PHASE_CREATE_ORDER;
    1839            3 :     return;
    1840            0 :   case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
    1841            0 :     GNUNET_assert (0);
    1842              :   }
    1843           12 :   primary_currency = uc->template_contract.currency;
    1844           12 :   if (! uc->parse_request.no_tip)
    1845            4 :     primary_currency = uc->parse_request.tip.currency;
    1846              :   /* An 'amount' given by the client takes precedence over the currency
    1847              :      of the 'tip': the total we compute here is later compared against
    1848              :      that 'amount' in #handle_phase_check_total(), which requires both
    1849              :      to be in the same currency.  If the tip then uses a different
    1850              :      currency, #handle_phase_check_tip() rejects the request with a
    1851              :      proper error instead. */
    1852           12 :   if (! uc->parse_request.no_amount)
    1853           12 :     primary_currency = uc->parse_request.amount.currency;
    1854           12 :   if (NULL == primary_currency)
    1855              :   {
    1856            0 :     ret = compute_totals_per_currency (uc);
    1857              :   }
    1858              :   else
    1859              :   {
    1860              :     uc->compute_price.totals
    1861           12 :       = GNUNET_new (struct TALER_Amount);
    1862              :     uc->compute_price.totals_len
    1863           12 :       = 1;
    1864           12 :     ret = compute_inventory_total (
    1865              :       uc->parse_request.inventory.items_len,
    1866           12 :       uc->parse_request.inventory.items,
    1867              :       primary_currency,
    1868              :       uc->compute_price.totals);
    1869              :   }
    1870           12 :   if (GNUNET_SYSERR == ret)
    1871              :   {
    1872            0 :     use_reply_with_error (
    1873              :       uc,
    1874              :       MHD_HTTP_INTERNAL_SERVER_ERROR,
    1875              :       TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
    1876              :       "calculation of currency totals failed");
    1877            0 :     return;
    1878              :   }
    1879           12 :   if (GNUNET_NO == ret)
    1880              :   {
    1881            0 :     use_reply_with_error (uc,
    1882              :                           MHD_HTTP_CONFLICT,
    1883              :                           TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_CURRENCY,
    1884              :                           NULL);
    1885            0 :     return;
    1886              :   }
    1887              :   /* The client picked the products and quantities, so the
    1888              :      resulting total is subject to the limits. */
    1889           12 :   for (unsigned int i = 0;
    1890           24 :        i < uc->compute_price.totals_len;
    1891           12 :        i++)
    1892              :   {
    1893           12 :     if (GNUNET_OK !=
    1894           12 :         check_amount_limits (uc,
    1895           12 :                              &uc->compute_price.totals[i],
    1896              :                              "total of the selected products"))
    1897            0 :       return;
    1898              :   }
    1899              : 
    1900           12 :   uc->phase++;
    1901              : }
    1902              : 
    1903              : 
    1904              : /* ***************** USE_PHASE_CHECK_TIP **************** */
    1905              : 
    1906              : 
    1907              : /**
    1908              :  * Check that tip specified is reasonable and add to total.
    1909              :  *
    1910              :  * @param[in,out] uc use context
    1911              :  */
    1912              : static void
    1913           20 : handle_phase_check_tip (struct UseContext *uc)
    1914              : {
    1915              :   struct TALER_Amount *total_amount;
    1916              : 
    1917           20 :   if (uc->parse_request.no_tip)
    1918              :   {
    1919           16 :     uc->phase++;
    1920           16 :     return;
    1921              :   }
    1922            4 :   if (0 == uc->compute_price.totals_len)
    1923              :   {
    1924            0 :     if (! TMH_test_exchange_configured_for_currency (
    1925            0 :           uc->parse_request.tip.currency))
    1926              :     {
    1927            0 :       GNUNET_break_op (0);
    1928            0 :       use_reply_with_error (uc,
    1929              :                             MHD_HTTP_CONFLICT,
    1930              :                             TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
    1931              :                             "Tip currency is not supported by backend");
    1932            0 :       return;
    1933              :     }
    1934              :     uc->compute_price.totals
    1935            0 :       = GNUNET_new (struct TALER_Amount);
    1936              :     uc->compute_price.totals_len
    1937            0 :       = 1;
    1938            0 :     *uc->compute_price.totals
    1939            0 :       = uc->parse_request.tip;
    1940            0 :     uc->phase++;
    1941            0 :     return;
    1942              :   }
    1943            4 :   GNUNET_assert (1 == uc->compute_price.totals_len);
    1944            4 :   total_amount = &uc->compute_price.totals[0];
    1945            4 :   if (GNUNET_YES !=
    1946            4 :       TALER_amount_cmp_currency (&uc->parse_request.tip,
    1947              :                                  total_amount))
    1948              :   {
    1949            0 :     GNUNET_break_op (0);
    1950            0 :     use_reply_with_error (uc,
    1951              :                           MHD_HTTP_CONFLICT,
    1952              :                           TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
    1953            0 :                           uc->parse_request.tip.currency);
    1954            0 :     return;
    1955              :   }
    1956            4 :   if (0 >
    1957            4 :       TALER_amount_add (total_amount,
    1958              :                         total_amount,
    1959            4 :                         &uc->parse_request.tip))
    1960              :   {
    1961            0 :     GNUNET_break (0);
    1962            0 :     use_reply_with_error (uc,
    1963              :                           MHD_HTTP_INTERNAL_SERVER_ERROR,
    1964              :                           TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
    1965              :                           "tip");
    1966            0 :     return;
    1967              :   }
    1968            4 :   uc->phase++;
    1969              : }
    1970              : 
    1971              : 
    1972              : /* ***************** USE_PHASE_CHECK_TOTAL **************** */
    1973              : 
    1974              : /**
    1975              :  * Check that if the client specified a total,
    1976              :  * it matches our own calculation.
    1977              :  *
    1978              :  * @param[in,out] uc use context
    1979              :  */
    1980              : static void
    1981           20 : handle_phase_check_total (struct UseContext *uc)
    1982              : {
    1983           20 :   GNUNET_assert (1 <= uc->compute_price.totals_len);
    1984           20 :   if (! uc->parse_request.no_amount)
    1985              :   {
    1986           16 :     GNUNET_assert (1 == uc->compute_price.totals_len);
    1987           16 :     if (GNUNET_YES !=
    1988           16 :         TALER_amount_cmp_currency (&uc->parse_request.amount,
    1989           16 :                                    &uc->compute_price.totals[0]))
    1990              :     {
    1991              :       /* Must not be an assertion: the currency of the total we computed
    1992              :          is influenced by the client (via 'tip' and the selected
    1993              :          products), so a mismatch here is remotely triggerable. */
    1994            0 :       GNUNET_break_op (0);
    1995            0 :       use_reply_with_error (uc,
    1996              :                             MHD_HTTP_CONFLICT,
    1997              :                             TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
    1998            0 :                             uc->compute_price.totals[0].currency);
    1999            0 :       return;
    2000              :     }
    2001           16 :     if (0 !=
    2002           16 :         TALER_amount_cmp (&uc->parse_request.amount,
    2003           16 :                           &uc->compute_price.totals[0]))
    2004              :     {
    2005            4 :       GNUNET_break_op (0);
    2006            4 :       use_reply_with_error (uc,
    2007              :                             MHD_HTTP_CONFLICT,
    2008              :                             TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
    2009            4 :                             TALER_amount2s (&uc->compute_price.totals[0]));
    2010            4 :       return;
    2011              :     }
    2012              :   }
    2013           16 :   uc->phase++;
    2014              : }
    2015              : 
    2016              : 
    2017              : /* ***************** USE_PHASE_CREATE_ORDER **************** */
    2018              : 
    2019              : 
    2020              : /**
    2021              :  * Create order request for inventory templates.
    2022              :  *
    2023              :  * @param[in,out] uc use context
    2024              :  */
    2025              : static void
    2026            8 : create_using_templates_inventory (struct UseContext *uc)
    2027              : {
    2028              :   json_t *inventory_products;
    2029              :   json_t *choices;
    2030              : 
    2031            8 :   inventory_products = json_array ();
    2032            8 :   GNUNET_assert (NULL != inventory_products);
    2033            8 :   for (unsigned int i = 0;
    2034           20 :        i < uc->parse_request.inventory.items_len;
    2035           12 :        i++)
    2036              :   {
    2037           12 :     const struct InventoryTemplateItemContext *item =
    2038           12 :       &uc->parse_request.inventory.items[i];
    2039              : 
    2040           12 :     GNUNET_assert (0 ==
    2041              :                    json_array_append_new (
    2042              :                      inventory_products,
    2043              :                      GNUNET_JSON_PACK (
    2044              :                        GNUNET_JSON_pack_string ("product_id",
    2045              :                                                 item->product_id),
    2046              :                        GNUNET_JSON_pack_string ("unit_quantity",
    2047              :                                                 item->unit_quantity))));
    2048              :   }
    2049            8 :   choices = json_array ();
    2050            8 :   GNUNET_assert (NULL != choices);
    2051            8 :   for (unsigned int i = 0;
    2052           16 :        i < uc->compute_price.totals_len;
    2053            8 :        i++)
    2054              :   {
    2055            8 :     GNUNET_assert (0 ==
    2056              :                    json_array_append_new (
    2057              :                      choices,
    2058              :                      GNUNET_JSON_PACK (
    2059              :                        TALER_JSON_pack_amount ("amount",
    2060              :                                                &uc->compute_price.totals[i]),
    2061              :                        GNUNET_JSON_pack_allow_null (
    2062              :                          TALER_JSON_pack_amount ("tip",
    2063              :                                                  uc->parse_request.no_tip
    2064              :                                                 ? NULL
    2065              :                                                 : &uc->parse_request.tip))
    2066              :                        )));
    2067              :   }
    2068              : 
    2069              :   uc->ihc.request_body
    2070            8 :     = GNUNET_JSON_PACK (
    2071              :         GNUNET_JSON_pack_allow_null (
    2072              :           GNUNET_JSON_pack_string ("otp_id",
    2073              :                                    uc->lookup_template.etp.otp_id)),
    2074              :         GNUNET_JSON_pack_array_steal ("inventory_products",
    2075              :                                       inventory_products),
    2076              :         GNUNET_JSON_pack_object_steal (
    2077              :           "order",
    2078              :           GNUNET_JSON_PACK (
    2079              :             GNUNET_JSON_pack_uint64 ("version",
    2080              :                                      1),
    2081              :             GNUNET_JSON_pack_array_steal ("choices",
    2082              :                                           choices),
    2083              :             GNUNET_JSON_pack_string ("summary",
    2084              :                                      NULL == uc->parse_request.summary
    2085              :                                    ? uc->template_contract.summary
    2086              :                                    : uc->parse_request.summary))));
    2087            8 :   if (! GNUNET_TIME_relative_is_forever (
    2088              :         uc->template_contract.max_pickup_duration))
    2089              :   {
    2090            0 :     GNUNET_assert (
    2091              :       0 ==
    2092              :       json_object_set_new (
    2093              :         uc->ihc.request_body,
    2094              :         "max_pickup_time",
    2095              :         GNUNET_JSON_from_timestamp (
    2096              :           GNUNET_TIME_absolute_to_timestamp (
    2097              :             GNUNET_TIME_relative_to_absolute (
    2098              :               uc->template_contract.max_pickup_duration)))));
    2099              :   }
    2100            8 : }
    2101              : 
    2102              : 
    2103              : /**
    2104              :  * Create order request for fixed-order templates.
    2105              :  *
    2106              :  * @param[in,out] uc use context
    2107              :  */
    2108              : static void
    2109            8 : create_using_templates_fixed (struct UseContext *uc)
    2110              : {
    2111              :   uc->ihc.request_body
    2112            8 :     = GNUNET_JSON_PACK (
    2113              :         GNUNET_JSON_pack_allow_null (
    2114              :           GNUNET_JSON_pack_string ("otp_id",
    2115              :                                    uc->lookup_template.etp.otp_id)),
    2116              :         GNUNET_JSON_pack_object_steal (
    2117              :           "order",
    2118              :           GNUNET_JSON_PACK (
    2119              :             TALER_JSON_pack_amount (
    2120              :               "amount",
    2121              :               &uc->compute_price.totals[0]),
    2122              :             GNUNET_JSON_pack_allow_null (
    2123              :               TALER_JSON_pack_amount ("tip",
    2124              :                                       uc->parse_request.no_tip
    2125              :                                       ? NULL
    2126              :                                       : &uc->parse_request.tip)),
    2127              :             GNUNET_JSON_pack_string (
    2128              :               "summary",
    2129              :               NULL == uc->parse_request.summary
    2130              :             ? uc->template_contract.summary
    2131              :             : uc->parse_request.summary))));
    2132            8 : }
    2133              : 
    2134              : 
    2135              : /**
    2136              :  * Create order request for paivana templates.
    2137              :  *
    2138              :  * @param[in,out] uc use context
    2139              :  */
    2140              : static void
    2141            3 : create_using_templates_paivana (struct UseContext *uc)
    2142              : {
    2143              :   uc->ihc.request_body
    2144            3 :     = GNUNET_JSON_PACK (
    2145              :         GNUNET_JSON_pack_string (
    2146              :           "session_id",
    2147              :           uc->parse_request.paivana.paivana_id),
    2148              :         GNUNET_JSON_pack_object_steal (
    2149              :           "order",
    2150              :           GNUNET_JSON_PACK (
    2151              :             GNUNET_JSON_pack_uint64 ("version",
    2152              :                                      1),
    2153              :             GNUNET_JSON_pack_array_incref ("choices",
    2154              :                                            uc->compute_price.choices),
    2155              :             GNUNET_JSON_pack_string (
    2156              :               "summary",
    2157              :               NULL == uc->parse_request.summary
    2158              :               ? uc->template_contract.summary
    2159              :               : uc->parse_request.summary),
    2160              :             GNUNET_JSON_pack_string ("fulfillment_url",
    2161              :                                      uc->parse_request.paivana.website))));
    2162            3 : }
    2163              : 
    2164              : 
    2165              : static void
    2166           19 : handle_phase_create_order (struct UseContext *uc)
    2167              : {
    2168              :   json_t *order;
    2169              : 
    2170           19 :   GNUNET_assert (NULL == uc->ihc.request_body);
    2171           19 :   switch (uc->template_type)
    2172              :   {
    2173            8 :   case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
    2174            8 :     create_using_templates_fixed (uc);
    2175            8 :     break;
    2176            3 :   case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
    2177            3 :     create_using_templates_paivana (uc);
    2178            3 :     break;
    2179            8 :   case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
    2180            8 :     create_using_templates_inventory (uc);
    2181            8 :     break;
    2182            0 :   case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
    2183            0 :     GNUNET_assert (0);
    2184              :   }
    2185           19 :   order = json_object_get (uc->ihc.request_body,
    2186              :                            "order");
    2187           19 :   GNUNET_assert (json_is_object (order));
    2188              :   /* A zero duration means that the template did not specify one.  Leave the
    2189              :      deadline absent in that case so the regular order handler applies the
    2190              :      instance default.  Older backends accepted FOREVER; also use the instance
    2191              :      default for such legacy templates instead of generating a forbidden
    2192              :      "never" deadline. */
    2193           19 :   if (GNUNET_TIME_relative_is_forever (
    2194              :         uc->template_contract.pay_duration))
    2195              :   {
    2196            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    2197              :                 "Template uses invalid infinite pay duration; "
    2198              :                 "using instance default\n");
    2199              :   }
    2200           19 :   else if (! GNUNET_TIME_relative_is_zero (
    2201              :              uc->template_contract.pay_duration))
    2202              :   {
    2203           12 :     GNUNET_assert (
    2204              :       0 ==
    2205              :       json_object_set_new (
    2206              :         order,
    2207              :         "pay_deadline",
    2208              :         GNUNET_JSON_from_timestamp (
    2209              :           GNUNET_TIME_relative_to_timestamp (
    2210              :             uc->template_contract.pay_duration))));
    2211              :   }
    2212           19 :   uc->phase++;
    2213           19 : }
    2214              : 
    2215              : 
    2216              : /* ***************** Main handler **************** */
    2217              : 
    2218              : enum MHD_Result
    2219           65 : TMH_post_using_templates_ID (
    2220              :   const struct TMH_RequestHandler *rh,
    2221              :   struct MHD_Connection *connection,
    2222              :   struct TMH_HandlerContext *hc)
    2223              : {
    2224           65 :   struct UseContext *uc = hc->ctx;
    2225              : 
    2226              :   (void) rh;
    2227           65 :   if (NULL == uc)
    2228              :   {
    2229           46 :     uc = GNUNET_new (struct UseContext);
    2230           46 :     uc->hc = hc;
    2231           46 :     hc->ctx = uc;
    2232           46 :     hc->cc = &cleanup_use_context;
    2233           46 :     uc->ihc.instance = hc->instance;
    2234           46 :     uc->phase = USE_PHASE_PARSE_REQUEST;
    2235           46 :     uc->template_type = TALER_MERCHANT_TEMPLATE_TYPE_INVALID;
    2236              :   }
    2237              : 
    2238              :   while (1)
    2239              :   {
    2240          365 :     switch (uc->phase)
    2241              :     {
    2242           46 :     case USE_PHASE_PARSE_REQUEST:
    2243           46 :       handle_phase_parse_request (uc);
    2244           46 :       break;
    2245           45 :     case USE_PHASE_LOOKUP_TEMPLATE:
    2246           45 :       handle_phase_lookup_template (uc);
    2247           45 :       break;
    2248           43 :     case USE_PHASE_PARSE_TEMPLATE:
    2249           43 :       handle_phase_template_contract (uc);
    2250           43 :       break;
    2251           43 :     case USE_PHASE_DB_FETCH:
    2252           43 :       handle_phase_db_fetch (uc);
    2253           43 :       break;
    2254           41 :     case USE_PHASE_VERIFY:
    2255           41 :       handle_phase_verify (uc);
    2256           41 :       break;
    2257           23 :     case USE_PHASE_COMPUTE_PRICE:
    2258           23 :       handle_phase_compute_price (uc);
    2259           23 :       break;
    2260           20 :     case USE_PHASE_CHECK_TIP:
    2261           20 :       handle_phase_check_tip (uc);
    2262           20 :       break;
    2263           20 :     case USE_PHASE_CHECK_TOTAL:
    2264           20 :       handle_phase_check_total (uc);
    2265           20 :       break;
    2266           19 :     case USE_PHASE_CREATE_ORDER:
    2267           19 :       handle_phase_create_order (uc);
    2268           19 :       break;
    2269           38 :     case USE_PHASE_SUBMIT_ORDER:
    2270           38 :       return TMH_private_post_orders (
    2271              :         NULL,    /* not even used */
    2272              :         connection,
    2273              :         &uc->ihc);
    2274           27 :     case USE_PHASE_FINISHED_MHD_YES:
    2275           27 :       return MHD_YES;
    2276            0 :     case USE_PHASE_FINISHED_MHD_NO:
    2277            0 :       return MHD_NO;
    2278              :     }
    2279              :   }
    2280              : }
        

Generated by: LCOV version 2.0-1