merchant

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

commit 7a38e2dcaaabfc0a17ca9362a40958539c629785
parent 5e22160688bc002bc9622b38d2d6d922d1dd8394
Author: Christian Grothoff <grothoff@gnunet.org>
Date:   Fri, 16 Jan 2026 09:00:09 +0100

more template creation tabularasa

Diffstat:
Msrc/backend/taler-merchant-httpd_get-templates-ID.c | 122++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Msrc/backend/taler-merchant-httpd_helper.c | 265++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/backend/taler-merchant-httpd_helper.h | 62++++++++------------------------------------------------------
Msrc/backend/taler-merchant-httpd_post-using-templates.c | 1994++++++++++++++++++++++++++++++++++++-------------------------------------------
Msrc/backend/taler-merchant-httpd_private-patch-templates-ID.c | 26+-------------------------
Msrc/backend/taler-merchant-httpd_private-post-templates.c | 26+-------------------------
Msrc/lib/merchant_api_post_templates.c | 2+-
Msrc/lib/merchant_api_post_using_templates.c | 2++
Msrc/testing/test_merchant_api.c | 26++++++++++++++++++++++----
9 files changed, 1159 insertions(+), 1366 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_get-templates-ID.c b/src/backend/taler-merchant-httpd_get-templates-ID.c @@ -105,12 +105,12 @@ inventory_payload_cleanup (struct InventoryPayloadContext *ipc) * @param categories category IDs */ static void -add_inventory_product (void *cls, - const char *product_id, - const struct - TALER_MERCHANTDB_InventoryProductDetails *pd, - size_t num_categories, - const uint64_t *categories) +add_inventory_product ( + void *cls, + const char *product_id, + const struct TALER_MERCHANTDB_InventoryProductDetails *pd, + size_t num_categories, + const uint64_t *categories) { struct InventoryPayloadContext *ipc = cls; json_t *jcategories; @@ -261,7 +261,6 @@ handle_get_templates_inventory ( size_t num_category_ids = 0; json_t *inventory_payload; json_t *template_contract; - enum GNUNET_DB_QueryStatus qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; memset (&ipc, 0, @@ -306,8 +305,6 @@ handle_get_templates_inventory ( if (! ipc.selected_all) { - // TODO: maybe, these 2 sections would be better to save to the db, in the post/patch part - // and not here, yet it is quite lightweight and straightforward so I wouldn't really care... if (NULL != ipc.selected_products) { size_t max_ids; @@ -323,6 +320,8 @@ handle_get_templates_inventory ( if (json_is_string (entry)) product_ids[num_product_ids++] = json_string_value (entry); + else + GNUNET_break (0); } } if (NULL != ipc.selected_categories) @@ -339,54 +338,75 @@ handle_get_templates_inventory ( i); if (json_is_integer (entry) && - (0 <= json_integer_value (entry))) + (0 < json_integer_value (entry))) category_ids[num_category_ids++] = (uint64_t) json_integer_value (entry); + else + GNUNET_break (0); } } } if (ipc.selected_all) + { + enum GNUNET_DB_QueryStatus qs; + qs = TMH_db->lookup_inventory_products (TMH_db->cls, mi->settings.id, &add_inventory_product, &ipc); + if (0 > qs) + { + GNUNET_break (0); + inventory_payload_cleanup (&ipc); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "lookup_inventory_products"); + } + } else if ( (0 < num_product_ids) || (0 < num_category_ids) ) { - qs = TMH_db->lookup_inventory_products_filtered (TMH_db->cls, - mi->settings.id, - product_ids, - num_product_ids, - category_ids, - num_category_ids, - &add_inventory_product, - &ipc); + enum GNUNET_DB_QueryStatus qs; + qs = TMH_db->lookup_inventory_products_filtered ( + TMH_db->cls, + mi->settings.id, + product_ids, + num_product_ids, + category_ids, + num_category_ids, + &add_inventory_product, + &ipc); GNUNET_free (product_ids); GNUNET_free (category_ids); - } - if (0 > qs) - { - GNUNET_break (0); - inventory_payload_cleanup (&ipc); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "lookup_inventory_products"); + if (0 > qs) + { + GNUNET_break (0); + inventory_payload_cleanup (&ipc); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "lookup_inventory_products_filtered"); + } } ipc.category_payload = json_array (); GNUNET_assert (NULL != ipc.category_payload); if (0 < ipc.category_set.len) { - qs = TMH_db->lookup_categories_by_ids (TMH_db->cls, - mi->settings.id, - ipc.category_set.ids, - ipc.category_set.len, - &add_inventory_category, - &ipc); + enum GNUNET_DB_QueryStatus qs; + + qs = TMH_db->lookup_categories_by_ids ( + TMH_db->cls, + mi->settings.id, + ipc.category_set.ids, + ipc.category_set.len, + &add_inventory_category, + &ipc); if (0 > qs) { GNUNET_break (0); @@ -403,11 +423,12 @@ handle_get_templates_inventory ( GNUNET_assert (NULL != ipc.unit_payload); if (0 < ipc.unit_set.len) { + enum GNUNET_DB_QueryStatus qs; + qs = TMH_db->lookup_custom_units_by_names ( TMH_db->cls, mi->settings.id, - (const char *const *) ipc.unit_set. - units, + (const char *const *) ipc.unit_set.units, ipc.unit_set.len, &add_inventory_unit, &ipc); @@ -436,6 +457,14 @@ handle_get_templates_inventory ( template_contract = json_deep_copy (tp->template_contract); GNUNET_assert (NULL != template_contract); + /* remove internal fields */ + (void) json_object_del (template_contract, + "selected_categories"); + (void) json_object_del (template_contract, + "selected_products"); + (void) json_object_del (template_contract, + "selected_all"); + /* add inventory data */ GNUNET_assert (0 == json_object_set_new (template_contract, "inventory_payload", @@ -498,11 +527,10 @@ TMH_get_templates_ID ( ret = handle_get_templates_inventory (connection, mi, &tp); - break; + TALER_MERCHANTDB_template_details_free (&tp); + return ret; case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: - /* TODO: PAIVANA add paivana-specific payload elements. - (Yet, I think, everything will be in the contract json so we can just pack it) */ ret = TALER_MHD_REPLY_JSON_PACK ( connection, MHD_HTTP_OK, @@ -511,17 +539,17 @@ TMH_get_templates_ID ( tp.editable_defaults)), GNUNET_JSON_pack_object_incref ("template_contract", tp.template_contract)); - break; + TALER_MERCHANTDB_template_details_free (&tp); + return ret; case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: - default: - GNUNET_break_op (0); - ret = TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - "template_type"); break; } + GNUNET_break_op (0); + ret = TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "template_type"); TALER_MERCHANTDB_template_details_free (&tp); return ret; } diff --git a/src/backend/taler-merchant-httpd_helper.c b/src/backend/taler-merchant-httpd_helper.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2014--2023 Taler Systems SA + (C) 2014--2026 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software @@ -374,6 +374,7 @@ TMH_products_array_valid (const json_t *products) } json_array_foreach ((json_t *) products, idx, product) { + /* FIXME: use TALER_MERCHANT_parse_product() instead? */ const char *product_id = NULL; const char *description; uint64_t quantity = 0; @@ -523,164 +524,151 @@ TMH_template_type_to_string (enum TALER_MERCHANT_TemplateType template_type) } -bool -TMH_template_contract_valid (const json_t *template_contract) +static bool +inventory_template_contract_valid (const json_t *template_contract) { - enum TALER_MERCHANT_TemplateType template_type; - const char *summary; - const char *currency; - struct TALER_Amount amount = { .value = 0}; - uint32_t minimum_age = 0; - struct GNUNET_TIME_Relative pay_duration = { 0 }; - struct GNUNET_JSON_Specification spec[] = { + bool selected_all = false; + bool choose_one = false; + bool request_tip = false; + const json_t *selected_categories = NULL; + const json_t *selected_products = NULL; + struct GNUNET_JSON_Specification ispec[] = { GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("summary", - &summary), + GNUNET_JSON_spec_bool ("request_tip", + &request_tip), NULL), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("currency", - &currency), + GNUNET_JSON_spec_bool ("selected_all", + &selected_all), NULL), GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount_any ("amount", - &amount), + GNUNET_JSON_spec_array_const ("selected_categories", + &selected_categories), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("selected_products", + &selected_products), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("choose_one", + &choose_one), NULL), - GNUNET_JSON_spec_uint32 ("minimum_age", - &minimum_age), - GNUNET_JSON_spec_relative_time ("pay_duration", - &pay_duration), GNUNET_JSON_spec_end () }; const char *ename; unsigned int eline; - template_type = TMH_template_type_from_contract (template_contract); - if (TALER_MERCHANT_TEMPLATE_TYPE_INVALID == template_type) + if (GNUNET_OK != + GNUNET_JSON_parse (template_contract, + ispec, + &ename, + &eline)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Invalid template_type\n"); + "Invalid inventory template_contract for field %s\n", + ename); return false; } - - if (TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART == template_type) + if (NULL != selected_categories) { - bool selected_all = false; - bool choose_one = false; - bool request_tip = false; - const json_t *selected_categories = NULL; - const json_t *selected_products = NULL; - struct GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("summary", - &summary), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("request_tip", - &request_tip), - NULL), - GNUNET_JSON_spec_relative_time ("pay_duration", - &pay_duration), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("selected_all", - &selected_all), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_array_const ("selected_categories", - &selected_categories), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_array_const ("selected_products", - &selected_products), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("choose_one", - &choose_one), - NULL), - GNUNET_JSON_spec_end () - }; + const json_t *entry; + size_t idx; - if (GNUNET_OK != - GNUNET_JSON_parse (template_contract, - ispec, - &ename, - &eline)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Invalid inventory template_contract for field %s\n", - ename); - return false; - } - if (NULL != selected_categories) + json_array_foreach ((json_t *) selected_categories, idx, entry) { - const json_t *entry; - size_t idx; - - json_array_foreach ((json_t *) selected_categories, idx, entry) + if (! json_is_integer (entry)) { - if (! json_is_integer (entry)) - { - GNUNET_break_op (0); - return false; - } - if (0 > json_integer_value (entry)) - { - GNUNET_break_op (0); - return false; - } + GNUNET_break_op (0); + return false; } - } - if (NULL != selected_products) - { - const json_t *entry; - size_t idx; - - json_array_foreach ((json_t *) selected_products, idx, entry) + if (0 > json_integer_value (entry)) { - if (! json_is_string (entry)) - { - GNUNET_break_op (0); - return false; - } + GNUNET_break_op (0); + return false; } } - return true; } - - if (TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA == template_type) + if (NULL != selected_products) { - const char *paivana_id; - struct GNUNET_JSON_Specification pspec[] = { - GNUNET_JSON_spec_string ("paivana_id", - &paivana_id), - GNUNET_JSON_spec_end () - }; + const json_t *entry; + size_t idx; - if (GNUNET_OK != - GNUNET_JSON_parse (template_contract, - spec, - &ename, - &eline)) + json_array_foreach ((json_t *) selected_products, idx, entry) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Invalid paivana template_contract for field %s\n", - ename); - return false; - } - if (GNUNET_OK != - GNUNET_JSON_parse (template_contract, - pspec, - &ename, - &eline)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Invalid paivana template_contract for field %s\n", - ename); - return false; + if (! json_is_string (entry)) + { + GNUNET_break_op (0); + return false; + } } - (void) paivana_id; - /* TODO: PAIVANA validate paivana-specific fields beyond presence. */ - return true; } + return true; +} + + +static bool +paivana_template_contract_valid (const json_t *template_contract) +{ + const char *paivana_id; + struct GNUNET_JSON_Specification pspec[] = { + GNUNET_JSON_spec_string ("paivana_id", + &paivana_id), + GNUNET_JSON_spec_end () + }; + const char *ename; + unsigned int eline; + + if (GNUNET_OK != + GNUNET_JSON_parse (template_contract, + pspec, + &ename, + &eline)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid paivana template_contract for field %s\n", + ename); + return false; + } + (void) paivana_id; + /* TODO: PAIVANA validate paivana-specific fields beyond presence. */ + return true; +} + + +bool +TMH_template_contract_valid (const json_t *template_contract) +{ + const char *summary; + const char *currency; + struct TALER_Amount amount = { .value = 0}; + uint32_t minimum_age = 0; + struct GNUNET_TIME_Relative pay_duration = { 0 }; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("summary", + &summary), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("currency", + &currency), + NULL), + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_amount_any ("amount", + &amount), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("minimum_age", + &minimum_age), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_relative_time ("pay_duration", + &pay_duration), + NULL), + GNUNET_JSON_spec_end () + }; + const char *ename; + unsigned int eline; + enum TALER_MERCHANT_TemplateType template_type; if (GNUNET_OK != GNUNET_JSON_parse (template_contract, @@ -693,7 +681,22 @@ TMH_template_contract_valid (const json_t *template_contract) ename); return false; } - return true; + template_type = TMH_template_type_from_contract (template_contract); + switch (template_type) + { + case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: + /* no further fields */ + return true; + case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: + return inventory_template_contract_valid (template_contract); + case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: + return paivana_template_contract_valid (template_contract); + case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: + break; + } + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid template_type\n"); + return false; } @@ -1016,6 +1019,7 @@ trigger_webhook_cb (void *cls, if (NULL != body_template) { int ret; + ret = TALER_TEMPLATING_fill (body_template, t->root, &body, @@ -1052,6 +1056,7 @@ trigger_webhook_cb (void *cls, &extra, extra_size); } + /* allocated by mustach, hence use free(), not GNUNET_free() */ free (header); free (body); } diff --git a/src/backend/taler-merchant-httpd_helper.h b/src/backend/taler-merchant-httpd_helper.h @@ -27,7 +27,7 @@ #include "taler-merchant-httpd.h" -#include <taler/taler_merchant_util.h> +#include "taler_merchant_util.h" /** * check @a accounts for well-formedness @@ -55,8 +55,6 @@ TMH_location_object_valid (const json_t *location); * Check if @a products is an array of valid Product(s) in the sense of * Taler's API definition. * - * FIXME: use TALER_MERCHANT_parse_product() instead? - * * @param products array to check * @return true if @a products is an array and all * entries are valid Products. @@ -90,17 +88,6 @@ TMH_validate_unit_price_array (const struct TALER_Amount *prices, size_t prices_len); /** - * Kind of fixed-decimal value the helpers operate on. - * Primarily distinguishes how special sentinel values (such as "-1" - * meaning infinity for stock) must be encoded. - */ -enum TMH_ValueKind -{ - TMH_VK_QUANTITY, /* -1 is illegal */ - TMH_VK_STOCK /* -1 means "infinity" */ -}; - -/** * Determine template type from a template contract. * * @param template_contract contract JSON @@ -199,39 +186,6 @@ TMH_unit_set_add (struct TMH_UnitSet *set, void TMH_unit_set_clear (struct TMH_UnitSet *set); -/** - * Extract a fixed-decimal number that may be supplied either - * - as pure integer (e.g. "total_stock"), or - * - as decimal text (e.g. "unit_total_stock"). - * - * Rules: - * - If both forms are missing -> error. - * - If both are present -> they must match and the decimal must have no fraction. - * - For kind == TMH_VK_STOCK the integer value -1 represents infinity. - * - * @param kind See #TMH_ValueKind - * @param allow_fractional False: any fractional part is rejected - * @param int_missing True if client omitted the integer field - * @param int_raw Raw integer (undefined if @a int_missing is true) - * @param str_missing True if client omitted the string field - * @param str_raw Raw UTF-8 string (undefined if @a str_missing is true) - * @param[out] int_out Canonicalised integer part - * @param[out] frac_out Canonicalised fractional part - * @param[out] error_param Set to offending field name on failure - * @param int_field Integer field name (for error reporting) - * @param str_field String field name (for error reporting) - * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise - */ -enum GNUNET_GenericReturnValue -TMH_process_quantity_inputs (enum TMH_ValueKind kind, - bool allow_fractional, - bool int_missing, - int64_t int_raw, - bool str_missing, - const char *str_raw, - uint64_t *int_out, - uint32_t *frac_out, - const char **error_param); /** * Lookup the defaults for @a unit within @a mi and fall back to sane @@ -419,13 +373,13 @@ TMH_make_order_status_url (struct MHD_Connection *con, * @param hr a `TALER_EXCHANGE_HttpResponse` */ #define TMH_pack_exchange_reply(hr) \ - GNUNET_JSON_pack_uint64 ("exchange_code", (hr)->ec), \ - GNUNET_JSON_pack_uint64 ("exchange_http_status", (hr)->http_status), \ - GNUNET_JSON_pack_uint64 ("exchange_ec", (hr)->ec), /* LEGACY */ \ - GNUNET_JSON_pack_uint64 ("exchange_hc", (hr)->http_status), /* LEGACY */ \ - GNUNET_JSON_pack_allow_null ( \ - GNUNET_JSON_pack_object_incref ("exchange_reply", (json_t *) (hr)-> \ - reply)) + GNUNET_JSON_pack_uint64 ("exchange_code", (hr)->ec), \ + GNUNET_JSON_pack_uint64 ("exchange_http_status", (hr)->http_status), \ + GNUNET_JSON_pack_uint64 ("exchange_ec", (hr)->ec), /* LEGACY */ \ + GNUNET_JSON_pack_uint64 ("exchange_hc", (hr)->http_status), /* LEGACY */ \ + GNUNET_JSON_pack_allow_null ( \ + GNUNET_JSON_pack_object_incref ("exchange_reply", (json_t *) (hr)-> \ + reply)) /** diff --git a/src/backend/taler-merchant-httpd_post-using-templates.c b/src/backend/taler-merchant-httpd_post-using-templates.c @@ -30,7 +30,6 @@ #include "taler-merchant-httpd_exchanges.h" #include <taler/taler_json_lib.h> - /** * Item selected from inventory_selection. */ @@ -39,12 +38,12 @@ struct InventoryTemplateItemContext /** * Product ID as referenced in inventory. */ - char *product_id; + const char *product_id; /** * Unit quantity string as provided by the client. */ - char *unit_quantity; + const char *unit_quantity; /** * Parsed integer quantity. @@ -82,30 +81,60 @@ enum UsePhase * Parse request payload into context fields. */ USE_PHASE_PARSE_REQUEST, + /** * Fetch template details from the database. */ USE_PHASE_LOOKUP_TEMPLATE, + + /** + * Parse template. + */ + USE_PHASE_PARSE_TEMPLATE, + /** - * Load DB-backed details needed for verification. + * Load additional details (like products and + * categories) needed for verification and + * price computation. */ USE_PHASE_DB_FETCH, + /** * Validate request and template compatibility. */ USE_PHASE_VERIFY, + + /** + * Compute price of the order. + */ + USE_PHASE_COMPUTE_PRICE, + + /** + * Handle tip. + */ + USE_PHASE_CHECK_TIP, + + /** + * Check if client-supplied total amount matches + * our calculation (if we did any). + */ + USE_PHASE_CHECK_TOTAL, + /** * Construct the internal order request body. */ USE_PHASE_CREATE_ORDER, + /** * Submit the order to the shared order handler. */ USE_PHASE_SUBMIT_ORDER, + /** * Finished successfully with MHD_YES. */ USE_PHASE_FINISHED_MHD_YES, + /** * Finished with MHD_NO. */ @@ -115,6 +144,11 @@ enum UsePhase struct UseContext { /** + * Context for our handler. + */ + struct TMH_HandlerContext *hc; + + /** * Internal handler context we are passing into the * POST /private/orders handler. */ @@ -151,91 +185,100 @@ struct UseContext const char *fulfillment_message; /** - * Parsed fields for each template type. + * Amount provided by the client. + */ + struct TALER_Amount amount; + + /** + * Tip provided by the client. + */ + struct TALER_Amount tip; + + /** + * True if @e amount was not provided. + */ + bool no_amount; + + /** + * True if @e tip was not provided. */ - union + bool no_tip; + + /** + * Parsed fields for inventory templates. + */ + struct { /** - * Parsed fields for fixed-order templates. + * Selected products from inventory_selection. */ - struct - { - /** - * Amount provided by the client. - */ - struct TALER_Amount amount; - /** - * Tip provided by the client. - */ - struct TALER_Amount tip; - /** - * True if @e amount was not provided. - */ - bool no_amount; - /** - * True if @e tip was not provided. - */ - bool no_tip; - } fixed; + struct InventoryTemplateItemContext *items; /** - * Parsed fields for inventory templates. + * Length of @e items. */ - struct - { - /** - * Selected products from inventory_selection. - */ - struct InventoryTemplateItemContext *items; - - /** - * Length of @e items. - */ - unsigned int items_len; - - /** - * Optional pre-calculated amount provided by the client. - */ - struct TALER_Amount amount; - - /** - * Optional tip amount. - */ - struct TALER_Amount tip; - - /** - * True if @e amount was not provided. - */ - bool no_amount; - /** - * True if @e tip was not provided. - */ - bool no_tip; - } inventory; - } template; + unsigned int items_len; + + } inventory; } parse_request; /** - * Information set in the #USE_PHASE_DB_FETCH phase. + * Information set in the #USE_PHASE_LOOKUP_TEMPLATE phase. */ struct { + /** * Our template details from the DB. */ struct TALER_MERCHANTDB_TemplateDetails etp; + } lookup_template; + + /** + * Information set in the #USE_PHASE_PARSE_TEMPLATE phase. + */ + struct + { + + /** + * Summary from the template contract. + */ + const char *tsummary; + + /** + * Minimum age required by the template. + */ + uint32_t min_age; + + /** + * How long does the customer have to pay for the + * order. 0 if not specified (use instance default). + */ + struct GNUNET_TIME_Relative pay_duration; + /** - * True once @e etp was initialized. + * Currency from the template contract. */ - bool have_etp; + const char *tcurrency; /** - * Inventory template selections from the contract. + * Amount from the template contract. + */ + struct TALER_Amount tamount; + + /** + * True if @e tamount was not provided. + */ + bool no_tamount; + + /** + * Parsed fields for inventory templates. */ struct { + /** * Selected categories from the template contract. */ @@ -250,69 +293,26 @@ struct UseContext * Whether all products are selectable. */ bool selected_all; - } inventory; - } db_fetch; - /** - * Information set in the #USE_PHASE_VERIFY phase. - */ - struct - { - /** - * Verified fields for each template type. - */ - union - { /** - * Verified fields for fixed-order templates. + * Template requires exactly one selection. */ - struct - { - /** - * Summary from the template contract. - */ - const char *tsummary; - /** - * Currency from the template contract. - */ - const char *tcurrency; - /** - * Amount from the template contract. - */ - struct TALER_Amount tamount; - /** - * True if @e tamount was not provided. - */ - bool no_tamount; - /** - * True if no summary override was provided. - */ - bool no_summary; - } fixed; + bool choose_one; /** - * Verified fields for inventory templates. + * Template allows tips. */ - struct - { - /** - * Template requires exactly one selection. - */ - bool choose_one; - /** - * Template allows tips. - */ - bool request_tip; - /** - * Summary from the template contract. - */ - const char *tsummary; - /** - * True if no summary override was provided. - */ - bool no_summary; - } inventory; - } template; + bool request_tip; + + } inventory; + + } parse_template; + + /** + * Information set in the #USE_PHASE_COMPUTE_PRICE phase. + */ + struct + { /** * Per-currency totals across selected products (without tips). @@ -323,75 +323,51 @@ struct UseContext * Length of @e totals. */ unsigned int totals_len; - } verify; + + } compute_price; + }; /** * Clean up inventory items. * - * @param items item array to free * @param items_len length of @a items + * @param[in] items item array to free */ static void -cleanup_inventory_items (struct InventoryTemplateItemContext **items, - unsigned int *items_len) +cleanup_inventory_items (unsigned int items_len, + struct InventoryTemplateItemContext items[static + items_len]) { - if ((NULL == items) || (NULL == *items)) - return; - for (unsigned int i = 0; i < *items_len; i++) + for (unsigned int i = 0; i < items_len; i++) { - struct InventoryTemplateItemContext *item = &(*items)[i]; + struct InventoryTemplateItemContext *item = &items[i]; - GNUNET_free (item->product_id); - GNUNET_free (item->unit_quantity); TALER_MERCHANTDB_product_details_free (&item->pd); GNUNET_free (item->categories); } - GNUNET_free (*items); - *items = NULL; - if (NULL != items_len) - *items_len = 0; -} - - -/** - * Clean up inventory totals. - * - * @param totals totals array to free - * @param totals_len length of @a totals - */ -static void -cleanup_inventory_totals (struct TALER_Amount **totals, - unsigned int *totals_len) -{ - if ((NULL == totals) || (NULL == *totals)) - return; - GNUNET_free (*totals); - *totals = NULL; - if (NULL != totals_len) - *totals_len = 0; + GNUNET_free (items); } /** * Clean up a `struct UseContext *` * - * @param cls a `struct UseContext *` + * @param[in] cls a `struct UseContext *` */ static void cleanup_use_context (void *cls) { struct UseContext *uc = cls; - TALER_MERCHANTDB_template_details_free (&uc->db_fetch.etp); - if (TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART == uc->template_type) - { - cleanup_inventory_items (&uc->parse_request.template.inventory.items, - &uc->parse_request.template.inventory.items_len); - cleanup_inventory_totals (&uc->verify.totals, - &uc->verify.totals_len); - } + TALER_MERCHANTDB_template_details_free (&uc->lookup_template.etp); + if (NULL != + uc->parse_request.inventory.items) + cleanup_inventory_items (uc->parse_request.inventory.items_len, + uc->parse_request.inventory.items); + GNUNET_free (uc->compute_price.totals); + uc->compute_price.totals_len = 0; if (NULL != uc->ihc.cc) uc->ihc.cc (uc->ihc.ctx); GNUNET_free (uc->ihc.infix); @@ -426,6 +402,7 @@ static void use_finalize_parse (struct UseContext *uc, enum GNUNET_GenericReturnValue res) { + GNUNET_assert (GNUNET_OK != res); use_finalize (uc, (GNUNET_NO == res) ? MHD_YES @@ -437,21 +414,19 @@ use_finalize_parse (struct UseContext *uc, * Reply with error and finalize the request. * * @param[in,out] uc use context - * @param connection connection to reply on * @param http_status HTTP status code * @param ec error code * @param detail error detail */ static void use_reply_with_error (struct UseContext *uc, - struct MHD_Connection *connection, unsigned int http_status, enum TALER_ErrorCode ec, const char *detail) { MHD_RESULT mret; - mret = TALER_MHD_reply_with_error (connection, + mret = TALER_MHD_reply_with_error (uc->hc->connection, http_status, ec, detail); @@ -465,42 +440,28 @@ use_reply_with_error (struct UseContext *uc, /** * Parse request data for inventory templates. * - * @param connection connection to reply on - * @param hc handler context - * @param uc use context + * @param[in,out] uc use context * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue -parse_using_templates_inventory_request (struct MHD_Connection *connection, - struct TMH_HandlerContext *hc, - struct UseContext *uc) +parse_using_templates_inventory_request ( + struct UseContext *uc) { - if (NULL != uc->ihc.request_body) - return GNUNET_OK; - - const json_t *inventory_selection = NULL; - + const json_t *inventory_selection; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("summary", - &uc->parse_request.summary), - NULL), - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount_any ("amount", - &uc->parse_request.template.inventory.amount), - &uc->parse_request.template.inventory.no_amount), - GNUNET_JSON_spec_mark_optional ( TALER_JSON_spec_amount_any ("tip", - &uc->parse_request.template.inventory.tip), - &uc->parse_request.template.inventory.no_tip), + &uc->parse_request.tip), + &uc->parse_request.no_tip), GNUNET_JSON_spec_array_const ("inventory_selection", &inventory_selection), GNUNET_JSON_spec_end () }; enum GNUNET_GenericReturnValue res; - res = TALER_MHD_parse_json_data (connection, - hc->request_body, + GNUNET_assert (NULL == uc->ihc.request_body); + res = TALER_MHD_parse_json_data (uc->hc->connection, + uc->hc->request_body, spec); if (GNUNET_OK != res) { @@ -510,55 +471,49 @@ parse_using_templates_inventory_request (struct MHD_Connection *connection, return GNUNET_SYSERR; } - if ( (! uc->parse_request.template.inventory.no_amount) || - (! uc->parse_request.template.inventory.no_tip) ) + if ( (! uc->parse_request.no_amount) || + (! uc->parse_request.no_tip) ) { if (! TMH_test_exchange_configured_for_currency ( - (! uc->parse_request.template.inventory.no_amount) - ? uc->parse_request.template.inventory.amount.currency - : uc->parse_request.template.inventory.tip.currency)) + (! uc->parse_request.no_amount) + ? uc->parse_request.amount.currency + : uc->parse_request.tip.currency)) { GNUNET_break_op (0); - cleanup_inventory_items (&uc->parse_request.template.inventory.items, - &uc->parse_request.template.inventory.items_len); use_reply_with_error (uc, - connection, MHD_HTTP_CONFLICT, TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, - "Currency for amount is not supported by backend"); + "Currency is not supported by backend"); return GNUNET_SYSERR; } } - if (! uc->parse_request.template.inventory.no_tip) + if (! uc->parse_request.no_tip) { - if (! uc->parse_request.template.inventory.no_amount && + if (! uc->parse_request.no_amount && (GNUNET_YES != TALER_amount_cmp_currency ( - &uc->parse_request.template.inventory.amount, - &uc->parse_request.template.inventory.tip))) + &uc->parse_request.amount, + &uc->parse_request.tip))) { GNUNET_break_op (0); - cleanup_inventory_items (&uc->parse_request.template.inventory.items, - &uc->parse_request.template.inventory.items_len); - use_reply_with_error (uc, - connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, - "Mismatch of currencies between the amount and tip"); + use_reply_with_error ( + uc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_CURRENCY_MISMATCH, + "Mismatch of currencies between the total amount and tip"); return GNUNET_SYSERR; } } for (size_t i = 0; i < json_array_size (inventory_selection); i++) { - const char *product_id; - const char *quantity; + struct InventoryTemplateItemContext item = { 0 }; struct GNUNET_JSON_Specification ispec[] = { GNUNET_JSON_spec_string ("product_id", - &product_id), + &item.product_id), GNUNET_JSON_spec_string ("quantity", - &quantity), + &item.unit_quantity), GNUNET_JSON_spec_end () }; const char *err_name; @@ -572,364 +527,353 @@ parse_using_templates_inventory_request (struct MHD_Connection *connection, if (GNUNET_OK != res) { GNUNET_break_op (0); - cleanup_inventory_items (&uc->parse_request.template.inventory.items, - &uc->parse_request.template.inventory.items_len); use_reply_with_error (uc, - connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, "inventory_selection"); return GNUNET_SYSERR; } - { - struct InventoryTemplateItemContext item; - - memset (&item, - 0, - sizeof (item)); - item.product_id = GNUNET_strdup (product_id); - item.unit_quantity = GNUNET_strdup (quantity); - GNUNET_array_append (uc->parse_request.template.inventory.items, - uc->parse_request.template.inventory.items_len, - item); - } + GNUNET_array_append (uc->parse_request.inventory.items, + uc->parse_request.inventory.items_len, + item); } - if (0 == uc->parse_request.template.inventory.items_len) +#if FIXME_PERMIT_ONLY_TIPPING + if (0 == uc->parse_request.inventory.items_len) { GNUNET_break_op (0); - cleanup_inventory_items (&uc->parse_request.template.inventory.items, - &uc->parse_request.template.inventory.items_len); use_reply_with_error (uc, - connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MISSING, "inventory_selection"); return GNUNET_SYSERR; } +#endif return GNUNET_OK; } /** - * Parse request data for fixed-order templates. + * Parse request data for paivana templates. * - * @param connection connection to reply on - * @param hc handler context - * @param uc use context + * @param[in,out] uc use context * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue -parse_using_templates_fixed_request (struct MHD_Connection *connection, - struct TMH_HandlerContext *hc, - struct UseContext *uc) +parse_using_templates_paivana_request ( + struct UseContext *uc) +{ + /* FIXME: not implemented */ + return GNUNET_OK; +} + + +/** + * Main function for the #USE_PHASE_PARSE_REQUEST. + * + * @param[in,out] uc context to update + */ +static void +handle_phase_parse_request ( + struct UseContext *uc) { + const char *template_type = NULL; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("template_type", + &template_type), + NULL), + GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("summary", &uc->parse_request.summary), NULL), GNUNET_JSON_spec_mark_optional ( TALER_JSON_spec_amount_any ("amount", - &uc->parse_request.template.fixed.amount), - &uc->parse_request.template.fixed.no_amount), - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount_any ("tip", - &uc->parse_request.template.fixed.tip), - &uc->parse_request.template.fixed.no_tip), + &uc->parse_request.amount), + &uc->parse_request.no_amount), GNUNET_JSON_spec_end () }; enum GNUNET_GenericReturnValue res; - res = TALER_MHD_parse_json_data (connection, - hc->request_body, + res = TALER_MHD_parse_json_data (uc->hc->connection, + uc->hc->request_body, spec); if (GNUNET_OK != res) { GNUNET_break_op (0); use_finalize_parse (uc, res); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Parse request data for paivana templates. - * - * @param connection connection to reply on - * @param hc handler context - * @param uc use context - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -parse_using_templates_paivana_request (struct MHD_Connection *connection, - struct TMH_HandlerContext *hc, - struct UseContext *uc) -{ - /* TODO: PAIVANA include paivana_id in instantiation flow. */ - return parse_using_templates_fixed_request (connection, - hc, - uc); -} - - -static void -handle_phase_parse_request (struct MHD_Connection *connection, - struct TMH_HandlerContext *hc, - struct UseContext *uc) -{ - if (TALER_MERCHANT_TEMPLATE_TYPE_INVALID == uc->template_type) - { - if (NULL == hc->request_body || ! json_is_object (hc->request_body)) - { - GNUNET_break_op (0); - use_reply_with_error (uc, - connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "request"); - return; - } - uc->phase = USE_PHASE_LOOKUP_TEMPLATE; return; } - enum GNUNET_GenericReturnValue res = GNUNET_SYSERR; - - // FIXME: share more logic between these, just skip (or run) individual - // phases depending on the template type! + if (NULL == template_type) + template_type = "fixed-order"; + uc->template_type + = TALER_MERCHANT_template_type_from_string ( + template_type); switch (uc->template_type) { case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: - res = parse_using_templates_fixed_request (connection, - hc, - uc); - break; + /* nothig left to do */ + uc->phase++; + return; case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: - /* TODO: PAIVANA handle paivana-specific instantiation. */ - res = parse_using_templates_paivana_request (connection, - hc, - uc); - break; + res = parse_using_templates_paivana_request (uc); + if (GNUNET_OK == res) + uc->phase++; + return; case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: - res = parse_using_templates_inventory_request (connection, - hc, - uc); - break; + res = parse_using_templates_inventory_request (uc); + if (GNUNET_OK == res) + uc->phase++; + return; case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: - GNUNET_break (0); - use_reply_with_error ( - uc, - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - "template_contract has unexpected type"); break; } - if (GNUNET_OK == res) - uc->phase = USE_PHASE_DB_FETCH; + GNUNET_break (0); + use_reply_with_error ( + uc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "template_type"); } /* ***************** USE_PHASE_LOOKUP_TEMPLATE **************** */ +/** + * Main function for the #USE_PHASE_LOOKUP_TEMPLATE. + * + * @param[in,out] uc context to update + */ static void -handle_phase_lookup_template (struct MHD_Connection *connection, - struct TMH_HandlerContext *hc, - struct UseContext *uc) +handle_phase_lookup_template ( + struct UseContext *uc) { - struct TMH_MerchantInstance *mi = hc->instance; - const char *template_id = hc->infix; - - if (! uc->db_fetch.have_etp) + struct TMH_MerchantInstance *mi = uc->hc->instance; + const char *template_id = uc->hc->infix; + enum GNUNET_DB_QueryStatus qs; + + qs = TMH_db->lookup_template (TMH_db->cls, + mi->settings.id, + template_id, + &uc->lookup_template.etp); + switch (qs) { - enum GNUNET_DB_QueryStatus qs; - - // FIXME: include template type in request, fully parse - // request before even going into the DB. - qs = TMH_db->lookup_template (TMH_db->cls, - mi->settings.id, - template_id, - &uc->db_fetch.etp); - switch (qs) - { - case GNUNET_DB_STATUS_HARD_ERROR: - /* Clean up and fail hard */ - GNUNET_break (0); - use_reply_with_error (uc, - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - NULL); - break; - case GNUNET_DB_STATUS_SOFT_ERROR: - /* this should be impossible (single select) */ - GNUNET_break (0); - use_reply_with_error (uc, - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - NULL); - break; - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - /* template not found! */ - use_reply_with_error (uc, - connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN, - template_id); - break; - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - /* all good */ - uc->db_fetch.have_etp = true; - break; - } - if (! uc->db_fetch.have_etp) - return; + case GNUNET_DB_STATUS_HARD_ERROR: + /* Clean up and fail hard */ + GNUNET_break (0); + use_reply_with_error (uc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "lookup_template"); + return; + case GNUNET_DB_STATUS_SOFT_ERROR: + /* this should be impossible (single select) */ + GNUNET_break (0); + use_reply_with_error (uc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "lookup_template"); + return; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + /* template not found! */ + use_reply_with_error (uc, + MHD_HTTP_NOT_FOUND, + TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN, + template_id); + return; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + /* all good */ + break; } - uc->template_type = - TMH_template_type_from_contract (uc->db_fetch.etp.template_contract); - if (TALER_MERCHANT_TEMPLATE_TYPE_INVALID == uc->template_type) + if (uc->template_type != + TMH_template_type_from_contract ( + uc->lookup_template.etp.template_contract)) { - GNUNET_break (0); + GNUNET_break_op (0); use_reply_with_error ( uc, - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - "template_contract has unexpected type"); + MHD_HTTP_CONFLICT, + TALER_EC_MERCHANT_POST_USING_TEMPLATES_WRONG_TYPE, + "template_contract has different type"); return; } - uc->phase = USE_PHASE_PARSE_REQUEST; + uc->phase++; } -/* ***************** USE_PHASE_DB_FETCH **************** */ +/* ***************** USE_PHASE_PARSE_TEMPLATE **************** */ + /** - * Fetch DB data for inventory templates. + * Parse template. * - * @param connection connection to reply on - * @param hc handler context - * @param uc use context - * @return #GNUNET_OK on success + * @param[in,out] uc use context */ -static enum GNUNET_GenericReturnValue -db_fetch_using_templates_inventory (struct MHD_Connection *connection, - struct TMH_HandlerContext *hc, - struct UseContext *uc) -{ - struct TMH_MerchantInstance *mi = hc->instance; - - for (unsigned int i = 0; i < uc->parse_request.template.inventory.items_len; - i++) - { - struct InventoryTemplateItemContext *item = - &uc->parse_request.template.inventory.items[i]; - struct TALER_MERCHANTDB_ProductDetails pd; - enum GNUNET_DB_QueryStatus qs; - size_t num_categories = 0; - uint64_t *categories = NULL; - - qs = TMH_db->lookup_product (TMH_db->cls, - mi->settings.id, - item->product_id, - &pd, - &num_categories, - &categories); - if (qs <= 0) - { - enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; - unsigned int http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; - - switch (qs) - { - case GNUNET_DB_STATUS_HARD_ERROR: - GNUNET_break (0); - ec = TALER_EC_GENERIC_DB_FETCH_FAILED; - break; - case GNUNET_DB_STATUS_SOFT_ERROR: - GNUNET_break (0); - ec = TALER_EC_GENERIC_DB_SOFT_FAILURE; - break; - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - http_status = MHD_HTTP_NOT_FOUND; - ec = TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN; - break; - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - GNUNET_assert (0); - } - use_reply_with_error (uc, - connection, - http_status, - ec, - item->product_id); - return GNUNET_SYSERR; - } - - item->pd = pd; - item->categories = categories; - item->num_categories = num_categories; - } - return GNUNET_OK; -} - - static void -handle_phase_db_fetch (struct MHD_Connection *connection, - struct TMH_HandlerContext *hc, - struct UseContext *uc) +handle_phase_parse_template (struct UseContext *uc) { - enum GNUNET_GenericReturnValue res = GNUNET_OK; - - switch (uc->template_type) - { - case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: + struct GNUNET_JSON_Specification tcspec[] = { + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("summary", + &uc->parse_template.tsummary), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("currency", + &uc->parse_template.tcurrency), + NULL), + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_amount_any ("amount", + &uc->parse_template.tamount), + &uc->parse_template.no_tamount), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_uint32 ("minimum_age", + &uc->parse_template.min_age), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_relative_time ("pay_duration", + &uc->parse_template.pay_duration), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ( + "selected_categories", + &uc->parse_template.inventory.selected_categories), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ( + "selected_products", + &uc->parse_template.inventory.selected_products), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("choose_one", + &uc->parse_template.inventory.choose_one), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("request_tip", + &uc->parse_template.inventory.request_tip), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("selected_all", + &uc->parse_template.inventory.selected_all), + NULL), + GNUNET_JSON_spec_end () + }; + const char *err_name; + unsigned int err_line; + enum GNUNET_GenericReturnValue res; + + res = GNUNET_JSON_parse (uc->lookup_template.etp.template_contract, + tcspec, + &err_name, + &err_line); + if (GNUNET_OK != res) + { + GNUNET_break (0); + use_reply_with_error (uc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + err_name); + return; + } + uc->phase++; +} + + +/* ***************** USE_PHASE_DB_FETCH **************** */ + +/** + * Fetch DB data for inventory templates. + * + * @param[in,out] uc use context + */ +static void +handle_phase_db_fetch (struct UseContext *uc) +{ + struct TMH_MerchantInstance *mi = uc->hc->instance; + + switch (uc->template_type) + { + case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: + uc->phase++; + return; case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: - break; + uc->phase++; + return; case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: - res = db_fetch_using_templates_inventory (connection, - hc, - uc); break; case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: - GNUNET_break (0); - use_reply_with_error ( - uc, - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - "template_contract has unexpected type"); - break; + GNUNET_assert (0); } - if (GNUNET_OK == res) - uc->phase = USE_PHASE_VERIFY; + + for (unsigned int i = 0; + i < uc->parse_request.inventory.items_len; + i++) + { + struct InventoryTemplateItemContext *item = + &uc->parse_request.inventory.items[i]; + enum GNUNET_DB_QueryStatus qs; + + qs = TMH_db->lookup_product (TMH_db->cls, + mi->settings.id, + item->product_id, + &item->pd, + &item->num_categories, + &item->categories); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + GNUNET_break (0); + use_reply_with_error (uc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "lookup_product"); + return; + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + use_reply_with_error (uc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "lookup_product"); + return; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + use_reply_with_error (uc, + MHD_HTTP_NOT_FOUND, + TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN, + item->product_id); + return; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; + } + } + uc->phase++; } -/* Helpers for USE_PHASE_VERIFY. */ +/* *************** Helpers for USE_PHASE_VERIFY ***************** */ /** - * Check if a product ID appears in the selected_products list. + * Check if the given product ID appears in the array of allowed_products. * - * @param selected_products JSON array of selected product IDs, may be NULL + * @param allowed_products JSON array of product IDs allowed by the template, may be NULL * @param product_id product ID to check - * @return true if the product ID is selected + * @return true if the product ID is in the list */ static bool -product_id_selected (const json_t *selected_products, - const char *product_id) +product_id_allowed (const json_t *allowed_products, + const char *product_id) { const json_t *entry; size_t idx; - if (NULL == selected_products) + if (NULL == allowed_products) return false; - json_array_foreach ((json_t *) selected_products, idx, entry) + json_array_foreach ((json_t *) allowed_products, idx, entry) { if (! json_is_string (entry)) + { + GNUNET_break (0); continue; + } if (0 == strcmp (json_string_value (entry), product_id)) return true; @@ -941,29 +885,35 @@ product_id_selected (const json_t *selected_products, /** * Check if any product category is in the selected_categories list. * - * @param selected_categories JSON array of selected category IDs, may be NULL + * @param allowed_categories JSON array of categories allowed by the template, may be NULL * @param num_categories length of @a categories - * @param categories list of category IDs for the product - * @return true if any category matches + * @param categories list of categories of the selected product + * @return true if any category of the product is in the list of allowed categories matches */ static bool -categories_selected (const json_t *selected_categories, - size_t num_categories, - const uint64_t *categories) +category_allowed (const json_t *allowed_categories, + size_t num_categories, + const uint64_t categories[num_categories]) { const json_t *entry; size_t idx; - if (NULL == selected_categories) + if (NULL == allowed_categories) return false; - json_array_foreach ((json_t *) selected_categories, idx, entry) + json_array_foreach ((json_t *) allowed_categories, idx, entry) { uint64_t selected_id; if (! json_is_integer (entry)) + { + GNUNET_break (0); continue; + } if (0 > json_integer_value (entry)) + { + GNUNET_break (0); continue; + } selected_id = (uint64_t) json_integer_value (entry); for (size_t i = 0; i < num_categories; i++) { @@ -976,713 +926,595 @@ categories_selected (const json_t *selected_categories, /** - * Compute the line total for a product based on quantity. + * Verify request data for inventory templates. + * Checks that the selected products are allowed + * for this template. * - * @param unit_price price per unit - * @param quantity integer quantity - * @param quantity_frac fractional quantity (0..TALER_MERCHANT_UNIT_FRAC_BASE-1) - * @param[out] line_total resulting line total + * @param[in,out] uc use context * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue -compute_line_total (const struct TALER_Amount *unit_price, - uint64_t quantity, - uint32_t quantity_frac, - struct TALER_Amount *line_total) +verify_using_templates_inventory (struct UseContext *uc) { - struct TALER_Amount tmp; - - if (GNUNET_OK != - TALER_amount_set_zero (unit_price->currency, - line_total)) + if (uc->parse_template.inventory.choose_one && + (1 != uc->parse_request.inventory.items_len)) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "compute_line_total: failed to init total for currency %s\n", - unit_price->currency); + GNUNET_break_op (0); + use_reply_with_error (uc, + MHD_HTTP_CONFLICT, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "inventory_selection"); return GNUNET_SYSERR; } - if (0 != quantity) - { - if (0 > - TALER_amount_multiply (line_total, - unit_price, - (uint32_t) quantity)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "compute_line_total: multiply failed for quantity %lu" - " in %s\n", - quantity, - unit_price->currency); - return GNUNET_SYSERR; - } - } - if (0 != quantity_frac) + if (uc->parse_template.inventory.selected_all) + return GNUNET_OK; + for (unsigned int i = 0; + i < uc->parse_request.inventory.items_len; + i++) { - if (0 > - TALER_amount_multiply (&tmp, - unit_price, - quantity_frac)) + struct InventoryTemplateItemContext *item = + &uc->parse_request.inventory.items[i]; + const char *eparam = NULL; + + if (GNUNET_OK != + TALER_MERCHANT_vk_process_quantity_inputs ( + TALER_MERCHANT_VK_QUANTITY, + item->pd.allow_fractional_quantity, + true, + 0, + false, + item->unit_quantity, + &item->quantity_value, + &item->quantity_frac, + &eparam)) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "compute_line_total: frac multiply failed for frac %u in %s\n", - quantity_frac, - unit_price->currency); + GNUNET_break_op (0); + use_reply_with_error (uc, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + eparam); return GNUNET_SYSERR; } - TALER_amount_divide (&tmp, - &tmp, - TALER_MERCHANT_UNIT_FRAC_BASE); - if (0 > - TALER_amount_add (line_total, - line_total, - &tmp)) + + if (0 == item->pd.price_array_length) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "compute_line_total: frac add failed in %s\n", - unit_price->currency); + GNUNET_break (0); + use_reply_with_error (uc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + "price_array"); return GNUNET_SYSERR; } } + + for (unsigned int i = 0; + i < uc->parse_request.inventory.items_len; + i++) + { + struct InventoryTemplateItemContext *item = + &uc->parse_request.inventory.items[i]; + + if (product_id_allowed (uc->parse_template.inventory.selected_products, + item->product_id)) + continue; + if (category_allowed ( + uc->parse_template.inventory.selected_categories, + item->num_categories, + item->categories)) + continue; + GNUNET_break_op (0); + use_reply_with_error ( + uc, + MHD_HTTP_CONFLICT, + TALER_EC_GENERIC_PARAMETER_MALFORMED, /* FIXME: bad EC! */ + item->product_id); + return GNUNET_SYSERR; + } return GNUNET_OK; } /** - * Compute totals for all currencies shared across selected products. + * Verify request data for fixed-order templates. + * As here we cannot compute the total amount, either + * the template or the client request must provide it. * * @param[in,out] uc use context * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue -compute_totals_per_currency (struct UseContext *uc) +verify_using_templates_fixed ( + struct UseContext *uc) { - struct TALER_Amount line_total; - struct InventoryTemplateItemContext *items = - uc->parse_request.template.inventory.items; - unsigned int items_len = uc->parse_request.template.inventory.items_len; - - if (0 == items_len) + if ( (! uc->parse_request.no_amount) && + (! uc->parse_template.no_tamount) ) { - GNUNET_break (0); + GNUNET_break_op (0); + use_reply_with_error (uc, + MHD_HTTP_CONFLICT, + TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT, + NULL); return GNUNET_SYSERR; } - - for (size_t i = 0; i < items[0].pd.price_array_length; i++) - { - const char *currency = items[0].pd.price_array[i].currency; - - if (! TMH_test_exchange_configured_for_currency (currency)) - continue; - - GNUNET_array_append (uc->verify.totals, - uc->verify.totals_len, - (struct TALER_Amount) { 0 }); - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero ( - currency, - &uc->verify.totals[uc->verify.totals_len - 1])); - } - - if (0 == uc->verify.totals_len) - return GNUNET_OK; - - /* Loop through items, ensure each currency exists and sum totals. */ - for (unsigned int i = 0; i < items_len; i++) + if (uc->parse_request.no_amount && + uc->parse_template.no_tamount) { - const struct InventoryTemplateItemContext *item = &items[i]; - for (unsigned int c = 0; c < uc->verify.totals_len;) - { - const struct TALER_Amount *unit_price = NULL; - - for (size_t j = 0; j < item->pd.price_array_length; j++) - { - if (0 == strcmp (item->pd.price_array[j].currency, - uc->verify.totals[c].currency)) - { - unit_price = &item->pd.price_array[j]; - break; - } - } - if (NULL == unit_price) - { - /* Just drop the currency, that we can't find in one of the items*/ - uc->verify.totals[c] = uc->verify.totals[uc->verify.totals_len - 1]; - uc->verify.totals_len--; - continue; - } - if (GNUNET_OK != - compute_line_total (unit_price, - item->quantity_value, - item->quantity_frac, - &line_total)) - return GNUNET_SYSERR; - if (0 > - TALER_amount_add (&uc->verify.totals[c], - &uc->verify.totals[c], - &line_total)) - return GNUNET_SYSERR; - c++; - } + GNUNET_break_op (0); + use_reply_with_error (uc, + MHD_HTTP_CONFLICT, + TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_AMOUNT, + NULL); + return GNUNET_SYSERR; } - - return (0 == uc->verify.totals_len) - ? GNUNET_SYSERR // FIXME: at least some logging would be nice... - : GNUNET_OK; + return GNUNET_OK; } /** - * Compute total for a specific currency. + * Verify request data for paivana templates. * - * @param[in] items inventory items - * @param[in] items_len length of @a items - * @param[in] currency currency to total - * @param[out] total computed total + * @param[in,out] uc use context * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue -compute_inventory_total (const struct InventoryTemplateItemContext *items, - unsigned int items_len, - const char *currency, - struct TALER_Amount *total) +verify_using_templates_paivana ( + struct UseContext *uc) { - struct TALER_Amount line_total; + /* TODO: PAIVANA include paivana-specific verification. */ + return verify_using_templates_fixed (uc); +} + + +/** + * Verify that the client request is structurally acceptable for the specified + * template. Does NOT check the total amount being reasonable. + * + * @param[in,out] uc use context + */ +static void +handle_phase_verify ( + struct UseContext *uc) +{ + enum GNUNET_GenericReturnValue res = GNUNET_SYSERR; - if (NULL == currency) + if ( (NULL != uc->parse_request.summary) && + (NULL != uc->parse_template.tsummary) ) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "compute_inventory_total: currency is NULL\n"); - return GNUNET_SYSERR; + GNUNET_break_op (0); + use_reply_with_error (uc, + MHD_HTTP_CONFLICT, + TALER_EC_MERCHANT_POST_USING_TEMPLATES_SUMMARY_CONFLICT_TEMPLATES_CONTRACT_SUBJECT, + NULL); + return; } - if (GNUNET_OK != - TALER_amount_set_zero (currency, - total)) + if ( (NULL == uc->parse_request.summary) && + (NULL == uc->parse_template.tsummary) ) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "compute_inventory_total: failed to init total for %s\n", - currency); - return GNUNET_SYSERR; + GNUNET_break_op (0); + use_reply_with_error (uc, + MHD_HTTP_CONFLICT, + TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_SUMMARY, + NULL); + return; } - for (unsigned int i = 0; i < items_len; i++) + if ( (! uc->parse_request.no_amount) && + (NULL != uc->parse_template.tcurrency) && + (0 != strcasecmp (uc->parse_template.tcurrency, + uc->parse_request.amount.currency)) ) { - const struct InventoryTemplateItemContext *item = &items[i]; - const struct TALER_Amount *unit_price = NULL; - - for (size_t j = 0; j < item->pd.price_array_length; j++) - { - if (0 == strcmp (item->pd.price_array[j].currency, - currency)) - { - unit_price = &item->pd.price_array[j]; - break; - } - } - if (NULL == unit_price) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "compute_inventory_total: missing price in %s for %s\n", - currency, - item->product_id); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - compute_line_total (unit_price, - item->quantity_value, - item->quantity_frac, - &line_total)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "compute_inventory_total: line total failed for %s in %s\n", - item->product_id, - currency); - return GNUNET_SYSERR; - } - if (0 > - TALER_amount_add (total, - total, - &line_total)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "compute_inventory_total: add failed for %s in %s\n", - item->product_id, - currency); - return GNUNET_SYSERR; - } + GNUNET_break_op (0); + use_reply_with_error (uc, + MHD_HTTP_CONFLICT, + TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, + uc->parse_template.tcurrency); + return; } - - return GNUNET_OK; + switch (uc->template_type) + { + case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: + res = verify_using_templates_fixed (uc); + break; + case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: + /* TODO: PAIVANA handle paivana-specific instantiation. */ + res = verify_using_templates_paivana (uc); + break; + case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: + res = verify_using_templates_inventory (uc); + break; + case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: + GNUNET_assert (0); + } + if (GNUNET_OK == res) + uc->phase++; } -/* ***************** USE_PHASE_VERIFY **************** */ +/* ***************** USE_PHASE_COMPUTE_PRICE **************** */ + /** - * Verify request data for inventory templates. + * Compute the line total for a product based on quantity. * - * @param connection connection to reply on - * @param uc use context + * @param unit_price price per unit + * @param quantity integer quantity + * @param quantity_frac fractional quantity (0..TALER_MERCHANT_UNIT_FRAC_BASE-1) + * @param[out] line_total resulting line total * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue -verify_using_templates_inventory (struct MHD_Connection *connection, - struct UseContext *uc) +compute_line_total (const struct TALER_Amount *unit_price, + uint64_t quantity, + uint32_t quantity_frac, + struct TALER_Amount *line_total) { - struct GNUNET_JSON_Specification tcspec[] = { - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("summary", - &uc->verify.template.inventory.tsummary), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_array_const ("selected_categories", - &uc->db_fetch.inventory.selected_categories), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_array_const ("selected_products", - &uc->db_fetch.inventory.selected_products), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("choose_one", - &uc->verify.template.inventory.choose_one), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("request_tip", - &uc->verify.template.inventory.request_tip), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("selected_all", - &uc->db_fetch.inventory.selected_all), - NULL), - GNUNET_JSON_spec_end () - }; - const char *err_name; - unsigned int err_line; - enum GNUNET_GenericReturnValue res; + struct TALER_Amount tmp; - res = GNUNET_JSON_parse (uc->db_fetch.etp.template_contract, - tcspec, - &err_name, - &err_line); - if (GNUNET_OK != res) + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (unit_price->currency, + line_total)); + if ( (0 != quantity) && + (0 > + TALER_amount_multiply (line_total, + unit_price, + (uint32_t) quantity)) ) { GNUNET_break (0); - use_reply_with_error (uc, - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - err_name); return GNUNET_SYSERR; } - - if ( (NULL != uc->parse_request.summary) && - (NULL != uc->verify.template.inventory.tsummary) ) + if (0 == quantity_frac) + return GNUNET_OK; + if (0 > + TALER_amount_multiply (&tmp, + unit_price, + quantity_frac)) { - GNUNET_break_op (0); - use_reply_with_error ( - uc, - connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_POST_USING_TEMPLATES_SUMMARY_CONFLICT_TEMPLATES_CONTRACT_SUBJECT, - NULL); + GNUNET_break (0); return GNUNET_SYSERR; } - if ( (NULL == uc->parse_request.summary) && - (NULL == uc->verify.template.inventory.tsummary) ) + TALER_amount_divide (&tmp, + &tmp, + TALER_MERCHANT_UNIT_FRAC_BASE); + if (0 > + TALER_amount_add (line_total, + line_total, + &tmp)) { - GNUNET_break_op (0); - use_reply_with_error (uc, - connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_SUMMARY, - NULL); + GNUNET_break (0); return GNUNET_SYSERR; } - uc->verify.template.inventory.no_summary = (NULL == - uc->parse_request.summary); + return GNUNET_OK; +} - if (! uc->parse_request.template.inventory.no_tip && - ! uc->verify.template.inventory.request_tip) + +/** + * Find the price of the given @a item in the specified + * @a currency. + * + * @param currency currency to search price in + * @param item item to check prices of + * @return NULL if a suitable price was not found + */ +static const struct TALER_Amount * +find_item_price_in_currency ( + const char *currency, + const struct InventoryTemplateItemContext *item) +{ + for (size_t j = 0; j < item->pd.price_array_length; j++) { - GNUNET_break_op (0); - use_reply_with_error (uc, - connection, - MHD_HTTP_CONFLICT, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "tip"); - return GNUNET_SYSERR; + if (0 == strcasecmp (item->pd.price_array[j].currency, + currency)) + return &item->pd.price_array[j]; } + return NULL; +} + - if (uc->verify.template.inventory.choose_one && - (1 != uc->parse_request.template.inventory.items_len)) +/** + * Compute totals for all currencies shared across selected products. + * + * @param[in,out] uc use context + * @return #GNUNET_OK on success (including no price due to no items) + * #GNUNET_NO if we could not find a price in any accepted currency + * for all selected products + * #GNUNET_SYSERR on arithmetic issues (internal error) + */ +static enum GNUNET_GenericReturnValue +compute_totals_per_currency (struct UseContext *uc) +{ + const struct InventoryTemplateItemContext *items + = uc->parse_request.inventory.items; + unsigned int items_len = uc->parse_request.inventory.items_len; + + if (0 == items_len) + return GNUNET_OK; + for (size_t i = 0; i < items[0].pd.price_array_length; i++) { - GNUNET_break_op (0); - use_reply_with_error (uc, - connection, - MHD_HTTP_CONFLICT, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "inventory_selection"); - return GNUNET_SYSERR; - } + const struct TALER_Amount *price + = &items[0].pd.price_array[i]; + struct TALER_Amount zero; - for (unsigned int i = 0; i < uc->parse_request.template.inventory.items_len; - i++) + if (! TMH_test_exchange_configured_for_currency (price->currency)) + continue; + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (price->currency, + &zero)); + GNUNET_array_append (uc->compute_price.totals, + uc->compute_price.totals_len, + zero); + } + if (0 == uc->compute_price.totals_len) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "No currency supported by our configuration in which we have prices for first selected product!\n"); + return GNUNET_NO; + } + /* Loop through items, ensure each currency exists and sum totals. */ + for (unsigned int i = 0; i < items_len; i++) { - struct InventoryTemplateItemContext *item = - &uc->parse_request.template.inventory.items[i]; - const char *eparam = NULL; + const struct InventoryTemplateItemContext *item = &items[i]; + unsigned int c = 0; - if (! uc->db_fetch.inventory.selected_all) + while (c < uc->compute_price.totals_len) { - bool allowed = false; + struct TALER_Amount *total = &uc->compute_price.totals[c]; + const struct TALER_Amount *unit_price; + struct TALER_Amount line_total; - if (product_id_selected (uc->db_fetch.inventory.selected_products, - item->product_id)) - allowed = true; - else if (categories_selected (uc->db_fetch.inventory.selected_categories, - item->num_categories, - item->categories)) - allowed = true; - - if (! allowed) + unit_price = find_item_price_in_currency (total->currency, + item); + if (NULL == unit_price) + { + /* Drop the currency: we have no price in one of + the selected products */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Product `%s' has no price in %s: dropping currency\n", + item->product_id, + total->currency); + *total = uc->compute_price.totals[--uc->compute_price.totals_len]; + continue; + } + if (GNUNET_OK != + compute_line_total (unit_price, + item->quantity_value, + item->quantity_frac, + &line_total)) { - use_reply_with_error ( - uc, - connection, - MHD_HTTP_CONFLICT, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "inventory_selection(selected product is not available for this template)"); + GNUNET_break (0); return GNUNET_SYSERR; } - } - - if (GNUNET_OK != - TALER_MERCHANT_vk_process_quantity_inputs ( - TMH_VK_QUANTITY, - item->pd.allow_fractional_quantity, - true, - 0, - false, - item->unit_quantity, - &item->quantity_value, - &item->quantity_frac, - &eparam)) - { - use_reply_with_error (uc, - connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - eparam); - return GNUNET_SYSERR; - } - - if (0 == item->pd.price_array_length) - { - GNUNET_break (0); - use_reply_with_error (uc, - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - "price_array"); - return GNUNET_SYSERR; + if (0 > + TALER_amount_add (total, + total, + &line_total)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + c++; } } - - if (uc->parse_request.template.inventory.no_amount && - uc->parse_request.template.inventory.no_tip) + if (0 == uc->compute_price.totals_len) { - if (GNUNET_OK != - compute_totals_per_currency (uc)) - { - use_reply_with_error (uc, - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - "calculation of currency totals failed"); - return GNUNET_SYSERR; - } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "No currency available in which we have prices for all selected products!\n"); + GNUNET_free (uc->compute_price.totals); } + return (0 == uc->compute_price.totals_len) + ? GNUNET_NO + : GNUNET_OK; +} - if (! uc->parse_request.template.inventory.no_amount || - ! uc->parse_request.template.inventory.no_tip) + +/** + * Compute total for only the given @a currency. + * + * @param items_len length of @a items + * @param items inventory items + * @param currency currency to total + * @param[out] total computed total + * @return #GNUNET_OK on success + * #GNUNET_NO if we could not find a price in any accepted currency + * for all selected products + * #GNUNET_SYSERR on arithmetic issues (internal error) + */ +static enum GNUNET_GenericReturnValue +compute_inventory_total (unsigned int items_len, + const struct InventoryTemplateItemContext *items, + const char *currency, + struct TALER_Amount *total) +{ + GNUNET_assert (NULL != currency); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (currency, + total)); + for (unsigned int i = 0; i < items_len; i++) { - const char *primary_currency = - uc->parse_request.template.inventory.no_amount - ? uc->parse_request.template.inventory.tip.currency - : uc->parse_request.template.inventory.amount.currency; - - GNUNET_array_append (uc->verify.totals, - uc->verify.totals_len, - (struct TALER_Amount) { 0 }); - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (primary_currency, - &uc->verify.totals[0])); - if (GNUNET_OK != - compute_inventory_total (uc->parse_request.template.inventory.items, - uc->parse_request.template.inventory.items_len, - primary_currency, - &uc->verify.totals[0])) + const struct InventoryTemplateItemContext *item = &items[i]; + const struct TALER_Amount *unit_price; + struct TALER_Amount line_total; + + unit_price = find_item_price_in_currency (currency, + item); + if (NULL == unit_price) { - use_reply_with_error (uc, - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - "total amount calculation failed"); - return GNUNET_SYSERR; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "compute_inventory_total: no price in %s for product `%s'\n", + currency, + item->product_id); + return GNUNET_NO; } - } - - if (! uc->parse_request.template.inventory.no_tip) - { - if (GNUNET_YES != - TALER_amount_cmp_currency (&uc->parse_request.template.inventory.tip, - &uc->verify.totals[0])) + if (GNUNET_OK != + compute_line_total (unit_price, + item->quantity_value, + item->quantity_frac, + &line_total)) { - /* YOU NEVER SUPPOSED TO REACH IT */ - GNUNET_break_op (0); - use_reply_with_error (uc, - connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, - uc->parse_request.template.inventory.tip.currency); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "compute_inventory_total: line total failed for %s in %s\n", + item->product_id, + currency); return GNUNET_SYSERR; } if (0 > - TALER_amount_add (&uc->verify.totals[0], - &uc->verify.totals[0], - &uc->parse_request.template.inventory.tip)) + TALER_amount_add (total, + total, + &line_total)) { GNUNET_break (0); - use_reply_with_error (uc, - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, - "tip"); return GNUNET_SYSERR; } } - - if (! uc->parse_request.template.inventory.no_amount && - (0 != TALER_amount_cmp (&uc->parse_request.template.inventory.amount, - &uc->verify.totals[0]))) - { - GNUNET_break_op (0); - use_reply_with_error (uc, - connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT, - NULL); - return GNUNET_SYSERR; - } return GNUNET_OK; } /** - * Verify request data for fixed-order templates. + * Compute total price. * - * @param connection connection to reply on - * @param uc use context - * @return #GNUNET_OK on success + * @param[in,out] uc use context */ -static enum GNUNET_GenericReturnValue -verify_using_templates_fixed (struct MHD_Connection *connection, - struct UseContext *uc) +static void +handle_phase_compute_price (struct UseContext *uc) { - uint32_t min_age; - struct GNUNET_TIME_Relative pay_duration; - struct GNUNET_JSON_Specification tspec[] = { - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("summary", - &uc->verify.template.fixed.tsummary), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("currency", - &uc->verify.template.fixed.tcurrency), - NULL), - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount_any ("amount", - &uc->verify.template.fixed.tamount), - &uc->verify.template.fixed.no_tamount), - GNUNET_JSON_spec_uint32 ("minimum_age", - &min_age), - GNUNET_JSON_spec_relative_time ("pay_duration", - &pay_duration), - GNUNET_JSON_spec_end () - }; - enum GNUNET_GenericReturnValue res; - const char *err_name; - unsigned int err_line; + const char *primary_currency; + enum GNUNET_GenericReturnValue ret; - /* USELESS STUFF FROM AI - memset (&uc->verify.template.fixed, - 0, - sizeof (uc->verify.template.fixed)); - */ - - res = GNUNET_JSON_parse (uc->db_fetch.etp.template_contract, - tspec, - &err_name, - &err_line); - if (GNUNET_OK != res) + switch (uc->template_type) { - GNUNET_break (0); - json_dumpf (uc->db_fetch.etp.template_contract, - stderr, - JSON_INDENT (2)); - use_reply_with_error (uc, - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - err_name); - return GNUNET_SYSERR; + case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: + uc->phase++; + return; + case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: + uc->phase++; + return; + case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: + break; + case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: + GNUNET_assert (0); } - - if ( (! uc->parse_request.template.fixed.no_amount) && - (! uc->verify.template.fixed.no_tamount) ) + primary_currency = uc->parse_template.tcurrency; + if (! uc->parse_request.no_amount) + primary_currency = uc->parse_request.amount.currency; + if (! uc->parse_request.no_tip) + primary_currency = uc->parse_request.tip.currency; + if (NULL == primary_currency) { - GNUNET_break_op (0); - use_reply_with_error (uc, - connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT, - NULL); - return GNUNET_SYSERR; + ret = compute_totals_per_currency (uc); } - - if ( (! uc->parse_request.template.fixed.no_amount) && - (NULL != uc->verify.template.fixed.tcurrency) && - (0 != strcmp (uc->verify.template.fixed.tcurrency, - uc->parse_request.template.fixed.amount.currency)) ) + else + { + uc->compute_price.totals + = GNUNET_new (struct TALER_Amount); + uc->compute_price.totals_len + = 1; + ret = compute_inventory_total (uc->parse_request.inventory.items_len, + uc->parse_request.inventory.items, + primary_currency, + uc->compute_price.totals); + } + if (GNUNET_SYSERR == ret) { - GNUNET_break_op (0); use_reply_with_error (uc, - connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, - uc->verify.template.fixed.tcurrency); - return GNUNET_SYSERR; + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, // FIXME: bad EC, use arithmetic error + "calculation of currency totals failed"); + return; } - - if (uc->parse_request.template.fixed.no_amount && - uc->verify.template.fixed.no_tamount) + if (GNUNET_NO == ret) { - GNUNET_break_op (0); use_reply_with_error (uc, - connection, MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_AMOUNT, - NULL); - return GNUNET_SYSERR; + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, // FIXME: bad EC! + "failed to find prices in any acceptable currency for all selected products"); + return; } - if (! uc->parse_request.template.fixed.no_tip) + uc->phase++; +} + + +/* ***************** USE_PHASE_CHECK_TIP **************** */ + + +/** + * Check that tip specified is reasonable and add to total. + * + * @param[in,out] uc use context + */ +static void +handle_phase_check_tip (struct UseContext *uc) +{ + struct TALER_Amount *total_amount; + + if (uc->parse_request.no_tip) { - const struct TALER_Amount *total_amount; - - total_amount = uc->parse_request.template.fixed.no_amount - ? &uc->verify.template.fixed.tamount - : &uc->parse_request.template.fixed.amount; - if (GNUNET_YES != - TALER_amount_cmp_currency (&uc->parse_request.template.fixed.tip, - total_amount)) - { - GNUNET_break_op (0); - use_reply_with_error (uc, - connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, - uc->parse_request.template.fixed.tip.currency); - return GNUNET_SYSERR; - } + uc->phase++; + return; } - - if ( (NULL != uc->parse_request.summary) && - (NULL != uc->verify.template.fixed.tsummary) ) + // FIXME: not quite right if we do not have compute_price total! + total_amount = &uc->compute_price.totals[0]; + if (GNUNET_YES != + TALER_amount_cmp_currency (&uc->parse_request.tip, + total_amount)) { GNUNET_break_op (0); use_reply_with_error (uc, - connection, MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_POST_USING_TEMPLATES_SUMMARY_CONFLICT_TEMPLATES_CONTRACT_SUBJECT, - NULL); - return GNUNET_SYSERR; + TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, + uc->parse_request.tip.currency); + return; } - - if ( (NULL == uc->parse_request.summary) && - (NULL == uc->verify.template.fixed.tsummary) ) + if (0 > + TALER_amount_add (total_amount, + total_amount, + &uc->parse_request.tip)) { - GNUNET_break_op (0); + GNUNET_break (0); use_reply_with_error (uc, - connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_SUMMARY, - NULL); - return GNUNET_SYSERR; + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, + "tip"); + return; } - uc->verify.template.fixed.no_summary = (NULL == uc->parse_request.summary); - - return GNUNET_OK; + uc->phase++; } +/* ***************** USE_PHASE_CHECK_TOTAL **************** */ + /** - * Verify request data for paivana templates. + * Check that if the client specified a total, + * it matches our own calculation. * - * @param connection connection to reply on - * @param uc use context - * @return #GNUNET_OK on success + * @param[in,out] uc use context */ -static enum GNUNET_GenericReturnValue -verify_using_templates_paivana (struct MHD_Connection *connection, - struct UseContext *uc) -{ - /* TODO: PAIVANA include paivana-specific verification. */ - return verify_using_templates_fixed (connection, - uc); -} - - static void -handle_phase_verify (struct MHD_Connection *connection, - struct UseContext *uc) +handle_phase_check_total (struct UseContext *uc) { - enum GNUNET_GenericReturnValue res = GNUNET_SYSERR; - - switch (uc->template_type) + // FIXME: not quite right if we do not have compute_price total! + if (! uc->parse_request.no_amount && + (0 != TALER_amount_cmp (&uc->parse_request.amount, + &uc->compute_price.totals[0]))) { - case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: - res = verify_using_templates_fixed (connection, - uc); - break; - case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: - /* TODO: PAIVANA handle paivana-specific instantiation. */ - res = verify_using_templates_paivana (connection, - uc); - break; - case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: - res = verify_using_templates_inventory (connection, - uc); - break; - case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: - GNUNET_break (0); - use_reply_with_error ( - uc, - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - "template_contract has unexpected type"); - break; + GNUNET_break_op (0); + use_reply_with_error (uc, + MHD_HTTP_CONFLICT, + TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT, + NULL); + return; } - if (GNUNET_OK == res) - uc->phase = USE_PHASE_CREATE_ORDER; + uc->phase++; } /* ***************** USE_PHASE_CREATE_ORDER **************** */ +// FIXME: review entirely from here... + + /** * Create order request for inventory templates. * - * @param connection connection to reply on * @param uc use context * @return #GNUNET_OK on success */ @@ -1691,15 +1523,16 @@ create_using_templates_inventory (struct UseContext *uc) { json_t *inventory_products; json_t *tip_products = NULL; - const bool multi_choices = uc->parse_request.template.inventory.no_amount && - uc->parse_request.template.inventory.no_tip; + const bool multi_choices = uc->parse_request.no_amount && + uc->parse_request.no_tip; if (NULL != uc->ihc.request_body) return GNUNET_OK; - if (! uc->parse_request.template.inventory.no_tip) + if (! uc->parse_request.no_tip) { tip_products = json_array (); + GNUNET_assert (NULL != tip_products); GNUNET_assert (0 == json_array_append_new ( tip_products, @@ -1710,15 +1543,15 @@ create_using_templates_inventory (struct UseContext *uc) 1), TALER_JSON_pack_amount ( "price", - &uc->parse_request.template.inventory.tip)))); + &uc->parse_request.tip)))); } inventory_products = json_array (); GNUNET_assert (NULL != inventory_products); - for (size_t i = 0; i < uc->parse_request.template.inventory.items_len; i++) + for (size_t i = 0; i < uc->parse_request.inventory.items_len; i++) { const struct InventoryTemplateItemContext *item = - &uc->parse_request.template.inventory.items[i]; + &uc->parse_request.inventory.items[i]; GNUNET_assert (0 == json_array_append_new ( @@ -1743,25 +1576,27 @@ create_using_templates_inventory (struct UseContext *uc) choices, GNUNET_JSON_PACK ( TALER_JSON_pack_amount ("amount", - &uc->verify.totals[0])))); + &uc->compute_price.totals[0]))) + ); } else { - for (size_t i = 0; i < uc->verify.totals_len; i++) + for (size_t i = 0; i < uc->compute_price.totals_len; i++) { GNUNET_assert (0 == json_array_append_new ( choices, GNUNET_JSON_PACK ( TALER_JSON_pack_amount ("amount", - &uc->verify.totals[i])))); + &uc->compute_price.totals[i]) + ))); } } body = GNUNET_JSON_PACK ( GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("otp_id", - uc->db_fetch.etp.otp_id)), + uc->lookup_template.etp.otp_id)), GNUNET_JSON_pack_array_steal ("inventory_products", inventory_products), GNUNET_JSON_pack_object_steal ( @@ -1772,8 +1607,8 @@ create_using_templates_inventory (struct UseContext *uc) GNUNET_JSON_pack_array_steal ("choices", choices), GNUNET_JSON_pack_string ("summary", - uc->verify.template.inventory.no_summary - ? uc->verify.template.inventory.tsummary + NULL == uc->parse_request.summary + ? uc->parse_template.tsummary : uc->parse_request.summary), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("fulfillment_url", @@ -1787,10 +1622,6 @@ create_using_templates_inventory (struct UseContext *uc) GNUNET_assert (NULL != body); uc->ihc.request_body = body; } - cleanup_inventory_items (&uc->parse_request.template.inventory.items, - &uc->parse_request.template.inventory.items_len); - cleanup_inventory_totals (&uc->verify.totals, - &uc->verify.totals_len); return GNUNET_OK; } @@ -1809,7 +1640,7 @@ create_using_templates_fixed (struct UseContext *uc) if (NULL != uc->ihc.request_body) return GNUNET_OK; - if (! uc->parse_request.template.fixed.no_tip) + if (! uc->parse_request.no_tip) { tip_products = json_array (); GNUNET_assert (NULL != tip_products); @@ -1823,7 +1654,7 @@ create_using_templates_fixed (struct UseContext *uc) 1), TALER_JSON_pack_amount ( "price", - &uc->parse_request.template.fixed.tip)))); + &uc->parse_request.tip)))); } { json_t *body; @@ -1831,19 +1662,19 @@ create_using_templates_fixed (struct UseContext *uc) body = GNUNET_JSON_PACK ( GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("otp_id", - uc->db_fetch.etp.otp_id)), + uc->lookup_template.etp.otp_id)), GNUNET_JSON_pack_object_steal ( "order", GNUNET_JSON_PACK ( TALER_JSON_pack_amount ( "amount", - uc->parse_request.template.fixed.no_amount - ? &uc->verify.template.fixed.tamount - : &uc->parse_request.template.fixed.amount), + uc->parse_request.no_amount + ? &uc->parse_template.tamount + : &uc->parse_request.amount), GNUNET_JSON_pack_string ( "summary", - uc->verify.template.fixed.no_summary - ? uc->verify.template.fixed.tsummary + NULL == uc->parse_request.summary + ? uc->parse_template.tsummary : uc->parse_request.summary), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("fulfillment_url", @@ -1876,8 +1707,7 @@ create_using_templates_paivana (struct UseContext *uc) static void -handle_phase_create_order (struct MHD_Connection *connection, - struct UseContext *uc) +handle_phase_create_order (struct UseContext *uc) { enum GNUNET_GenericReturnValue res = GNUNET_SYSERR; @@ -1897,14 +1727,13 @@ handle_phase_create_order (struct MHD_Connection *connection, GNUNET_break (0); use_reply_with_error ( uc, - connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, "template_contract has unexpected type"); break; } if (GNUNET_OK == res) - uc->phase = USE_PHASE_SUBMIT_ORDER; + uc->phase++; } @@ -1920,6 +1749,7 @@ TMH_post_using_templates_ID (const struct TMH_RequestHandler *rh, if (NULL == uc) { uc = GNUNET_new (struct UseContext); + uc->hc = hc; hc->ctx = uc; hc->cc = &cleanup_use_context; uc->ihc.instance = hc->instance; @@ -1932,27 +1762,31 @@ TMH_post_using_templates_ID (const struct TMH_RequestHandler *rh, switch (uc->phase) { case USE_PHASE_PARSE_REQUEST: - handle_phase_parse_request (connection, - hc, - uc); + handle_phase_parse_request (uc); break; case USE_PHASE_LOOKUP_TEMPLATE: - handle_phase_lookup_template (connection, - hc, - uc); + handle_phase_lookup_template (uc); + break; + case USE_PHASE_PARSE_TEMPLATE: + handle_phase_parse_template (uc); break; case USE_PHASE_DB_FETCH: - handle_phase_db_fetch (connection, - hc, - uc); + handle_phase_db_fetch (uc); break; case USE_PHASE_VERIFY: - handle_phase_verify (connection, - uc); + handle_phase_verify (uc); + break; + case USE_PHASE_COMPUTE_PRICE: + handle_phase_compute_price (uc); + break; + case USE_PHASE_CHECK_TIP: + handle_phase_check_tip (uc); + break; + case USE_PHASE_CHECK_TOTAL: + handle_phase_check_total (uc); break; case USE_PHASE_CREATE_ORDER: - handle_phase_create_order (connection, - uc); + handle_phase_create_order (uc); break; case USE_PHASE_SUBMIT_ORDER: return TMH_private_post_orders ( diff --git a/src/backend/taler-merchant-httpd_private-patch-templates-ID.c b/src/backend/taler-merchant-httpd_private-patch-templates-ID.c @@ -87,30 +87,6 @@ determine_cause (struct MHD_Connection *connection, /** - * Validate template contract based on its type. - * - * @param template_contract JSON template contract - * @return true if valid - */ -static bool -validate_template_contract_by_type (const json_t *template_contract) -{ - /* For some special different post/patch checks - otherwise change/check the TMH_template */ - switch (TMH_template_type_from_contract (template_contract)) - { - case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: - case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: - case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: - return TMH_template_contract_valid (template_contract); - case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: - break; - } - return false; -} - - -/** * PATCH configuration of an existing instance, given its configuration. * * @param rh context of the handler @@ -157,7 +133,7 @@ TMH_private_patch_templates_ID (const struct TMH_RequestHandler *rh, : MHD_NO; } - if (! validate_template_contract_by_type (tp.template_contract)) + if (! TMH_template_contract_valid (tp.template_contract)) { GNUNET_break_op (0); GNUNET_JSON_parse_free (spec); diff --git a/src/backend/taler-merchant-httpd_private-post-templates.c b/src/backend/taler-merchant-httpd_private-post-templates.c @@ -58,30 +58,6 @@ templates_equal (const struct TALER_MERCHANTDB_TemplateDetails *t1, } -/** - * Validate template contract based on its type. - * - * @param template_contract JSON template contract - * @return true if valid - */ -static bool -validate_template_contract_by_type (const json_t *template_contract) -{ - /* For some special different post/patch checks - otherwise change/check the TMH_template */ - switch (TMH_template_type_from_contract (template_contract)) - { - case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: - case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: - case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: - return TMH_template_contract_valid (template_contract); - case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: - break; - } - return false; -} - - MHD_RESULT TMH_private_post_templates (const struct TMH_RequestHandler *rh, struct MHD_Connection *connection, @@ -125,7 +101,7 @@ TMH_private_post_templates (const struct TMH_RequestHandler *rh, : MHD_NO; } } - if (! validate_template_contract_by_type (tp.template_contract)) + if (! TMH_template_contract_valid (tp.template_contract)) { GNUNET_break_op (0); json_dumpf (tp.template_contract, diff --git a/src/lib/merchant_api_post_templates.c b/src/lib/merchant_api_post_templates.c @@ -32,7 +32,7 @@ #include "merchant_api_common.h" #include <taler/taler_json_lib.h> #include <taler/taler_curl_lib.h> -#include <taler/taler_merchant_util.h> +#include "taler_merchant_util.h" /** diff --git a/src/lib/merchant_api_post_using_templates.c b/src/lib/merchant_api_post_using_templates.c @@ -174,6 +174,8 @@ TALER_MERCHANT_using_templates_post ( json_t *req_obj; req_obj = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("template_type", + "fixed-order"), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("summary", summary)), diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c @@ -49,7 +49,7 @@ * commands should NOT wait for this timeout! */ #define POLL_ORDER_TIMEOUT \ - GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 60) + GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 60) /** * The 'poll-orders-conclude-1x' and other 'conclude' @@ -57,7 +57,7 @@ * here we use a short value! */ #define POLL_ORDER_SHORT_TIMEOUT \ - GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 2) + GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 2) /** * Configuration file we use. One (big) configuration is used @@ -288,8 +288,8 @@ cmd_exec_wirewatch (const char *label) * @param label label to use for the command. */ #define CMD_EXEC_AGGREGATOR(label) \ - TALER_TESTING_cmd_exec_aggregator (label "-aggregator", config_file), \ - TALER_TESTING_cmd_exec_transfer (label "-transfer", config_file) + TALER_TESTING_cmd_exec_aggregator (label "-aggregator", config_file), \ + TALER_TESTING_cmd_exec_transfer (label "-transfer", config_file) /** @@ -1996,6 +1996,8 @@ run (void *cls, GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("amount", "EUR:1"), + GNUNET_JSON_pack_string ("template_type", + "inventory-cart"), GNUNET_JSON_pack_array_steal ( "inventory_selection", make_inventory_selection ("inv-product-1", @@ -2030,6 +2032,8 @@ run (void *cls, GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("amount", "EUR:3"), + GNUNET_JSON_pack_string ("template_type", + "inventory-cart"), GNUNET_JSON_pack_array_steal ( "inventory_selection", make_inventory_selection ("inv-product-1", @@ -2046,6 +2050,8 @@ run (void *cls, GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("amount", "EUR:1"), + GNUNET_JSON_pack_string ("template_type", + "inventory-cart"), GNUNET_JSON_pack_array_steal ( "inventory_selection", json_array ())), @@ -2059,6 +2065,8 @@ run (void *cls, GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("amount", "EUR:3"), + GNUNET_JSON_pack_string ("template_type", + "inventory-cart"), GNUNET_JSON_pack_array_steal ( "inventory_selection", make_inventory_selection ("inv-product-1", @@ -2077,6 +2085,8 @@ run (void *cls, "EUR:1"), GNUNET_JSON_pack_string ("tip", "EUR:1"), + GNUNET_JSON_pack_string ("template_type", + "inventory-cart"), GNUNET_JSON_pack_array_steal ( "inventory_selection", make_inventory_selection ("inv-product-1", @@ -2093,6 +2103,8 @@ run (void *cls, GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("amount", "EUR:1"), + GNUNET_JSON_pack_string ("template_type", + "inventory-cart"), GNUNET_JSON_pack_array_steal ( "inventory_selection", make_inventory_selection ("inv-product-404", @@ -2127,6 +2139,8 @@ run (void *cls, GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("amount", "EUR:2"), + GNUNET_JSON_pack_string ("template_type", + "inventory-cart"), GNUNET_JSON_pack_array_steal ( "inventory_selection", make_inventory_selection ("inv-product-2", @@ -2143,6 +2157,8 @@ run (void *cls, GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("amount", "EUR:4"), + GNUNET_JSON_pack_string ("template_type", + "inventory-cart"), GNUNET_JSON_pack_array_steal ( "inventory_selection", make_inventory_selection ("inv-product-1", @@ -2260,6 +2276,8 @@ run (void *cls, GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("amount", "EUR:3.2"), + GNUNET_JSON_pack_string ("template_type", + "inventory-cart"), GNUNET_JSON_pack_array_steal ( "inventory_selection", make_inventory_selection ("inv-unit-product-1",