merchant

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

taler-merchant-httpd_post-templates-TEMPLATE_ID.c (49636B)


      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/lookup_product.h"
     36 #include "merchant-database/lookup_template.h"
     37 
     38 
     39 /**
     40  * Item selected from inventory_selection.
     41  */
     42 struct InventoryTemplateItemContext
     43 {
     44   /**
     45    * Product ID as referenced in inventory.
     46    */
     47   const char *product_id;
     48 
     49   /**
     50    * Unit quantity string as provided by the client.
     51    */
     52   const char *unit_quantity;
     53 
     54   /**
     55    * Parsed integer quantity.
     56    */
     57   uint64_t quantity_value;
     58 
     59   /**
     60    * Parsed fractional quantity.
     61    */
     62   uint32_t quantity_frac;
     63 
     64   /**
     65    * Product details from the DB (includes price array).
     66    */
     67   struct TALER_MERCHANTDB_ProductDetails pd;
     68 
     69   /**
     70    * Categories referenced by the product.
     71    */
     72   uint64_t *categories;
     73 
     74   /**
     75    * Length of @e categories.
     76    */
     77   size_t num_categories;
     78 };
     79 
     80 
     81 /**
     82  * Our context.
     83  */
     84 enum UsePhase
     85 {
     86   /**
     87    * Parse request payload into context fields.
     88    */
     89   USE_PHASE_PARSE_REQUEST,
     90 
     91   /**
     92    * Fetch template details from the database.
     93    */
     94   USE_PHASE_LOOKUP_TEMPLATE,
     95 
     96   /**
     97    * Parse template.
     98    */
     99   USE_PHASE_PARSE_TEMPLATE,
    100 
    101   /**
    102    * Load additional details (like products and
    103    * categories) needed for verification and
    104    * price computation.
    105    */
    106   USE_PHASE_DB_FETCH,
    107 
    108   /**
    109    * Validate request and template compatibility.
    110    */
    111   USE_PHASE_VERIFY,
    112 
    113   /**
    114    * Compute price of the order.
    115    */
    116   USE_PHASE_COMPUTE_PRICE,
    117 
    118   /**
    119    * Handle tip.
    120    */
    121   USE_PHASE_CHECK_TIP,
    122 
    123   /**
    124    * Check if client-supplied total amount matches
    125    * our calculation (if we did any).
    126    */
    127   USE_PHASE_CHECK_TOTAL,
    128 
    129   /**
    130    * Construct the internal order request body.
    131    */
    132   USE_PHASE_CREATE_ORDER,
    133 
    134   /**
    135    * Submit the order to the shared order handler.
    136    */
    137   USE_PHASE_SUBMIT_ORDER,
    138 
    139   /**
    140    * Finished successfully with MHD_YES.
    141    */
    142   USE_PHASE_FINISHED_MHD_YES,
    143 
    144   /**
    145    * Finished with MHD_NO.
    146    */
    147   USE_PHASE_FINISHED_MHD_NO
    148 };
    149 
    150 struct UseContext
    151 {
    152   /**
    153    * Context for our handler.
    154    */
    155   struct TMH_HandlerContext *hc;
    156 
    157   /**
    158    * Internal handler context we are passing into the
    159    * POST /private/orders handler.
    160    */
    161   struct TMH_HandlerContext ihc;
    162 
    163   /**
    164    * Phase we are currently in.
    165    */
    166   enum UsePhase phase;
    167 
    168   /**
    169    * Template type from the contract.
    170    */
    171   enum TALER_MERCHANT_TemplateType template_type;
    172 
    173   /**
    174    * Information set in the #USE_PHASE_PARSE_REQUEST phase.
    175    */
    176   struct
    177   {
    178     /**
    179      * Summary override from request, if any.
    180      */
    181     const char *summary;
    182 
    183     /**
    184      * Amount provided by the client.
    185      */
    186     struct TALER_Amount amount;
    187 
    188     /**
    189      * Tip provided by the client.
    190      */
    191     struct TALER_Amount tip;
    192 
    193     /**
    194      * True if @e amount was not provided.
    195      */
    196     bool no_amount;
    197 
    198     /**
    199      * True if @e tip was not provided.
    200      */
    201     bool no_tip;
    202 
    203     /**
    204      * Parsed fields for inventory templates.
    205      */
    206     struct
    207     {
    208       /**
    209        * Selected products from inventory_selection.
    210        */
    211       struct InventoryTemplateItemContext *items;
    212 
    213       /**
    214        * Length of @e items.
    215        */
    216       unsigned int items_len;
    217 
    218     } inventory;
    219 
    220     /**
    221      * Request details if this is a paivana instantiation.
    222      */
    223     struct
    224     {
    225 
    226       /**
    227        * Target website for the request.
    228        */
    229       const char *website;
    230 
    231       /**
    232        * Unique client identifier, consisting of
    233        * current time, "-", and the hash of a nonce,
    234        * the website and the current time.
    235        */
    236       const char *paivana_id;
    237 
    238     } paivana;
    239 
    240   } parse_request;
    241 
    242   /**
    243    * Information set in the #USE_PHASE_LOOKUP_TEMPLATE phase.
    244    */
    245   struct
    246   {
    247 
    248     /**
    249      * Our template details from the DB.
    250      */
    251     struct TALER_MERCHANTDB_TemplateDetails etp;
    252 
    253   } lookup_template;
    254 
    255   /**
    256    * Information set in the #USE_PHASE_PARSE_TEMPLATE phase.
    257    */
    258   struct TALER_MERCHANT_TemplateContract template_contract;
    259 
    260   /**
    261    * Information set in the #USE_PHASE_COMPUTE_PRICE phase.
    262    */
    263   struct
    264   {
    265 
    266     /**
    267      * Per-currency totals across selected products (without tips).
    268      */
    269     struct TALER_Amount *totals;
    270 
    271     /**
    272      * Length of @e totals.
    273      */
    274     unsigned int totals_len;
    275 
    276     /**
    277      * Array of payment choices, used with Paviana.
    278      */
    279     json_t *choices;
    280 
    281   } compute_price;
    282 
    283 };
    284 
    285 
    286 /**
    287  * Clean up inventory items.
    288  *
    289  * @param items_len length of @a items
    290  * @param[in] items item array to free
    291  */
    292 static void
    293 cleanup_inventory_items (unsigned int items_len,
    294                          struct InventoryTemplateItemContext items[static
    295                                                                    items_len])
    296 {
    297   for (unsigned int i = 0; i < items_len; i++)
    298   {
    299     struct InventoryTemplateItemContext *item = &items[i];
    300 
    301     TALER_MERCHANTDB_product_details_free (&item->pd);
    302     GNUNET_free (item->categories);
    303   }
    304   GNUNET_free (items);
    305 }
    306 
    307 
    308 /**
    309  * Clean up a `struct UseContext *`
    310  *
    311  * @param[in] cls a `struct UseContext *`
    312  */
    313 static void
    314 cleanup_use_context (void *cls)
    315 {
    316   struct UseContext *uc = cls;
    317 
    318   TALER_MERCHANTDB_template_details_free (&uc->lookup_template.etp);
    319   if (NULL !=
    320       uc->parse_request.inventory.items)
    321     cleanup_inventory_items (uc->parse_request.inventory.items_len,
    322                              uc->parse_request.inventory.items);
    323   GNUNET_free (uc->compute_price.totals);
    324   uc->compute_price.totals_len = 0;
    325   json_decref (uc->compute_price.choices);
    326   if (NULL != uc->ihc.cc)
    327     uc->ihc.cc (uc->ihc.ctx);
    328   GNUNET_free (uc->ihc.infix);
    329   json_decref (uc->ihc.request_body);
    330   GNUNET_free (uc);
    331 }
    332 
    333 
    334 /**
    335  * Finalize a template use request.
    336  *
    337  * @param[in,out] uc use context
    338  * @param ret handler return value
    339  */
    340 static void
    341 use_finalize (struct UseContext *uc,
    342               enum MHD_Result ret)
    343 {
    344   uc->phase = (MHD_YES == ret)
    345     ? USE_PHASE_FINISHED_MHD_YES
    346     : USE_PHASE_FINISHED_MHD_NO;
    347 }
    348 
    349 
    350 /**
    351  * Finalize after JSON parsing result.
    352  *
    353  * @param[in,out] uc use context
    354  * @param res parse result
    355  */
    356 static void
    357 use_finalize_parse (struct UseContext *uc,
    358                     enum GNUNET_GenericReturnValue res)
    359 {
    360   GNUNET_assert (GNUNET_OK != res);
    361   use_finalize (uc,
    362                 (GNUNET_NO == res)
    363                 ? MHD_YES
    364                 : MHD_NO);
    365 }
    366 
    367 
    368 /**
    369  * Reply with error and finalize the request.
    370  *
    371  * @param[in,out] uc use context
    372  * @param http_status HTTP status code
    373  * @param ec error code
    374  * @param detail error detail
    375  */
    376 static void
    377 use_reply_with_error (struct UseContext *uc,
    378                       unsigned int http_status,
    379                       enum TALER_ErrorCode ec,
    380                       const char *detail)
    381 {
    382   enum MHD_Result mret;
    383 
    384   mret = TALER_MHD_reply_with_error (uc->hc->connection,
    385                                      http_status,
    386                                      ec,
    387                                      detail);
    388   use_finalize (uc,
    389                 mret);
    390 }
    391 
    392 
    393 /* ***************** USE_PHASE_PARSE_REQUEST **************** */
    394 
    395 /**
    396  * Parse request data for inventory templates.
    397  *
    398  * @param[in,out] uc use context
    399  * @return #GNUNET_OK on success
    400  */
    401 static enum GNUNET_GenericReturnValue
    402 parse_using_templates_inventory_request (
    403   struct UseContext *uc)
    404 {
    405   const json_t *inventory_selection;
    406   struct GNUNET_JSON_Specification spec[] = {
    407     GNUNET_JSON_spec_array_const ("inventory_selection",
    408                                   &inventory_selection),
    409     GNUNET_JSON_spec_end ()
    410   };
    411   enum GNUNET_GenericReturnValue res;
    412 
    413   GNUNET_assert (NULL == uc->ihc.request_body);
    414   res = TALER_MHD_parse_json_data (uc->hc->connection,
    415                                    uc->hc->request_body,
    416                                    spec);
    417   if (GNUNET_OK != res)
    418   {
    419     GNUNET_break_op (0);
    420     use_finalize_parse (uc,
    421                         res);
    422     return GNUNET_SYSERR;
    423   }
    424 
    425   if ( (! uc->parse_request.no_amount) &&
    426        (! TMH_test_exchange_configured_for_currency (
    427           uc->parse_request.amount.currency)) )
    428   {
    429     GNUNET_break_op (0);
    430     use_reply_with_error (uc,
    431                           MHD_HTTP_CONFLICT,
    432                           TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
    433                           "Currency is not supported by backend");
    434     return GNUNET_SYSERR;
    435   }
    436 
    437   for (size_t i = 0; i < json_array_size (inventory_selection); i++)
    438   {
    439     struct InventoryTemplateItemContext item = { 0 };
    440     struct GNUNET_JSON_Specification ispec[] = {
    441       GNUNET_JSON_spec_string ("product_id",
    442                                &item.product_id),
    443       GNUNET_JSON_spec_string ("quantity",
    444                                &item.unit_quantity),
    445       GNUNET_JSON_spec_end ()
    446     };
    447     const char *err_name;
    448     unsigned int err_line;
    449 
    450     res = GNUNET_JSON_parse (json_array_get (inventory_selection,
    451                                              i),
    452                              ispec,
    453                              &err_name,
    454                              &err_line);
    455     if (GNUNET_OK != res)
    456     {
    457       GNUNET_break_op (0);
    458       use_reply_with_error (uc,
    459                             MHD_HTTP_BAD_REQUEST,
    460                             TALER_EC_GENERIC_PARAMETER_MALFORMED,
    461                             "inventory_selection");
    462       return GNUNET_SYSERR;
    463     }
    464 
    465     GNUNET_array_append (uc->parse_request.inventory.items,
    466                          uc->parse_request.inventory.items_len,
    467                          item);
    468   }
    469   return GNUNET_OK;
    470 }
    471 
    472 
    473 /**
    474  * Parse request data for paivana templates.
    475  *
    476  * @param[in,out] uc use context
    477  * @return #GNUNET_OK on success
    478  */
    479 static enum GNUNET_GenericReturnValue
    480 parse_using_templates_paivana_request (
    481   struct UseContext *uc)
    482 {
    483   struct GNUNET_JSON_Specification spec[] = {
    484     GNUNET_JSON_spec_string ("website",
    485                              &uc->parse_request.paivana.website),
    486     GNUNET_JSON_spec_string ("paivana_id",
    487                              &uc->parse_request.paivana.paivana_id),
    488     GNUNET_JSON_spec_end ()
    489   };
    490   enum GNUNET_GenericReturnValue res;
    491   unsigned long long tv;
    492   const char *dash;
    493 
    494   GNUNET_assert (NULL == uc->ihc.request_body);
    495   res = TALER_MHD_parse_json_data (uc->hc->connection,
    496                                    uc->hc->request_body,
    497                                    spec);
    498   if (GNUNET_OK != res)
    499   {
    500     GNUNET_break_op (0);
    501     json_dumpf (uc->hc->request_body,
    502                 stderr,
    503                 JSON_INDENT (2));
    504     use_finalize_parse (uc,
    505                         res);
    506     return GNUNET_SYSERR;
    507   }
    508   if (1 !=
    509       sscanf (uc->parse_request.paivana.paivana_id,
    510               "%llu-",
    511               &tv))
    512   {
    513     GNUNET_break_op (0);
    514     use_reply_with_error (uc,
    515                           MHD_HTTP_BAD_REQUEST,
    516                           TALER_EC_GENERIC_PARAMETER_MALFORMED,
    517                           "paivana_id");
    518     return GNUNET_SYSERR;
    519   }
    520   dash = strchr (uc->parse_request.paivana.paivana_id,
    521                  '-');
    522   GNUNET_assert (NULL != dash);
    523   {
    524     size_t olen;
    525     void *out = NULL;
    526 
    527     olen = GNUNET_STRINGS_base64url_decode (dash + 1,
    528                                             strlen (dash + 1),
    529                                             &out);
    530     GNUNET_free (out);
    531     if (sizeof (struct GNUNET_ShortHashCode) != olen)
    532     {
    533       GNUNET_break_op (0);
    534       use_reply_with_error (uc,
    535                             MHD_HTTP_BAD_REQUEST,
    536                             TALER_EC_GENERIC_PARAMETER_MALFORMED,
    537                             "paivana_id");
    538       return GNUNET_SYSERR;
    539     }
    540   }
    541   return GNUNET_OK;
    542 }
    543 
    544 
    545 /**
    546  * Main function for the #USE_PHASE_PARSE_REQUEST.
    547  *
    548  * @param[in,out] uc context to update
    549  */
    550 static void
    551 handle_phase_parse_request (
    552   struct UseContext *uc)
    553 {
    554   const char *template_type = NULL;
    555   struct GNUNET_JSON_Specification spec[] = {
    556     GNUNET_JSON_spec_mark_optional (
    557       GNUNET_JSON_spec_string ("template_type",
    558                                &template_type),
    559       NULL),
    560     GNUNET_JSON_spec_mark_optional (
    561       TALER_JSON_spec_amount_any ("tip",
    562                                   &uc->parse_request.tip),
    563       &uc->parse_request.no_tip),
    564     GNUNET_JSON_spec_mark_optional (
    565       GNUNET_JSON_spec_string ("summary",
    566                                &uc->parse_request.summary),
    567       NULL),
    568     GNUNET_JSON_spec_mark_optional (
    569       TALER_JSON_spec_amount_any ("amount",
    570                                   &uc->parse_request.amount),
    571       &uc->parse_request.no_amount),
    572     GNUNET_JSON_spec_end ()
    573   };
    574   enum GNUNET_GenericReturnValue res;
    575 
    576   res = TALER_MHD_parse_json_data (uc->hc->connection,
    577                                    uc->hc->request_body,
    578                                    spec);
    579   if (GNUNET_OK != res)
    580   {
    581     GNUNET_break_op (0);
    582     use_finalize_parse (uc,
    583                         res);
    584     return;
    585   }
    586   if (NULL == template_type)
    587     template_type = "fixed-order";
    588   uc->template_type
    589     = TALER_MERCHANT_template_type_from_string (
    590         template_type);
    591   switch (uc->template_type)
    592   {
    593   case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
    594     /* nothig left to do */
    595     uc->phase++;
    596     return;
    597   case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
    598     res = parse_using_templates_paivana_request (uc);
    599     if (GNUNET_OK == res)
    600       uc->phase++;
    601     return;
    602   case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
    603     res = parse_using_templates_inventory_request (uc);
    604     if (GNUNET_OK == res)
    605       uc->phase++;
    606     return;
    607   case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
    608     break;
    609   }
    610   GNUNET_break (0);
    611   use_reply_with_error (
    612     uc,
    613     MHD_HTTP_BAD_REQUEST,
    614     TALER_EC_GENERIC_PARAMETER_MALFORMED,
    615     "template_type");
    616 }
    617 
    618 
    619 /* ***************** USE_PHASE_LOOKUP_TEMPLATE **************** */
    620 
    621 /**
    622  * Main function for the #USE_PHASE_LOOKUP_TEMPLATE.
    623  *
    624  * @param[in,out] uc context to update
    625  */
    626 static void
    627 handle_phase_lookup_template (
    628   struct UseContext *uc)
    629 {
    630   struct TMH_MerchantInstance *mi = uc->hc->instance;
    631   const char *template_id = uc->hc->infix;
    632   enum GNUNET_DB_QueryStatus qs;
    633 
    634   qs = TALER_MERCHANTDB_lookup_template (TMH_db,
    635                                          mi->settings.id,
    636                                          template_id,
    637                                          &uc->lookup_template.etp);
    638   switch (qs)
    639   {
    640   case GNUNET_DB_STATUS_HARD_ERROR:
    641     /* Clean up and fail hard */
    642     GNUNET_break (0);
    643     use_reply_with_error (uc,
    644                           MHD_HTTP_INTERNAL_SERVER_ERROR,
    645                           TALER_EC_GENERIC_DB_FETCH_FAILED,
    646                           "lookup_template");
    647     return;
    648   case GNUNET_DB_STATUS_SOFT_ERROR:
    649     /* this should be impossible (single select) */
    650     GNUNET_break (0);
    651     use_reply_with_error (uc,
    652                           MHD_HTTP_INTERNAL_SERVER_ERROR,
    653                           TALER_EC_GENERIC_DB_FETCH_FAILED,
    654                           "lookup_template");
    655     return;
    656   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    657     /* template not found! */
    658     use_reply_with_error (uc,
    659                           MHD_HTTP_NOT_FOUND,
    660                           TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN,
    661                           template_id);
    662     return;
    663   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    664     /* all good */
    665     break;
    666   }
    667   if (uc->template_type !=
    668       TALER_MERCHANT_template_type_from_contract (
    669         uc->lookup_template.etp.template_contract))
    670   {
    671     GNUNET_break_op (0);
    672     use_reply_with_error (
    673       uc,
    674       MHD_HTTP_CONFLICT,
    675       TALER_EC_MERCHANT_POST_USING_TEMPLATES_WRONG_TYPE,
    676       "template_contract has different type");
    677     return;
    678   }
    679   uc->phase++;
    680 }
    681 
    682 
    683 /* ***************** USE_PHASE_PARSE_TEMPLATE **************** */
    684 
    685 
    686 /**
    687  * Parse template.
    688  *
    689  * @param[in,out] uc use context
    690  */
    691 static void
    692 handle_phase_template_contract (struct UseContext *uc)
    693 {
    694   const char *err_name;
    695   enum GNUNET_GenericReturnValue res;
    696 
    697   res = TALER_MERCHANT_template_contract_parse (
    698     uc->lookup_template.etp.template_contract,
    699     &uc->template_contract,
    700     &err_name);
    701   if (GNUNET_OK != res)
    702   {
    703     GNUNET_break (0);
    704     use_reply_with_error (uc,
    705                           MHD_HTTP_INTERNAL_SERVER_ERROR,
    706                           TALER_EC_GENERIC_DB_FETCH_FAILED,
    707                           err_name);
    708     return;
    709   }
    710   uc->phase++;
    711 }
    712 
    713 
    714 /* ***************** USE_PHASE_DB_FETCH **************** */
    715 
    716 /**
    717  * Fetch DB data for inventory templates.
    718  *
    719  * @param[in,out] uc use context
    720  */
    721 static void
    722 handle_phase_db_fetch (struct UseContext *uc)
    723 {
    724   struct TMH_MerchantInstance *mi = uc->hc->instance;
    725 
    726   switch (uc->template_type)
    727   {
    728   case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
    729     uc->phase++;
    730     return;
    731   case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
    732     uc->phase++;
    733     return;
    734   case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
    735     break;
    736   case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
    737     GNUNET_assert (0);
    738   }
    739 
    740   for (unsigned int i = 0;
    741        i < uc->parse_request.inventory.items_len;
    742        i++)
    743   {
    744     struct InventoryTemplateItemContext *item =
    745       &uc->parse_request.inventory.items[i];
    746     enum GNUNET_DB_QueryStatus qs;
    747 
    748     qs = TALER_MERCHANTDB_lookup_product (TMH_db,
    749                                           mi->settings.id,
    750                                           item->product_id,
    751                                           &item->pd,
    752                                           &item->num_categories,
    753                                           &item->categories);
    754     switch (qs)
    755     {
    756     case GNUNET_DB_STATUS_HARD_ERROR:
    757       GNUNET_break (0);
    758       use_reply_with_error (uc,
    759                             MHD_HTTP_INTERNAL_SERVER_ERROR,
    760                             TALER_EC_GENERIC_DB_FETCH_FAILED,
    761                             "lookup_product");
    762       return;
    763     case GNUNET_DB_STATUS_SOFT_ERROR:
    764       GNUNET_break (0);
    765       use_reply_with_error (uc,
    766                             MHD_HTTP_INTERNAL_SERVER_ERROR,
    767                             TALER_EC_GENERIC_DB_FETCH_FAILED,
    768                             "lookup_product");
    769       return;
    770     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    771       use_reply_with_error (uc,
    772                             MHD_HTTP_NOT_FOUND,
    773                             TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN,
    774                             item->product_id);
    775       return;
    776     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    777       break;
    778     }
    779   }
    780   uc->phase++;
    781 }
    782 
    783 
    784 /* *************** Helpers for USE_PHASE_VERIFY ***************** */
    785 
    786 /**
    787  * Check if the given product ID appears in the array of allowed_products.
    788  *
    789  * @param allowed_products JSON array of product IDs allowed by the template, may be NULL
    790  * @param product_id product ID to check
    791  * @return true if the product ID is in the list
    792  */
    793 static bool
    794 product_id_allowed (const json_t *allowed_products,
    795                     const char *product_id)
    796 {
    797   const json_t *entry;
    798   size_t idx;
    799 
    800   if (NULL == allowed_products)
    801     return false;
    802   json_array_foreach ((json_t *) allowed_products, idx, entry)
    803   {
    804     if (! json_is_string (entry))
    805     {
    806       GNUNET_break (0);
    807       continue;
    808     }
    809     if (0 == strcmp (json_string_value (entry),
    810                      product_id))
    811       return true;
    812   }
    813   return false;
    814 }
    815 
    816 
    817 /**
    818  * Check if any product category is in the selected_categories list.
    819  *
    820  * @param allowed_categories JSON array of categories allowed by the template, may be NULL
    821  * @param num_categories length of @a categories
    822  * @param categories list of categories of the selected product
    823  * @return true if any category of the product is in the list of allowed categories matches
    824  */
    825 static bool
    826 category_allowed (const json_t *allowed_categories,
    827                   size_t num_categories,
    828                   const uint64_t categories[num_categories])
    829 {
    830   const json_t *entry;
    831   size_t idx;
    832 
    833   if (NULL == allowed_categories)
    834     return false;
    835   json_array_foreach ((json_t *) allowed_categories,
    836                       idx,
    837                       entry)
    838   {
    839     uint64_t selected_id;
    840 
    841     if (! json_is_integer (entry))
    842     {
    843       GNUNET_break (0);
    844       continue;
    845     }
    846     if (0 > json_integer_value (entry))
    847     {
    848       GNUNET_break (0);
    849       continue;
    850     }
    851     selected_id = (uint64_t) json_integer_value (entry);
    852     for (size_t i = 0; i < num_categories; i++)
    853     {
    854       if (categories[i] == selected_id)
    855         return true;
    856     }
    857   }
    858   return false;
    859 }
    860 
    861 
    862 /**
    863  * Verify request data for inventory templates.
    864  * Checks that the selected products are allowed
    865  * for this template.
    866  *
    867  * @param[in,out] uc use context
    868  * @return #GNUNET_OK on success
    869  */
    870 static enum GNUNET_GenericReturnValue
    871 verify_using_templates_inventory (struct UseContext *uc)
    872 {
    873   if (uc->template_contract.details.inventory.choose_one &&
    874       (1 != uc->parse_request.inventory.items_len))
    875   {
    876     GNUNET_break_op (0);
    877     use_reply_with_error (uc,
    878                           MHD_HTTP_CONFLICT,
    879                           TALER_EC_GENERIC_PARAMETER_MALFORMED,
    880                           "inventory_selection");
    881     return GNUNET_SYSERR;
    882   }
    883   if (uc->template_contract.details.inventory.selected_all)
    884     return GNUNET_OK;
    885   for (unsigned int i = 0;
    886        i < uc->parse_request.inventory.items_len;
    887        i++)
    888   {
    889     struct InventoryTemplateItemContext *item =
    890       &uc->parse_request.inventory.items[i];
    891     const char *eparam = NULL;
    892 
    893     if (GNUNET_OK !=
    894         TALER_MERCHANT_vk_process_quantity_inputs (
    895           TALER_MERCHANT_VK_QUANTITY,
    896           item->pd.allow_fractional_quantity,
    897           true,
    898           0,
    899           false,
    900           item->unit_quantity,
    901           &item->quantity_value,
    902           &item->quantity_frac,
    903           &eparam))
    904     {
    905       GNUNET_break_op (0);
    906       use_reply_with_error (uc,
    907                             MHD_HTTP_BAD_REQUEST,
    908                             TALER_EC_GENERIC_PARAMETER_MALFORMED,
    909                             eparam);
    910       return GNUNET_SYSERR;
    911     }
    912 
    913     if (0 == item->pd.price_array_length)
    914     {
    915       GNUNET_break (0);
    916       use_reply_with_error (uc,
    917                             MHD_HTTP_INTERNAL_SERVER_ERROR,
    918                             TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
    919                             "price_array");
    920       return GNUNET_SYSERR;
    921     }
    922   }
    923 
    924   for (unsigned int i = 0;
    925        i < uc->parse_request.inventory.items_len;
    926        i++)
    927   {
    928     struct InventoryTemplateItemContext *item =
    929       &uc->parse_request.inventory.items[i];
    930 
    931     if (product_id_allowed (uc->template_contract.details.inventory.
    932                             selected_products,
    933                             item->product_id))
    934       continue;
    935     if (category_allowed (
    936           uc->template_contract.details.inventory.selected_categories,
    937           item->num_categories,
    938           item->categories))
    939       continue;
    940     GNUNET_break_op (0);
    941     use_reply_with_error (
    942       uc,
    943       MHD_HTTP_CONFLICT,
    944       TALER_EC_MERCHANT_POST_USING_TEMPLATES_WRONG_PRODUCT,
    945       item->product_id);
    946     return GNUNET_SYSERR;
    947   }
    948   return GNUNET_OK;
    949 }
    950 
    951 
    952 /**
    953  * Verify request data for fixed-order templates.
    954  * As here we cannot compute the total amount, either
    955  * the template or the client request must provide it.
    956  *
    957  * @param[in,out] uc use context
    958  * @return #GNUNET_OK on success
    959  */
    960 static enum GNUNET_GenericReturnValue
    961 verify_using_templates_fixed (
    962   struct UseContext *uc)
    963 {
    964   if ( (! uc->parse_request.no_amount) &&
    965        (! uc->template_contract.no_amount) )
    966   {
    967     GNUNET_break_op (0);
    968     use_reply_with_error (uc,
    969                           MHD_HTTP_CONFLICT,
    970                           TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
    971                           NULL);
    972     return GNUNET_SYSERR;
    973   }
    974   if (uc->parse_request.no_amount &&
    975       uc->template_contract.no_amount)
    976   {
    977     GNUNET_break_op (0);
    978     use_reply_with_error (uc,
    979                           MHD_HTTP_CONFLICT,
    980                           TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_AMOUNT,
    981                           NULL);
    982     return GNUNET_SYSERR;
    983   }
    984   return GNUNET_OK;
    985 }
    986 
    987 
    988 /**
    989  * Verify request data for paivana templates.
    990  *
    991  * @param[in,out] uc use context
    992  * @return #GNUNET_OK on success
    993  */
    994 static enum GNUNET_GenericReturnValue
    995 verify_using_templates_paivana (
    996   struct UseContext *uc)
    997 {
    998   if (NULL != uc->template_contract.details.paivana.website_regex)
    999   {
   1000     regex_t ex;
   1001     bool allowed = false;
   1002 
   1003     if (0 != regcomp (&ex,
   1004                       uc->template_contract.details.paivana.website_regex,
   1005                       REG_NOSUB | REG_EXTENDED))
   1006     {
   1007       GNUNET_break_op (0);
   1008       return GNUNET_SYSERR;
   1009     }
   1010     if (0 ==
   1011         regexec (&ex,
   1012                  uc->parse_request.paivana.website,
   1013                  0, NULL,
   1014                  0))
   1015     {
   1016       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1017                   "Website `%s' allowed by template\n",
   1018                   uc->parse_request.paivana.website);
   1019       allowed = true;
   1020     }
   1021     regfree (&ex);
   1022     if (! allowed)
   1023     {
   1024       GNUNET_break_op (0);
   1025       use_reply_with_error (uc,
   1026                             MHD_HTTP_BAD_REQUEST,
   1027                             TALER_EC_GENERIC_PARAMETER_MALFORMED,
   1028                             "website_regex");
   1029       return GNUNET_SYSERR;
   1030     }
   1031   }
   1032   return GNUNET_OK;
   1033 }
   1034 
   1035 
   1036 /**
   1037  * Verify that the client request is structurally acceptable for the specified
   1038  * template.  Does NOT check the total amount being reasonable.
   1039  *
   1040  * @param[in,out] uc use context
   1041  */
   1042 static void
   1043 handle_phase_verify (
   1044   struct UseContext *uc)
   1045 {
   1046   enum GNUNET_GenericReturnValue res = GNUNET_SYSERR;
   1047 
   1048   if ( (NULL != uc->parse_request.summary) &&
   1049        (NULL != uc->template_contract.summary) )
   1050   {
   1051     GNUNET_break_op (0);
   1052     use_reply_with_error (uc,
   1053                           MHD_HTTP_CONFLICT,
   1054                           TALER_EC_MERCHANT_POST_USING_TEMPLATES_SUMMARY_CONFLICT_TEMPLATES_CONTRACT_SUBJECT,
   1055                           NULL);
   1056     return;
   1057   }
   1058   if ( (NULL == uc->parse_request.summary) &&
   1059        (NULL == uc->template_contract.summary) )
   1060   {
   1061     GNUNET_break_op (0);
   1062     use_reply_with_error (uc,
   1063                           MHD_HTTP_CONFLICT,
   1064                           TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_SUMMARY,
   1065                           NULL);
   1066     return;
   1067   }
   1068   if ( (! uc->parse_request.no_amount) &&
   1069        (NULL != uc->template_contract.currency) &&
   1070        (0 != strcasecmp (uc->template_contract.currency,
   1071                          uc->parse_request.amount.currency)) )
   1072   {
   1073     GNUNET_break_op (0);
   1074     use_reply_with_error (uc,
   1075                           MHD_HTTP_CONFLICT,
   1076                           TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
   1077                           uc->template_contract.currency);
   1078     return;
   1079   }
   1080   switch (uc->template_type)
   1081   {
   1082   case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
   1083     res = verify_using_templates_fixed (uc);
   1084     break;
   1085   case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
   1086     res = verify_using_templates_paivana (uc);
   1087     break;
   1088   case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
   1089     res = verify_using_templates_inventory (uc);
   1090     break;
   1091   case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
   1092     GNUNET_assert (0);
   1093   }
   1094   if (GNUNET_OK == res)
   1095     uc->phase++;
   1096 }
   1097 
   1098 
   1099 /* ***************** USE_PHASE_COMPUTE_PRICE **************** */
   1100 
   1101 
   1102 /**
   1103  * Compute the line total for a product based on quantity.
   1104  *
   1105  * @param unit_price price per unit
   1106  * @param quantity integer quantity
   1107  * @param quantity_frac fractional quantity (0..TALER_MERCHANT_UNIT_FRAC_BASE-1)
   1108  * @param[out] line_total resulting line total
   1109  * @return #GNUNET_OK on success
   1110  */
   1111 static enum GNUNET_GenericReturnValue
   1112 compute_line_total (const struct TALER_Amount *unit_price,
   1113                     uint64_t quantity,
   1114                     uint32_t quantity_frac,
   1115                     struct TALER_Amount *line_total)
   1116 {
   1117   struct TALER_Amount tmp;
   1118 
   1119   GNUNET_assert (GNUNET_OK ==
   1120                  TALER_amount_set_zero (unit_price->currency,
   1121                                         line_total));
   1122   if ( (0 != quantity) &&
   1123        (0 >
   1124         TALER_amount_multiply (line_total,
   1125                                unit_price,
   1126                                (uint32_t) quantity)) )
   1127   {
   1128     GNUNET_break (0);
   1129     return GNUNET_SYSERR;
   1130   }
   1131   if (0 == quantity_frac)
   1132     return GNUNET_OK;
   1133   if (0 >
   1134       TALER_amount_multiply (&tmp,
   1135                              unit_price,
   1136                              quantity_frac))
   1137   {
   1138     GNUNET_break (0);
   1139     return GNUNET_SYSERR;
   1140   }
   1141   TALER_amount_divide (&tmp,
   1142                        &tmp,
   1143                        TALER_MERCHANT_UNIT_FRAC_BASE);
   1144   if (0 >
   1145       TALER_amount_add (line_total,
   1146                         line_total,
   1147                         &tmp))
   1148   {
   1149     GNUNET_break (0);
   1150     return GNUNET_SYSERR;
   1151   }
   1152   return GNUNET_OK;
   1153 }
   1154 
   1155 
   1156 /**
   1157  * Find the price of the given @a item in the specified
   1158  * @a currency.
   1159  *
   1160  * @param currency currency to search price in
   1161  * @param item item to check prices of
   1162  * @return NULL if a suitable price was not found
   1163  */
   1164 static const struct TALER_Amount *
   1165 find_item_price_in_currency (
   1166   const char *currency,
   1167   const struct InventoryTemplateItemContext *item)
   1168 {
   1169   for (size_t j = 0; j < item->pd.price_array_length; j++)
   1170   {
   1171     if (0 == strcasecmp (item->pd.price_array[j].currency,
   1172                          currency))
   1173       return &item->pd.price_array[j];
   1174   }
   1175   return NULL;
   1176 }
   1177 
   1178 
   1179 /**
   1180  * Compute totals for all currencies shared across selected products.
   1181  *
   1182  * @param[in,out] uc use context
   1183  * @return #GNUNET_OK on success (including no price due to no items)
   1184  *         #GNUNET_NO if we could not find a price in any accepted currency
   1185  *                    for all selected products
   1186  *         #GNUNET_SYSERR on arithmetic issues (internal error)
   1187  */
   1188 static enum GNUNET_GenericReturnValue
   1189 compute_totals_per_currency (struct UseContext *uc)
   1190 {
   1191   const struct InventoryTemplateItemContext *items
   1192     = uc->parse_request.inventory.items;
   1193   unsigned int items_len = uc->parse_request.inventory.items_len;
   1194 
   1195   if (0 == items_len)
   1196     return GNUNET_OK;
   1197   for (size_t i = 0; i < items[0].pd.price_array_length; i++)
   1198   {
   1199     const struct TALER_Amount *price
   1200       = &items[0].pd.price_array[i];
   1201     struct TALER_Amount zero;
   1202 
   1203     if (! TMH_test_exchange_configured_for_currency (price->currency))
   1204       continue;
   1205     GNUNET_assert (GNUNET_OK ==
   1206                    TALER_amount_set_zero (price->currency,
   1207                                           &zero));
   1208     GNUNET_array_append (uc->compute_price.totals,
   1209                          uc->compute_price.totals_len,
   1210                          zero);
   1211   }
   1212   if (0 == uc->compute_price.totals_len)
   1213   {
   1214     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1215                 "No currency supported by our configuration in which we have prices for first selected product!\n");
   1216     return GNUNET_NO;
   1217   }
   1218   /* Loop through items, ensure each currency exists and sum totals. */
   1219   for (unsigned int i = 0; i < items_len; i++)
   1220   {
   1221     const struct InventoryTemplateItemContext *item = &items[i];
   1222     unsigned int c = 0;
   1223 
   1224     while (c < uc->compute_price.totals_len)
   1225     {
   1226       struct TALER_Amount *total = &uc->compute_price.totals[c];
   1227       const struct TALER_Amount *unit_price;
   1228       struct TALER_Amount line_total;
   1229 
   1230       unit_price = find_item_price_in_currency (total->currency,
   1231                                                 item);
   1232       if (NULL == unit_price)
   1233       {
   1234         /* Drop the currency: we have no price in one of
   1235            the selected products */
   1236         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1237                     "Product `%s' has no price in %s: dropping currency\n",
   1238                     item->product_id,
   1239                     total->currency);
   1240         *total = uc->compute_price.totals[--uc->compute_price.totals_len];
   1241         continue;
   1242       }
   1243       if (GNUNET_OK !=
   1244           compute_line_total (unit_price,
   1245                               item->quantity_value,
   1246                               item->quantity_frac,
   1247                               &line_total))
   1248       {
   1249         GNUNET_break (0);
   1250         return GNUNET_SYSERR;
   1251       }
   1252       if (0 >
   1253           TALER_amount_add (total,
   1254                             total,
   1255                             &line_total))
   1256       {
   1257         GNUNET_break (0);
   1258         return GNUNET_SYSERR;
   1259       }
   1260       c++;
   1261     }
   1262   }
   1263   if (0 == uc->compute_price.totals_len)
   1264   {
   1265     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1266                 "No currency available in which we have prices for all selected products!\n");
   1267     GNUNET_free (uc->compute_price.totals);
   1268   }
   1269   return (0 == uc->compute_price.totals_len)
   1270     ? GNUNET_NO
   1271     : GNUNET_OK;
   1272 }
   1273 
   1274 
   1275 /**
   1276  * Compute total for only the given @a currency.
   1277  *
   1278  * @param items_len length of @a items
   1279  * @param items inventory items
   1280  * @param currency currency to total
   1281  * @param[out] total computed total
   1282  * @return #GNUNET_OK on success
   1283  *         #GNUNET_NO if we could not find a price in any accepted currency
   1284  *                    for all selected products
   1285  *         #GNUNET_SYSERR on arithmetic issues (internal error)
   1286  */
   1287 static enum GNUNET_GenericReturnValue
   1288 compute_inventory_total (unsigned int items_len,
   1289                          const struct InventoryTemplateItemContext *items,
   1290                          const char *currency,
   1291                          struct TALER_Amount *total)
   1292 {
   1293   GNUNET_assert (NULL != currency);
   1294   GNUNET_assert (GNUNET_OK ==
   1295                  TALER_amount_set_zero (currency,
   1296                                         total));
   1297   for (unsigned int i = 0; i < items_len; i++)
   1298   {
   1299     const struct InventoryTemplateItemContext *item = &items[i];
   1300     const struct TALER_Amount *unit_price;
   1301     struct TALER_Amount line_total;
   1302 
   1303     unit_price = find_item_price_in_currency (currency,
   1304                                               item);
   1305     if (NULL == unit_price)
   1306     {
   1307       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
   1308                   "compute_inventory_total: no price in %s for product `%s'\n",
   1309                   currency,
   1310                   item->product_id);
   1311       return GNUNET_NO;
   1312     }
   1313     if (GNUNET_OK !=
   1314         compute_line_total (unit_price,
   1315                             item->quantity_value,
   1316                             item->quantity_frac,
   1317                             &line_total))
   1318     {
   1319       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
   1320                   "compute_inventory_total: line total failed for %s in %s\n",
   1321                   item->product_id,
   1322                   currency);
   1323       return GNUNET_SYSERR;
   1324     }
   1325     if (0 >
   1326         TALER_amount_add (total,
   1327                           total,
   1328                           &line_total))
   1329     {
   1330       GNUNET_break (0);
   1331       return GNUNET_SYSERR;
   1332     }
   1333   }
   1334   return GNUNET_OK;
   1335 }
   1336 
   1337 
   1338 /**
   1339  * Compute total price.
   1340  *
   1341  * @param[in,out] uc use context
   1342  */
   1343 static void
   1344 handle_phase_compute_price (struct UseContext *uc)
   1345 {
   1346   const char *primary_currency;
   1347   enum GNUNET_GenericReturnValue ret;
   1348 
   1349   switch (uc->template_type)
   1350   {
   1351   case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
   1352     uc->compute_price.totals
   1353       = GNUNET_new (struct TALER_Amount);
   1354     uc->compute_price.totals_len
   1355       = 1;
   1356     if (uc->parse_request.no_amount)
   1357     {
   1358       GNUNET_assert (! uc->template_contract.no_amount);
   1359       *uc->compute_price.totals
   1360         = uc->template_contract.amount;
   1361     }
   1362     else
   1363     {
   1364       GNUNET_assert (uc->template_contract.no_amount);
   1365       *uc->compute_price.totals
   1366         = uc->parse_request.amount;
   1367     }
   1368     uc->phase++;
   1369     return;
   1370   case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
   1371     /* handled below */
   1372     break;
   1373   case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
   1374     {
   1375       const struct TALER_MERCHANT_TemplateContractPaivana *tcp
   1376         = &uc->template_contract.details.paivana;
   1377       json_t *choices;
   1378 
   1379       choices = json_array ();
   1380       GNUNET_assert (NULL != choices);
   1381       for (size_t i = 0; i < tcp->choices_len; i++)
   1382       {
   1383         /* Make deep copy, we're going to MODIFY it! */
   1384         struct TALER_MERCHANT_ContractChoice choice
   1385           = tcp->choices[i];
   1386 
   1387         choice.no_tip = uc->parse_request.no_tip;
   1388         if (! uc->parse_request.no_tip)
   1389         {
   1390           if (GNUNET_YES !=
   1391               TALER_amount_cmp_currency (&choice.amount,
   1392                                          &uc->parse_request.tip))
   1393             continue; /* tip does not match choice currency */
   1394           choice.tip = uc->parse_request.tip;
   1395           if (0 >
   1396               TALER_amount_add (&choice.amount,
   1397                                 &choice.amount,
   1398                                 &uc->parse_request.tip))
   1399           {
   1400             GNUNET_break (0);
   1401             use_reply_with_error (uc,
   1402                                   MHD_HTTP_INTERNAL_SERVER_ERROR,
   1403                                   TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
   1404                                   "tip");
   1405             return;
   1406           }
   1407         }
   1408         GNUNET_assert (0 ==
   1409                        json_array_append_new (
   1410                          choices,
   1411                          TALER_MERCHANT_json_from_contract_choice (&choice,
   1412                                                                    true)));
   1413       }
   1414       if (0 == json_array_size (choices))
   1415       {
   1416         GNUNET_break_op (0);
   1417         json_decref (choices);
   1418         use_reply_with_error (uc,
   1419                               MHD_HTTP_CONFLICT,
   1420                               TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_CURRENCY,
   1421                               "tip");
   1422         return;
   1423       }
   1424       uc->compute_price.choices = choices;
   1425     }
   1426     /* Note: we already did the tip and pricing
   1427        fully here, so we skip these phases. */
   1428     uc->phase = USE_PHASE_CREATE_ORDER;
   1429     return;
   1430   case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
   1431     GNUNET_assert (0);
   1432   }
   1433   primary_currency = uc->template_contract.currency;
   1434   if (! uc->parse_request.no_amount)
   1435     primary_currency = uc->parse_request.amount.currency;
   1436   if (! uc->parse_request.no_tip)
   1437     primary_currency = uc->parse_request.tip.currency;
   1438   if (NULL == primary_currency)
   1439   {
   1440     ret = compute_totals_per_currency (uc);
   1441   }
   1442   else
   1443   {
   1444     uc->compute_price.totals
   1445       = GNUNET_new (struct TALER_Amount);
   1446     uc->compute_price.totals_len
   1447       = 1;
   1448     ret = compute_inventory_total (
   1449       uc->parse_request.inventory.items_len,
   1450       uc->parse_request.inventory.items,
   1451       primary_currency,
   1452       uc->compute_price.totals);
   1453   }
   1454   if (GNUNET_SYSERR == ret)
   1455   {
   1456     use_reply_with_error (
   1457       uc,
   1458       MHD_HTTP_INTERNAL_SERVER_ERROR,
   1459       TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
   1460       "calculation of currency totals failed");
   1461     return;
   1462   }
   1463   if (GNUNET_NO == ret)
   1464   {
   1465     use_reply_with_error (uc,
   1466                           MHD_HTTP_CONFLICT,
   1467                           TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_CURRENCY,
   1468                           NULL);
   1469     return;
   1470   }
   1471 
   1472   uc->phase++;
   1473 }
   1474 
   1475 
   1476 /* ***************** USE_PHASE_CHECK_TIP **************** */
   1477 
   1478 
   1479 /**
   1480  * Check that tip specified is reasonable and add to total.
   1481  *
   1482  * @param[in,out] uc use context
   1483  */
   1484 static void
   1485 handle_phase_check_tip (struct UseContext *uc)
   1486 {
   1487   struct TALER_Amount *total_amount;
   1488 
   1489   if (uc->parse_request.no_tip)
   1490   {
   1491     uc->phase++;
   1492     return;
   1493   }
   1494   if (0 == uc->compute_price.totals_len)
   1495   {
   1496     if (! TMH_test_exchange_configured_for_currency (
   1497           uc->parse_request.tip.currency))
   1498     {
   1499       GNUNET_break_op (0);
   1500       use_reply_with_error (uc,
   1501                             MHD_HTTP_CONFLICT,
   1502                             TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
   1503                             "Tip currency is not supported by backend");
   1504       return;
   1505     }
   1506     uc->compute_price.totals
   1507       = GNUNET_new (struct TALER_Amount);
   1508     uc->compute_price.totals_len
   1509       = 1;
   1510     *uc->compute_price.totals
   1511       = uc->parse_request.tip;
   1512     uc->phase++;
   1513     return;
   1514   }
   1515   GNUNET_assert (1 == uc->compute_price.totals_len);
   1516   total_amount = &uc->compute_price.totals[0];
   1517   if (GNUNET_YES !=
   1518       TALER_amount_cmp_currency (&uc->parse_request.tip,
   1519                                  total_amount))
   1520   {
   1521     GNUNET_break_op (0);
   1522     use_reply_with_error (uc,
   1523                           MHD_HTTP_CONFLICT,
   1524                           TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
   1525                           uc->parse_request.tip.currency);
   1526     return;
   1527   }
   1528   if (0 >
   1529       TALER_amount_add (total_amount,
   1530                         total_amount,
   1531                         &uc->parse_request.tip))
   1532   {
   1533     GNUNET_break (0);
   1534     use_reply_with_error (uc,
   1535                           MHD_HTTP_INTERNAL_SERVER_ERROR,
   1536                           TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
   1537                           "tip");
   1538     return;
   1539   }
   1540   uc->phase++;
   1541 }
   1542 
   1543 
   1544 /* ***************** USE_PHASE_CHECK_TOTAL **************** */
   1545 
   1546 /**
   1547  * Check that if the client specified a total,
   1548  * it matches our own calculation.
   1549  *
   1550  * @param[in,out] uc use context
   1551  */
   1552 static void
   1553 handle_phase_check_total (struct UseContext *uc)
   1554 {
   1555   GNUNET_assert (1 <= uc->compute_price.totals_len);
   1556   if (! uc->parse_request.no_amount)
   1557   {
   1558     GNUNET_assert (1 == uc->compute_price.totals_len);
   1559     GNUNET_assert (GNUNET_YES ==
   1560                    TALER_amount_cmp_currency (&uc->parse_request.amount,
   1561                                               &uc->compute_price.totals[0]));
   1562     if (0 !=
   1563         TALER_amount_cmp (&uc->parse_request.amount,
   1564                           &uc->compute_price.totals[0]))
   1565     {
   1566       GNUNET_break_op (0);
   1567       use_reply_with_error (uc,
   1568                             MHD_HTTP_CONFLICT,
   1569                             TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
   1570                             TALER_amount2s (&uc->compute_price.totals[0]));
   1571       return;
   1572     }
   1573   }
   1574   uc->phase++;
   1575 }
   1576 
   1577 
   1578 /* ***************** USE_PHASE_CREATE_ORDER **************** */
   1579 
   1580 
   1581 /**
   1582  * Create order request for inventory templates.
   1583  *
   1584  * @param[in,out] uc use context
   1585  */
   1586 static void
   1587 create_using_templates_inventory (struct UseContext *uc)
   1588 {
   1589   json_t *inventory_products;
   1590   json_t *choices;
   1591 
   1592   inventory_products = json_array ();
   1593   GNUNET_assert (NULL != inventory_products);
   1594   for (unsigned int i = 0;
   1595        i < uc->parse_request.inventory.items_len;
   1596        i++)
   1597   {
   1598     const struct InventoryTemplateItemContext *item =
   1599       &uc->parse_request.inventory.items[i];
   1600 
   1601     GNUNET_assert (0 ==
   1602                    json_array_append_new (
   1603                      inventory_products,
   1604                      GNUNET_JSON_PACK (
   1605                        GNUNET_JSON_pack_string ("product_id",
   1606                                                 item->product_id),
   1607                        GNUNET_JSON_pack_string ("unit_quantity",
   1608                                                 item->unit_quantity))));
   1609   }
   1610   choices = json_array ();
   1611   GNUNET_assert (NULL != choices);
   1612   for (unsigned int i = 0;
   1613        i < uc->compute_price.totals_len;
   1614        i++)
   1615   {
   1616     GNUNET_assert (0 ==
   1617                    json_array_append_new (
   1618                      choices,
   1619                      GNUNET_JSON_PACK (
   1620                        TALER_JSON_pack_amount ("amount",
   1621                                                &uc->compute_price.totals[i]),
   1622                        GNUNET_JSON_pack_allow_null (
   1623                          TALER_JSON_pack_amount ("tip",
   1624                                                  uc->parse_request.no_tip
   1625                                                 ? NULL
   1626                                                 : &uc->parse_request.tip))
   1627                        )));
   1628   }
   1629 
   1630   uc->ihc.request_body
   1631     = GNUNET_JSON_PACK (
   1632         GNUNET_JSON_pack_allow_null (
   1633           GNUNET_JSON_pack_string ("otp_id",
   1634                                    uc->lookup_template.etp.otp_id)),
   1635         GNUNET_JSON_pack_array_steal ("inventory_products",
   1636                                       inventory_products),
   1637         GNUNET_JSON_pack_object_steal (
   1638           "order",
   1639           GNUNET_JSON_PACK (
   1640             GNUNET_JSON_pack_uint64 ("version",
   1641                                      1),
   1642             GNUNET_JSON_pack_array_steal ("choices",
   1643                                           choices),
   1644             GNUNET_JSON_pack_string ("summary",
   1645                                      NULL == uc->parse_request.summary
   1646                                    ? uc->template_contract.summary
   1647                                    : uc->parse_request.summary))));
   1648   if (! GNUNET_TIME_relative_is_forever (
   1649         uc->template_contract.max_pickup_duration))
   1650   {
   1651     GNUNET_assert (
   1652       0 ==
   1653       json_object_set_new (
   1654         uc->ihc.request_body,
   1655         "max_pickup_time",
   1656         GNUNET_JSON_from_timestamp (
   1657           GNUNET_TIME_absolute_to_timestamp (
   1658             GNUNET_TIME_relative_to_absolute (
   1659               uc->template_contract.max_pickup_duration)))));
   1660   }
   1661 }
   1662 
   1663 
   1664 /**
   1665  * Create order request for fixed-order templates.
   1666  *
   1667  * @param[in,out] uc use context
   1668  */
   1669 static void
   1670 create_using_templates_fixed (struct UseContext *uc)
   1671 {
   1672   uc->ihc.request_body
   1673     = GNUNET_JSON_PACK (
   1674         GNUNET_JSON_pack_allow_null (
   1675           GNUNET_JSON_pack_string ("otp_id",
   1676                                    uc->lookup_template.etp.otp_id)),
   1677         GNUNET_JSON_pack_object_steal (
   1678           "order",
   1679           GNUNET_JSON_PACK (
   1680             TALER_JSON_pack_amount (
   1681               "amount",
   1682               &uc->compute_price.totals[0]),
   1683             GNUNET_JSON_pack_allow_null (
   1684               TALER_JSON_pack_amount ("tip",
   1685                                       uc->parse_request.no_tip
   1686                                       ? NULL
   1687                                       : &uc->parse_request.tip)),
   1688             GNUNET_JSON_pack_string (
   1689               "summary",
   1690               NULL == uc->parse_request.summary
   1691             ? uc->template_contract.summary
   1692             : uc->parse_request.summary))));
   1693 }
   1694 
   1695 
   1696 /**
   1697  * Create order request for paivana templates.
   1698  *
   1699  * @param[in,out] uc use context
   1700  */
   1701 static void
   1702 create_using_templates_paivana (struct UseContext *uc)
   1703 {
   1704   uc->ihc.request_body
   1705     = GNUNET_JSON_PACK (
   1706         GNUNET_JSON_pack_string (
   1707           "session_id",
   1708           uc->parse_request.paivana.paivana_id),
   1709         GNUNET_JSON_pack_object_steal (
   1710           "order",
   1711           GNUNET_JSON_PACK (
   1712             GNUNET_JSON_pack_uint64 ("version",
   1713                                      1),
   1714             GNUNET_JSON_pack_array_incref ("choices",
   1715                                            uc->compute_price.choices),
   1716             GNUNET_JSON_pack_string (
   1717               "summary",
   1718               NULL == uc->parse_request.summary
   1719               ? uc->template_contract.summary
   1720               : uc->parse_request.summary),
   1721             GNUNET_JSON_pack_string ("fulfillment_url",
   1722                                      uc->parse_request.paivana.website))));
   1723 }
   1724 
   1725 
   1726 static void
   1727 handle_phase_create_order (struct UseContext *uc)
   1728 {
   1729   GNUNET_assert (NULL == uc->ihc.request_body);
   1730   switch (uc->template_type)
   1731   {
   1732   case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
   1733     create_using_templates_fixed (uc);
   1734     break;
   1735   case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
   1736     create_using_templates_paivana (uc);
   1737     break;
   1738   case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
   1739     create_using_templates_inventory (uc);
   1740     break;
   1741   case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
   1742     GNUNET_assert (0);
   1743   }
   1744   uc->phase++;
   1745 }
   1746 
   1747 
   1748 /* ***************** Main handler **************** */
   1749 
   1750 enum MHD_Result
   1751 TMH_post_using_templates_ID (
   1752   const struct TMH_RequestHandler *rh,
   1753   struct MHD_Connection *connection,
   1754   struct TMH_HandlerContext *hc)
   1755 {
   1756   struct UseContext *uc = hc->ctx;
   1757 
   1758   (void) rh;
   1759   if (NULL == uc)
   1760   {
   1761     uc = GNUNET_new (struct UseContext);
   1762     uc->hc = hc;
   1763     hc->ctx = uc;
   1764     hc->cc = &cleanup_use_context;
   1765     uc->ihc.instance = hc->instance;
   1766     uc->phase = USE_PHASE_PARSE_REQUEST;
   1767     uc->template_type = TALER_MERCHANT_TEMPLATE_TYPE_INVALID;
   1768   }
   1769 
   1770   while (1)
   1771   {
   1772     switch (uc->phase)
   1773     {
   1774     case USE_PHASE_PARSE_REQUEST:
   1775       handle_phase_parse_request (uc);
   1776       break;
   1777     case USE_PHASE_LOOKUP_TEMPLATE:
   1778       handle_phase_lookup_template (uc);
   1779       break;
   1780     case USE_PHASE_PARSE_TEMPLATE:
   1781       handle_phase_template_contract (uc);
   1782       break;
   1783     case USE_PHASE_DB_FETCH:
   1784       handle_phase_db_fetch (uc);
   1785       break;
   1786     case USE_PHASE_VERIFY:
   1787       handle_phase_verify (uc);
   1788       break;
   1789     case USE_PHASE_COMPUTE_PRICE:
   1790       handle_phase_compute_price (uc);
   1791       break;
   1792     case USE_PHASE_CHECK_TIP:
   1793       handle_phase_check_tip (uc);
   1794       break;
   1795     case USE_PHASE_CHECK_TOTAL:
   1796       handle_phase_check_total (uc);
   1797       break;
   1798     case USE_PHASE_CREATE_ORDER:
   1799       handle_phase_create_order (uc);
   1800       break;
   1801     case USE_PHASE_SUBMIT_ORDER:
   1802       return TMH_private_post_orders (
   1803         NULL,    /* not even used */
   1804         connection,
   1805         &uc->ihc);
   1806     case USE_PHASE_FINISHED_MHD_YES:
   1807       return MHD_YES;
   1808     case USE_PHASE_FINISHED_MHD_NO:
   1809       return MHD_NO;
   1810     }
   1811   }
   1812 }