merchant

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

commit 240c6d73f99db07f479d57c4a15f8236b401ced6
parent b337fd6de7d3eb57b4be5d6cfa93e1c562658528
Author: Christian Grothoff <grothoff@gnunet.org>
Date:   Tue, 13 Jan 2026 07:28:38 +0900

fix memory leak

Diffstat:
Msrc/backend/taler-merchant-httpd_post-using-templates.c | 2303++++++++++++++++++++++++++++++++++++++++++-------------------------------------
1 file changed, 1222 insertions(+), 1081 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_post-using-templates.c b/src/backend/taler-merchant-httpd_post-using-templates.c @@ -30,103 +30,46 @@ #include "taler-merchant-httpd_exchanges.h" #include <taler/taler_json_lib.h> -// FIXME: further split template-specific logic to reduce redundancy. -// Make structs for each phase, so easier cleanup and more readable checks -// add commented sections like in orders - /** - * Context for inventory template processing. + * Item selected from inventory_selection. */ -struct InventoryTemplateContext +struct InventoryTemplateItemContext { /** - * Selected products from inventory_selection. - */ - struct InventoryTemplateItem - { - /** - * Product ID as referenced in inventory. - */ - char *product_id; - - /** - * Unit quantity string as provided by the client. - */ - char *unit_quantity; - - /** - * Parsed integer quantity. - */ - uint64_t quantity_value; - - /** - * Parsed fractional quantity. - */ - uint32_t quantity_frac; - - /** - * Product details from the DB (includes price array). - */ - struct TALER_MERCHANTDB_ProductDetails pd; - - /** - * Categories referenced by the product. - */ - uint64_t *categories; - - /** - * Length of @e categories. - */ - size_t num_categories; - } *items; - - /** - * Length of @e items. - */ - unsigned int items_len; - - /** - * Selected categories from the template contract. + * Product ID as referenced in inventory. */ - const json_t *selected_categories; + char *product_id; /** - * Selected products from the template contract. + * Unit quantity string as provided by the client. */ - const json_t *selected_products; + char *unit_quantity; /** - * Whether all products are selectable. + * Parsed integer quantity. */ - bool selected_all; + uint64_t quantity_value; /** - * Optional pre-calculated amount provided by the client. + * Parsed fractional quantity. */ - struct TALER_Amount amount; + uint32_t quantity_frac; /** - * Optional tip amount. + * Product details from the DB (includes price array). */ - struct TALER_Amount tip; + struct TALER_MERCHANTDB_ProductDetails pd; /** - * Currency totals shared across selected products. - * Without amount.currency + * Categories referenced by the product. */ - struct InventoryTemplateCurrency - { - /** - * Total amount for this currency (without tips) - */ - struct TALER_Amount total; - } *currencies; + uint64_t *categories; /** - * Length of @e currencies. + * Length of @e categories. */ - unsigned int currencies_len; + size_t num_categories; }; @@ -136,14 +79,14 @@ struct InventoryTemplateContext enum UsePhase { /** - * Fetch template details from the database. - */ - USE_PHASE_LOOKUP_TEMPLATE, - /** * Parse request payload into context fields. */ USE_PHASE_PARSE_REQUEST, /** + * Fetch template details from the database. + */ + USE_PHASE_LOOKUP_TEMPLATE, + /** * Load DB-backed details needed for verification. */ USE_PHASE_DB_FETCH, @@ -188,113 +131,247 @@ struct UseContext enum TALER_MERCHANT_TemplateType template_type; /** - * Summary override from request, if any. - */ - const char *summary; - - /** - * Fulfillment URL override from request, if any. - */ - const char *fulfillment_url; - - /** - * Fulfillment message override from request, if any. - */ - const char *fulfillment_message; - - /** - * Parsed fields for fixed-order templates. + * Information set in the #USE_PHASE_PARSE_REQUEST phase. */ 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; - /** - * Summary from the template contract. - */ - const char *tsummary; - /** - * Currency from the template contract. + * Summary override from request, if any. */ - const char *tcurrency; + const char *summary; + /** - * Amount from the template contract. + * Fulfillment URL override from request, if any. */ - struct TALER_Amount tamount; + const char *fulfillment_url; + /** - * True if @e tamount was not provided. + * Fulfillment message override from request, if any. */ - bool no_tamount; + const char *fulfillment_message; + /** - * True if no summary override was provided. + * Parsed fields for each template type. */ - bool no_summary; - } fixed; + union + { + /** + * Parsed fields for fixed-order templates. + */ + 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; + + /** + * Parsed fields for inventory templates. + */ + 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; + + } parse_request; /** - * Parsed fields for inventory templates. + * Information set in the #USE_PHASE_DB_FETCH phase. */ struct { /** - * True if @e amount was not provided. + * Our template details from the DB. */ - bool no_amount; + struct TALER_MERCHANTDB_TemplateDetails etp; + /** - * True if @e tip was not provided. + * True once @e etp was initialized. */ - bool no_tip; + bool have_etp; + /** - * Template requires exactly one selection. + * Inventory template selections from the contract. */ - bool choose_one; + struct + { + /** + * Selected categories from the template contract. + */ + const json_t *selected_categories; + + /** + * Selected products from the template contract. + */ + const json_t *selected_products; + + /** + * Whether all products are selectable. + */ + bool selected_all; + } inventory; + } db_fetch; + + /** + * Information set in the #USE_PHASE_VERIFY phase. + */ + struct + { /** - * Template allows tips. + * Verified fields for each template type. */ - bool request_tip; + union + { + /** + * Verified fields for fixed-order templates. + */ + 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; + + /** + * Verified fields for inventory templates. + */ + 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; + /** - * Summary from the template contract. + * Per-currency totals across selected products (without tips). */ - const char *tsummary; + struct TALER_Amount *totals; + /** - * True if no summary override was provided. + * Length of @e totals. */ - bool no_summary; - } inventory; + unsigned int totals_len; + } verify; +}; - /** - * Inventory template processing context. - */ - struct InventoryTemplateContext itc; - /** - * Our template details from the DB. - */ - struct TALER_MERCHANTDB_TemplateDetails etp; +/** + * Clean up inventory items. + * + * @param items item array to free + * @param items_len length of @a items + */ +static void +cleanup_inventory_items (struct InventoryTemplateItemContext **items, + unsigned int *items_len) +{ + if ((NULL == items) || (NULL == *items)) + return; + for (unsigned int i = 0; i < *items_len; i++) + { + struct InventoryTemplateItemContext *item = &(*items)[i]; - /** - * True once @e etp was initialized. - */ - bool have_etp; -}; + 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_template_context (void *cls); +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; +} /** @@ -307,8 +384,14 @@ cleanup_use_context (void *cls) { struct UseContext *uc = cls; - TALER_MERCHANTDB_template_details_free (&uc->etp); - cleanup_inventory_template_context (&uc->itc); + 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); + } if (NULL != uc->ihc.cc) uc->ihc.cc (uc->ihc.ctx); GNUNET_free (uc->ihc.infix); @@ -377,127 +460,577 @@ use_reply_with_error (struct UseContext *uc, } -/** - * Check if a product ID appears in the selected_products list. - * - * @param selected_products JSON array of selected product IDs, may be NULL - * @param product_id product ID to check - * @return true if the product ID is selected - */ -static bool -product_id_selected (const json_t *selected_products, - const char *product_id) -{ - const json_t *entry; - size_t idx; - - if (NULL == selected_products) - return false; - json_array_foreach ((json_t *) selected_products, idx, entry) - { - if (! json_is_string (entry)) - continue; - if (0 == strcmp (json_string_value (entry), - product_id)) - return true; - } - return false; -} - - -/** - * Check if any product category is in the selected_categories list. - * - * @param selected_categories JSON array of selected category IDs, 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 - */ -static bool -categories_selected (const json_t *selected_categories, - size_t num_categories, - const uint64_t *categories) -{ - const json_t *entry; - size_t idx; - - if (NULL == selected_categories) - return false; - json_array_foreach ((json_t *) selected_categories, idx, entry) - { - uint64_t selected_id; - - if (! json_is_integer (entry)) - continue; - if (0 > json_integer_value (entry)) - continue; - selected_id = (uint64_t) json_integer_value (entry); - for (size_t i = 0; i < num_categories; i++) - { - if (categories[i] == selected_id) - return true; - } - } - return false; -} - +/* ***************** USE_PHASE_PARSE_REQUEST **************** */ /** - * Compute the line total for a product based on quantity. + * Parse request data for inventory templates. * - * @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 connection connection to reply on + * @param hc handler context + * @param 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) +parse_using_templates_inventory_request (struct MHD_Connection *connection, + struct TMH_HandlerContext *hc, + struct UseContext *uc) { - struct TALER_Amount tmp; + if (NULL != uc->ihc.request_body) + return GNUNET_OK; - if (GNUNET_OK != - TALER_amount_set_zero (unit_price->currency, - line_total)) + const json_t *inventory_selection = NULL; + + 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), + 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, + spec); + if (GNUNET_OK != res) { - 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_finalize_parse (uc, + res); return GNUNET_SYSERR; } - if (0 != quantity) + + if ( (! uc->parse_request.template.inventory.no_amount) || + (! uc->parse_request.template.inventory.no_tip) ) { - if (0 > - TALER_amount_multiply (line_total, - unit_price, - (uint32_t) quantity)) + 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)) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "compute_line_total: multiply failed for quantity %lu" - " in %s\n", - quantity, - unit_price->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"); return GNUNET_SYSERR; } } - if (0 != quantity_frac) + + if (! uc->parse_request.template.inventory.no_tip) { - if (0 > - TALER_amount_multiply (&tmp, - unit_price, - quantity_frac)) + if (! uc->parse_request.template.inventory.no_amount && + (GNUNET_YES != + TALER_amount_cmp_currency ( + &uc->parse_request.template.inventory.amount, + &uc->parse_request.template.inventory.tip))) { - 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); + 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"); return GNUNET_SYSERR; } - TALER_amount_divide (&tmp, - &tmp, + } + + for (size_t i = 0; i < json_array_size (inventory_selection); i++) + { + const char *product_id; + const char *quantity; + struct GNUNET_JSON_Specification ispec[] = { + GNUNET_JSON_spec_string ("product_id", + &product_id), + GNUNET_JSON_spec_string ("quantity", + &quantity), + GNUNET_JSON_spec_end () + }; + const char *err_name; + unsigned int err_line; + + res = GNUNET_JSON_parse (json_array_get (inventory_selection, + i), + ispec, + &err_name, + &err_line); + 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); + } + } + if (0 == uc->parse_request.template.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; + } + return GNUNET_OK; +} + + +/** + * Parse request data for fixed-order 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_fixed_request (struct MHD_Connection *connection, + struct TMH_HandlerContext *hc, + struct UseContext *uc) +{ + 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.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), + GNUNET_JSON_spec_end () + }; + enum GNUNET_GenericReturnValue res; + + res = TALER_MHD_parse_json_data (connection, + 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! + switch (uc->template_type) + { + case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: + res = parse_using_templates_fixed_request (connection, + hc, + uc); + break; + case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: + /* TODO: PAIVANA handle paivana-specific instantiation. */ + res = parse_using_templates_paivana_request (connection, + hc, + uc); + break; + case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: + res = parse_using_templates_inventory_request (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; + } + if (GNUNET_OK == res) + uc->phase = USE_PHASE_DB_FETCH; +} + + +/* ***************** USE_PHASE_LOOKUP_TEMPLATE **************** */ + +static void +handle_phase_lookup_template (struct MHD_Connection *connection, + struct TMH_HandlerContext *hc, + struct UseContext *uc) +{ + struct TMH_MerchantInstance *mi = hc->instance; + const char *template_id = hc->infix; + + if (! uc->db_fetch.have_etp) + { + 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; + } + uc->template_type = + TMH_template_type_from_contract (uc->db_fetch.etp.template_contract); + if (TALER_MERCHANT_TEMPLATE_TYPE_INVALID == uc->template_type) + { + 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"); + return; + } + uc->phase = USE_PHASE_PARSE_REQUEST; +} + + +/* ***************** USE_PHASE_DB_FETCH **************** */ + +/** + * Fetch DB data for inventory templates. + * + * @param connection connection to reply on + * @param hc handler context + * @param uc use context + * @return #GNUNET_OK on success + */ +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) +{ + enum GNUNET_GenericReturnValue res = GNUNET_OK; + + switch (uc->template_type) + { + case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: + case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: + break; + 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; + } + if (GNUNET_OK == res) + uc->phase = USE_PHASE_VERIFY; +} + + +/* Helpers for USE_PHASE_VERIFY. */ + +/** + * Check if a product ID appears in the selected_products list. + * + * @param selected_products JSON array of selected product IDs, may be NULL + * @param product_id product ID to check + * @return true if the product ID is selected + */ +static bool +product_id_selected (const json_t *selected_products, + const char *product_id) +{ + const json_t *entry; + size_t idx; + + if (NULL == selected_products) + return false; + json_array_foreach ((json_t *) selected_products, idx, entry) + { + if (! json_is_string (entry)) + continue; + if (0 == strcmp (json_string_value (entry), + product_id)) + return true; + } + return false; +} + + +/** + * Check if any product category is in the selected_categories list. + * + * @param selected_categories JSON array of selected category IDs, 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 + */ +static bool +categories_selected (const json_t *selected_categories, + size_t num_categories, + const uint64_t *categories) +{ + const json_t *entry; + size_t idx; + + if (NULL == selected_categories) + return false; + json_array_foreach ((json_t *) selected_categories, idx, entry) + { + uint64_t selected_id; + + if (! json_is_integer (entry)) + continue; + if (0 > json_integer_value (entry)) + continue; + selected_id = (uint64_t) json_integer_value (entry); + for (size_t i = 0; i < num_categories; i++) + { + if (categories[i] == selected_id) + return true; + } + } + return false; +} + + +/** + * Compute the line total for a product based on quantity. + * + * @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 +compute_line_total (const struct TALER_Amount *unit_price, + uint64_t quantity, + uint32_t quantity_frac, + struct TALER_Amount *line_total) +{ + struct TALER_Amount tmp; + + if (GNUNET_OK != + TALER_amount_set_zero (unit_price->currency, + line_total)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "compute_line_total: failed to init total for currency %s\n", + unit_price->currency); + 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 (0 > + TALER_amount_multiply (&tmp, + unit_price, + quantity_frac)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "compute_line_total: frac multiply failed for frac %u in %s\n", + quantity_frac, + unit_price->currency); + return GNUNET_SYSERR; + } + TALER_amount_divide (&tmp, + &tmp, TALER_MERCHANT_UNIT_FRAC_BASE); if (0 > TALER_amount_add (line_total, @@ -515,82 +1048,56 @@ compute_line_total (const struct TALER_Amount *unit_price, /** - * Clean up a `struct InventoryTemplateContext *`. - * - * @param cls a `struct InventoryTemplateContext *` - */ -static void -cleanup_inventory_template_context (void *cls) -{ - struct InventoryTemplateContext *ctx = cls; - - if (NULL == ctx) - return; - for (unsigned int i = 0; i < ctx->items_len; i++) - { - struct InventoryTemplateItem *item = &ctx->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 (ctx->items); - GNUNET_free (ctx->currencies); - memset (ctx, - 0, - sizeof (*ctx)); -} - - -/** * Compute totals for all currencies shared across selected products. * - * @param[in,out] ctx inventory template context + * @param[in,out] uc use context * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue -compute_totals_per_currency (struct InventoryTemplateContext *ctx) +compute_totals_per_currency (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 == ctx->items_len) + if (0 == items_len) { GNUNET_break (0); return GNUNET_SYSERR; } - for (size_t i = 0; i < ctx->items[0].pd.price_array_length; i++) + for (size_t i = 0; i < items[0].pd.price_array_length; i++) { - const char *currency = ctx->items[0].pd.price_array[i].currency; + const char *currency = items[0].pd.price_array[i].currency; if (! TMH_test_exchange_configured_for_currency (currency)) continue; - GNUNET_array_append (ctx->currencies, - ctx->currencies_len, - (struct InventoryTemplateCurrency) { 0 }); + GNUNET_array_append (uc->verify.totals, + uc->verify.totals_len, + (struct TALER_Amount) { 0 }); GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (currency, - &ctx->currencies[ctx->currencies_len - - 1].total)); + TALER_amount_set_zero ( + currency, + &uc->verify.totals[uc->verify.totals_len - 1])); } - if (0 == ctx->currencies_len) + 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 < ctx->items_len; i++) + for (unsigned int i = 0; i < items_len; i++) { - const struct InventoryTemplateItem *item = &ctx->items[i]; - for (unsigned int c = 0; c < ctx->currencies_len;) + 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, - ctx->currencies[c].total.currency)) + uc->verify.totals[c].currency)) { unit_price = &item->pd.price_array[j]; break; @@ -599,8 +1106,8 @@ compute_totals_per_currency (struct InventoryTemplateContext *ctx) if (NULL == unit_price) { /* Just drop the currency, that we can't find in one of the items*/ - ctx->currencies[c] = ctx->currencies[ctx->currencies_len - 1]; - ctx->currencies_len--; + uc->verify.totals[c] = uc->verify.totals[uc->verify.totals_len - 1]; + uc->verify.totals_len--; continue; } if (GNUNET_OK != @@ -610,15 +1117,15 @@ compute_totals_per_currency (struct InventoryTemplateContext *ctx) &line_total)) return GNUNET_SYSERR; if (0 > - TALER_amount_add (&ctx->currencies[c].total, - &ctx->currencies[c].total, + TALER_amount_add (&uc->verify.totals[c], + &uc->verify.totals[c], &line_total)) return GNUNET_SYSERR; c++; } } - return (0 == ctx->currencies_len) + return (0 == uc->verify.totals_len) ? GNUNET_SYSERR // FIXME: at least some logging would be nice... : GNUNET_OK; } @@ -626,296 +1133,89 @@ compute_totals_per_currency (struct InventoryTemplateContext *ctx) /** * Compute total for a specific currency. - * - * @param[in] ctx inventory template context - * @param[in] currency currency to total - * @param[out] total computed total - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -compute_inventory_total (const struct InventoryTemplateContext *ctx, - const char *currency, - struct TALER_Amount *total) -{ - struct TALER_Amount line_total; - - if (NULL == currency) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "compute_inventory_total: currency is NULL\n"); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_amount_set_zero (currency, - total)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "compute_inventory_total: failed to init total for %s\n", - currency); - return GNUNET_SYSERR; - } - for (unsigned int i = 0; i < ctx->items_len; i++) - { - const struct InventoryTemplateItem *item = &ctx->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; - } - } - - return GNUNET_OK; -} - - -/** - * Parse request data for inventory 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_inventory_request (struct MHD_Connection *connection, - struct TMH_HandlerContext *hc, - struct UseContext *uc) -{ - const json_t *inventory_selection = NULL; - cleanup_inventory_template_context (&uc->itc); - uc->summary = NULL; - uc->inventory.no_amount = false; - uc->inventory.no_tip = false; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("summary", - &uc->summary), - NULL), - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount_any ("amount", - &uc->itc.amount), - &uc->inventory.no_amount), - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount_any ("tip", - &uc->itc.tip), - &uc->inventory.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, - spec); - if (GNUNET_OK != res) - { - GNUNET_break_op (0); - use_finalize_parse (uc, - res); - return GNUNET_SYSERR; - } - - if ( (! uc->inventory.no_amount) || - (! uc->inventory.no_tip) ) - { - if (! TMH_test_exchange_configured_for_currency ( - (! uc->inventory.no_amount) - ? uc->itc.amount.currency - : uc->itc.tip.currency)) - { - GNUNET_break_op (0); - cleanup_inventory_template_context (&uc->itc); - use_reply_with_error (uc, - connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, - "Currency for amount is not supported by backend"); - return GNUNET_SYSERR; - } - } - - if (! uc->inventory.no_tip) - { - if (! uc->inventory.no_amount && - (GNUNET_YES != - TALER_amount_cmp_currency (&uc->itc.amount, - &uc->itc.tip))) - { - GNUNET_break_op (0); - cleanup_inventory_template_context (&uc->itc); - use_reply_with_error (uc, - connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, - "Mismatch of currencies between the 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 GNUNET_JSON_Specification ispec[] = { - GNUNET_JSON_spec_string ("product_id", - &product_id), - GNUNET_JSON_spec_string ("quantity", - &quantity), - GNUNET_JSON_spec_end () - }; - const char *err_name; - unsigned int err_line; - - res = GNUNET_JSON_parse (json_array_get (inventory_selection, - i), - ispec, - &err_name, - &err_line); - if (GNUNET_OK != res) - { - GNUNET_break_op (0); - cleanup_inventory_template_context (&uc->itc); - use_reply_with_error (uc, - connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "inventory_selection"); - return GNUNET_SYSERR; - } - - { - struct InventoryTemplateItem item; - - memset (&item, - 0, - sizeof (item)); - item.product_id = GNUNET_strdup (product_id); - item.unit_quantity = GNUNET_strdup (quantity); - GNUNET_array_append (uc->itc.items, - uc->itc.items_len, - item); - } - } - if (0 == uc->itc.items_len) - { - GNUNET_break_op (0); - cleanup_inventory_template_context (&uc->itc); - use_reply_with_error (uc, - connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MISSING, - "inventory_selection"); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** - * Fetch DB data for inventory templates. - * - * @param connection connection to reply on - * @param hc handler context - * @param uc use context + * + * @param[in] items inventory items + * @param[in] items_len length of @a items + * @param[in] currency currency to total + * @param[out] total computed total * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue -db_fetch_using_templates_inventory (struct MHD_Connection *connection, - struct TMH_HandlerContext *hc, - struct UseContext *uc) +compute_inventory_total (const struct InventoryTemplateItemContext *items, + unsigned int items_len, + const char *currency, + struct TALER_Amount *total) { - struct TMH_MerchantInstance *mi = hc->instance; + struct TALER_Amount line_total; - for (unsigned int i = 0; i < uc->itc.items_len; i++) + if (NULL == currency) { - struct InventoryTemplateItem *item = &uc->itc.items[i]; - struct TALER_MERCHANTDB_ProductDetails pd; - enum GNUNET_DB_QueryStatus qs; - size_t num_categories = 0; - uint64_t *categories = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "compute_inventory_total: currency is NULL\n"); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_amount_set_zero (currency, + total)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "compute_inventory_total: failed to init total for %s\n", + currency); + return GNUNET_SYSERR; + } + for (unsigned int i = 0; i < items_len; i++) + { + const struct InventoryTemplateItemContext *item = &items[i]; + const struct TALER_Amount *unit_price = NULL; - qs = TMH_db->lookup_product (TMH_db->cls, - mi->settings.id, - item->product_id, - &pd, - &num_categories, - &categories); - if (qs <= 0) + for (size_t j = 0; j < item->pd.price_array_length; j++) { - enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; - unsigned int http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; - - switch (qs) + if (0 == strcmp (item->pd.price_array[j].currency, + currency)) { - 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; + unit_price = &item->pd.price_array[j]; break; - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - GNUNET_assert (0); } - use_reply_with_error (uc, - connection, - http_status, - ec, - item->product_id); + } + 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; } - - item->pd = pd; - item->categories = categories; - item->num_categories = num_categories; } + return GNUNET_OK; } +/* ***************** USE_PHASE_VERIFY **************** */ + /** * Verify request data for inventory templates. * @@ -930,27 +1230,27 @@ verify_using_templates_inventory (struct MHD_Connection *connection, struct GNUNET_JSON_Specification tcspec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("summary", - &uc->inventory.tsummary), + &uc->verify.template.inventory.tsummary), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_array_const ("selected_categories", - &uc->itc.selected_categories), + &uc->db_fetch.inventory.selected_categories), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_array_const ("selected_products", - &uc->itc.selected_products), + &uc->db_fetch.inventory.selected_products), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_bool ("choose_one", - &uc->inventory.choose_one), + &uc->verify.template.inventory.choose_one), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_bool ("request_tip", - &uc->inventory.request_tip), + &uc->verify.template.inventory.request_tip), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_bool ("selected_all", - &uc->itc.selected_all), + &uc->db_fetch.inventory.selected_all), NULL), GNUNET_JSON_spec_end () }; @@ -958,14 +1258,7 @@ verify_using_templates_inventory (struct MHD_Connection *connection, unsigned int err_line; enum GNUNET_GenericReturnValue res; - uc->inventory.tsummary = NULL; - uc->inventory.choose_one = false; - uc->inventory.request_tip = false; - uc->itc.selected_categories = NULL; - uc->itc.selected_products = NULL; - uc->itc.selected_all = false; - - res = GNUNET_JSON_parse (uc->etp.template_contract, + res = GNUNET_JSON_parse (uc->db_fetch.etp.template_contract, tcspec, &err_name, &err_line); @@ -980,8 +1273,8 @@ verify_using_templates_inventory (struct MHD_Connection *connection, return GNUNET_SYSERR; } - if ( (NULL != uc->summary) && - (NULL != uc->inventory.tsummary) ) + if ( (NULL != uc->parse_request.summary) && + (NULL != uc->verify.template.inventory.tsummary) ) { GNUNET_break_op (0); use_reply_with_error ( @@ -992,8 +1285,8 @@ verify_using_templates_inventory (struct MHD_Connection *connection, NULL); return GNUNET_SYSERR; } - if ( (NULL == uc->summary) && - (NULL == uc->inventory.tsummary) ) + if ( (NULL == uc->parse_request.summary) && + (NULL == uc->verify.template.inventory.tsummary) ) { GNUNET_break_op (0); use_reply_with_error (uc, @@ -1003,10 +1296,11 @@ verify_using_templates_inventory (struct MHD_Connection *connection, NULL); return GNUNET_SYSERR; } - uc->inventory.no_summary = (NULL == uc->summary); + uc->verify.template.inventory.no_summary = (NULL == + uc->parse_request.summary); - if (! uc->inventory.no_tip && - ! uc->inventory.request_tip) + if (! uc->parse_request.template.inventory.no_tip && + ! uc->verify.template.inventory.request_tip) { GNUNET_break_op (0); use_reply_with_error (uc, @@ -1017,8 +1311,8 @@ verify_using_templates_inventory (struct MHD_Connection *connection, return GNUNET_SYSERR; } - if (uc->inventory.choose_one && - (1 != uc->itc.items_len)) + if (uc->verify.template.inventory.choose_one && + (1 != uc->parse_request.template.inventory.items_len)) { GNUNET_break_op (0); use_reply_with_error (uc, @@ -1029,19 +1323,21 @@ verify_using_templates_inventory (struct MHD_Connection *connection, return GNUNET_SYSERR; } - for (unsigned int i = 0; i < uc->itc.items_len; i++) + for (unsigned int i = 0; i < uc->parse_request.template.inventory.items_len; + i++) { - struct InventoryTemplateItem *item = &uc->itc.items[i]; + struct InventoryTemplateItemContext *item = + &uc->parse_request.template.inventory.items[i]; const char *eparam = NULL; - if (! uc->itc.selected_all) + if (! uc->db_fetch.inventory.selected_all) { bool allowed = false; - if (product_id_selected (uc->itc.selected_products, + if (product_id_selected (uc->db_fetch.inventory.selected_products, item->product_id)) allowed = true; - else if (categories_selected (uc->itc.selected_categories, + else if (categories_selected (uc->db_fetch.inventory.selected_categories, item->num_categories, item->categories)) allowed = true; @@ -1090,11 +1386,11 @@ verify_using_templates_inventory (struct MHD_Connection *connection, } } - if (uc->inventory.no_amount && - uc->inventory.no_tip) + if (uc->parse_request.template.inventory.no_amount && + uc->parse_request.template.inventory.no_tip) { if (GNUNET_OK != - compute_totals_per_currency (&uc->itc)) + compute_totals_per_currency (uc)) { use_reply_with_error (uc, connection, @@ -1105,232 +1401,75 @@ verify_using_templates_inventory (struct MHD_Connection *connection, } } - if (! uc->inventory.no_amount || - ! uc->inventory.no_tip) + if (! uc->parse_request.template.inventory.no_amount || + ! uc->parse_request.template.inventory.no_tip) { - const char *primary_currency = uc->inventory.no_amount - ? uc->itc.tip.currency - : uc->itc.amount.currency; - - GNUNET_array_append (uc->itc.currencies, - uc->itc.currencies_len, - (struct InventoryTemplateCurrency) { 0 }); + 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->itc.currencies[0].total)); + &uc->verify.totals[0])); if (GNUNET_OK != - compute_inventory_total (&uc->itc, + compute_inventory_total (uc->parse_request.template.inventory.items, + uc->parse_request.template.inventory.items_len, primary_currency, - &uc->itc.currencies[0].total)) + &uc->verify.totals[0])) { use_reply_with_error (uc, connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - "total amount calculation failed"); - return GNUNET_SYSERR; - } - } - - if (! uc->inventory.no_tip) - { - if (GNUNET_YES != - TALER_amount_cmp_currency (&uc->itc.tip, - &uc->itc.currencies[0].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->itc.tip.currency); - return GNUNET_SYSERR; - } - if (0 > - TALER_amount_add (&uc->itc.currencies[0].total, - &uc->itc.currencies[0].total, - &uc->itc.tip)) - { - 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->inventory.no_amount && - (0 != TALER_amount_cmp (&uc->itc.amount, - &uc->itc.currencies[0].total))) - { - 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; -} - - -/** - * Create order request for inventory templates. - * - * @param connection connection to reply on - * @param uc use context - * @return #GNUNET_OK on success - */ -static enum GNUNET_GenericReturnValue -create_using_templates_inventory (struct UseContext *uc) -{ - json_t *inventory_products; - json_t *tip_products = NULL; - const bool multi_choices = uc->inventory.no_amount && uc->inventory.no_tip; - - if (NULL != uc->ihc.request_body) - return GNUNET_OK; - - if (! uc->inventory.no_tip) - { - tip_products = json_array (); - GNUNET_assert (0 == - json_array_append_new ( - tip_products, - GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("description", - "tip"), - GNUNET_JSON_pack_uint64 ("quantity", - 1), - TALER_JSON_pack_amount ("price", - &uc->itc.tip)))); - } - - inventory_products = json_array (); - GNUNET_assert (NULL != inventory_products); - for (size_t i = 0; i < uc->itc.items_len; i++) - { - const struct InventoryTemplateItem *item = &uc->itc.items[i]; - - GNUNET_assert (0 == - json_array_append_new ( - inventory_products, - GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("product_id", - item->product_id), - GNUNET_JSON_pack_string ("unit_quantity", - item->unit_quantity)))); - } - - { - json_t *body; - json_t *choices; - - choices = json_array (); - GNUNET_assert (NULL != choices); - if (! multi_choices) - { - GNUNET_assert (0 == - json_array_append_new (choices, - GNUNET_JSON_PACK ( - TALER_JSON_pack_amount ("amount", - &uc->itc. - currencies - [0].total)) - )); - } - else - { - for (size_t i = 0; i < uc->itc.currencies_len; i++) - { - GNUNET_assert (0 == - json_array_append_new ( - choices, - GNUNET_JSON_PACK ( - TALER_JSON_pack_amount ("amount", - &uc->itc.currencies[i].total)))); - } - } - - body = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("otp_id", - uc->etp.otp_id)), - GNUNET_JSON_pack_array_steal ("inventory_products", - inventory_products), - GNUNET_JSON_pack_object_steal ( - "order", - GNUNET_JSON_PACK ( - GNUNET_JSON_pack_uint64 ("version", - 1), - GNUNET_JSON_pack_array_steal ("choices", - choices), - GNUNET_JSON_pack_string ("summary", - uc->inventory.no_summary - ? uc->inventory.tsummary - : uc->summary), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("fulfillment_url", - uc->fulfillment_url)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("fulfillment_message", - uc->fulfillment_message)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_array_steal ("products", - tip_products))))); - GNUNET_assert (NULL != body); - uc->ihc.request_body = body; - } - cleanup_inventory_template_context (&uc->itc); - return GNUNET_OK; -} - - -/** - * Parse request data for fixed-order 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_fixed_request (struct MHD_Connection *connection, - struct TMH_HandlerContext *hc, - struct UseContext *uc) -{ - uc->summary = NULL; - uc->fixed.no_amount = false; - uc->fixed.no_tip = false; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("summary", - &uc->summary), - NULL), - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount_any ("amount", - &uc->fixed.amount), - &uc->fixed.no_amount), - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount_any ("tip", - &uc->fixed.tip), - &uc->fixed.no_tip), - GNUNET_JSON_spec_end () - }; - enum GNUNET_GenericReturnValue res; + "total amount calculation failed"); + return GNUNET_SYSERR; + } + } - res = TALER_MHD_parse_json_data (connection, - hc->request_body, - spec); - if (GNUNET_OK != res) + 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])) + { + /* 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); + return GNUNET_SYSERR; + } + if (0 > + TALER_amount_add (&uc->verify.totals[0], + &uc->verify.totals[0], + &uc->parse_request.template.inventory.tip)) + { + 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_finalize_parse (uc, - res); + 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; @@ -1353,16 +1492,16 @@ verify_using_templates_fixed (struct MHD_Connection *connection, struct GNUNET_JSON_Specification tspec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("summary", - &uc->fixed.tsummary), + &uc->verify.template.fixed.tsummary), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("currency", - &uc->fixed.tcurrency), + &uc->verify.template.fixed.tcurrency), NULL), GNUNET_JSON_spec_mark_optional ( TALER_JSON_spec_amount_any ("amount", - &uc->fixed.tamount), - &uc->fixed.no_tamount), + &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", @@ -1373,18 +1512,20 @@ verify_using_templates_fixed (struct MHD_Connection *connection, const char *err_name; unsigned int err_line; - uc->fixed.tsummary = NULL; - uc->fixed.tcurrency = NULL; - uc->fixed.no_tamount = false; + /* USELESS STUFF FROM AI + memset (&uc->verify.template.fixed, + 0, + sizeof (uc->verify.template.fixed)); + */ - res = GNUNET_JSON_parse (uc->etp.template_contract, + res = GNUNET_JSON_parse (uc->db_fetch.etp.template_contract, tspec, &err_name, &err_line); if (GNUNET_OK != res) { GNUNET_break (0); - json_dumpf (uc->etp.template_contract, + json_dumpf (uc->db_fetch.etp.template_contract, stderr, JSON_INDENT (2)); use_reply_with_error (uc, @@ -1395,8 +1536,8 @@ verify_using_templates_fixed (struct MHD_Connection *connection, return GNUNET_SYSERR; } - if ( (! uc->fixed.no_amount) && - (! uc->fixed.no_tamount) ) + if ( (! uc->parse_request.template.fixed.no_amount) && + (! uc->verify.template.fixed.no_tamount) ) { GNUNET_break_op (0); use_reply_with_error (uc, @@ -1407,21 +1548,22 @@ verify_using_templates_fixed (struct MHD_Connection *connection, return GNUNET_SYSERR; } - if ( (! uc->fixed.no_amount) && - (NULL != uc->fixed.tcurrency) && - (0 != strcmp (uc->fixed.tcurrency, - uc->fixed.amount.currency)) ) + 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)) ) { GNUNET_break_op (0); use_reply_with_error (uc, connection, MHD_HTTP_CONFLICT, TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, - uc->fixed.tcurrency); + uc->verify.template.fixed.tcurrency); return GNUNET_SYSERR; } - if (uc->fixed.no_amount && uc->fixed.no_tamount) + if (uc->parse_request.template.fixed.no_amount && + uc->verify.template.fixed.no_tamount) { GNUNET_break_op (0); use_reply_with_error (uc, @@ -1432,15 +1574,15 @@ verify_using_templates_fixed (struct MHD_Connection *connection, return GNUNET_SYSERR; } - if (! uc->fixed.no_tip) + if (! uc->parse_request.template.fixed.no_tip) { const struct TALER_Amount *total_amount; - total_amount = uc->fixed.no_amount - ? &uc->fixed.tamount - : &uc->fixed.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->fixed.tip, + TALER_amount_cmp_currency (&uc->parse_request.template.fixed.tip, total_amount)) { GNUNET_break_op (0); @@ -1448,13 +1590,13 @@ verify_using_templates_fixed (struct MHD_Connection *connection, connection, MHD_HTTP_CONFLICT, TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, - uc->fixed.tip.currency); + uc->parse_request.template.fixed.tip.currency); return GNUNET_SYSERR; } } - if ( (NULL != uc->summary) && - (NULL != uc->fixed.tsummary) ) + if ( (NULL != uc->parse_request.summary) && + (NULL != uc->verify.template.fixed.tsummary) ) { GNUNET_break_op (0); use_reply_with_error (uc, @@ -1465,8 +1607,8 @@ verify_using_templates_fixed (struct MHD_Connection *connection, return GNUNET_SYSERR; } - if ( (NULL == uc->summary) && - (NULL == uc->fixed.tsummary) ) + if ( (NULL == uc->parse_request.summary) && + (NULL == uc->verify.template.fixed.tsummary) ) { GNUNET_break_op (0); use_reply_with_error (uc, @@ -1476,30 +1618,88 @@ verify_using_templates_fixed (struct MHD_Connection *connection, NULL); return GNUNET_SYSERR; } - uc->fixed.no_summary = (NULL == uc->summary); + uc->verify.template.fixed.no_summary = (NULL == uc->parse_request.summary); return GNUNET_OK; } /** - * Create order request for fixed-order templates. + * Verify request data for paivana templates. * + * @param connection connection to reply on * @param uc use context * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue -create_using_templates_fixed (struct UseContext *uc) +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) +{ + enum GNUNET_GenericReturnValue res = GNUNET_SYSERR; + + switch (uc->template_type) + { + 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; + } + if (GNUNET_OK == res) + uc->phase = USE_PHASE_CREATE_ORDER; +} + + +/* ***************** USE_PHASE_CREATE_ORDER **************** */ + +/** + * Create order request for inventory templates. + * + * @param connection connection to reply on + * @param uc use context + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +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; if (NULL != uc->ihc.request_body) return GNUNET_OK; - if (! uc->fixed.no_tip) + if (! uc->parse_request.template.inventory.no_tip) { tip_products = json_array (); - GNUNET_assert (NULL != tip_products); GNUNET_assert (0 == json_array_append_new ( tip_products, @@ -1508,77 +1708,156 @@ create_using_templates_fixed (struct UseContext *uc) "tip"), GNUNET_JSON_pack_uint64 ("quantity", 1), - TALER_JSON_pack_amount ("price", - &uc->fixed.tip)))); + TALER_JSON_pack_amount ( + "price", + &uc->parse_request.template.inventory.tip)))); + } + + inventory_products = json_array (); + GNUNET_assert (NULL != inventory_products); + for (size_t i = 0; i < uc->parse_request.template.inventory.items_len; i++) + { + const struct InventoryTemplateItemContext *item = + &uc->parse_request.template.inventory.items[i]; + + GNUNET_assert (0 == + json_array_append_new ( + inventory_products, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("product_id", + item->product_id), + GNUNET_JSON_pack_string ("unit_quantity", + item->unit_quantity)))); } + { json_t *body; + json_t *choices; + + choices = json_array (); + GNUNET_assert (NULL != choices); + if (! multi_choices) + { + GNUNET_assert (0 == + json_array_append_new ( + choices, + GNUNET_JSON_PACK ( + TALER_JSON_pack_amount ("amount", + &uc->verify.totals[0])))); + } + else + { + for (size_t i = 0; i < uc->verify.totals_len; i++) + { + GNUNET_assert (0 == + json_array_append_new ( + choices, + GNUNET_JSON_PACK ( + TALER_JSON_pack_amount ("amount", + &uc->verify.totals[i])))); + } + } body = GNUNET_JSON_PACK ( GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("otp_id", - uc->etp.otp_id)), + uc->db_fetch.etp.otp_id)), + GNUNET_JSON_pack_array_steal ("inventory_products", + inventory_products), GNUNET_JSON_pack_object_steal ( "order", GNUNET_JSON_PACK ( - TALER_JSON_pack_amount ("amount", - uc->fixed.no_amount - ? &uc->fixed.tamount - : &uc->fixed.amount), + GNUNET_JSON_pack_uint64 ("version", + 1), + GNUNET_JSON_pack_array_steal ("choices", + choices), GNUNET_JSON_pack_string ("summary", - uc->fixed.no_summary - ? uc->fixed.tsummary - : uc->summary), + uc->verify.template.inventory.no_summary + ? uc->verify.template.inventory.tsummary + : uc->parse_request.summary), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("fulfillment_url", - uc->fulfillment_url)), + uc->parse_request.fulfillment_url)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("fulfillment_message", - uc->fulfillment_message)), + uc->parse_request.fulfillment_message)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_array_steal ("products", tip_products))))); + 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; } /** - * Parse request data for paivana templates. + * Create order request for fixed-order 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) +create_using_templates_fixed (struct UseContext *uc) { - /* TODO: PAIVANA include paivana_id in instantiation flow. */ - return parse_using_templates_fixed_request (connection, - hc, - uc); -} + json_t *tip_products = NULL; + if (NULL != uc->ihc.request_body) + return GNUNET_OK; -/** - * Verify request data for paivana templates. - * - * @param connection connection to reply on - * @param uc use context - * @return #GNUNET_OK on success - */ -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); + if (! uc->parse_request.template.fixed.no_tip) + { + tip_products = json_array (); + GNUNET_assert (NULL != tip_products); + GNUNET_assert (0 == + json_array_append_new ( + tip_products, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("description", + "tip"), + GNUNET_JSON_pack_uint64 ("quantity", + 1), + TALER_JSON_pack_amount ( + "price", + &uc->parse_request.template.fixed.tip)))); + } + { + json_t *body; + + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("otp_id", + uc->db_fetch.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), + GNUNET_JSON_pack_string ( + "summary", + uc->verify.template.fixed.no_summary + ? uc->verify.template.fixed.tsummary + : uc->parse_request.summary), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("fulfillment_url", + uc->parse_request.fulfillment_url)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("fulfillment_message", + uc->parse_request.fulfillment_message)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_array_steal ("products", + tip_products))))); + uc->ihc.request_body = body; + } + + return GNUNET_OK; } @@ -1596,13 +1875,46 @@ create_using_templates_paivana (struct UseContext *uc) } +static void +handle_phase_create_order (struct MHD_Connection *connection, + struct UseContext *uc) +{ + enum GNUNET_GenericReturnValue res = GNUNET_SYSERR; + + switch (uc->template_type) + { + case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: + res = create_using_templates_fixed (uc); + break; + case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: + /* TODO: PAIVANA handle paivana-specific instantiation. */ + res = create_using_templates_paivana (uc); + break; + case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: + res = create_using_templates_inventory (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; + } + if (GNUNET_OK == res) + uc->phase = USE_PHASE_SUBMIT_ORDER; +} + + +/* ***************** Main handler **************** */ + MHD_RESULT TMH_post_using_templates_ID (const struct TMH_RequestHandler *rh, struct MHD_Connection *connection, struct TMH_HandlerContext *hc) { - struct TMH_MerchantInstance *mi = hc->instance; - const char *template_id = hc->infix; struct UseContext *uc = hc->ctx; if (NULL == uc) @@ -1611,208 +1923,37 @@ TMH_post_using_templates_ID (const struct TMH_RequestHandler *rh, hc->ctx = uc; hc->cc = &cleanup_use_context; uc->ihc.instance = hc->instance; - uc->phase = USE_PHASE_LOOKUP_TEMPLATE; + uc->phase = USE_PHASE_PARSE_REQUEST; uc->template_type = TALER_MERCHANT_TEMPLATE_TYPE_INVALID; - uc->summary = NULL; - uc->fulfillment_url = NULL; - uc->fulfillment_message = NULL; } while (1) { switch (uc->phase) { + case USE_PHASE_PARSE_REQUEST: + handle_phase_parse_request (connection, + hc, + uc); + break; case USE_PHASE_LOOKUP_TEMPLATE: - if (! uc->have_etp) - { - 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->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->have_etp = true; - break; - } /* End of the switch */ - if (! uc->have_etp) - break; - } - uc->template_type = - TMH_template_type_from_contract (uc->etp.template_contract); - if (TALER_MERCHANT_TEMPLATE_TYPE_INVALID == uc->template_type) - { - 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; - } - uc->phase = USE_PHASE_PARSE_REQUEST; + handle_phase_lookup_template (connection, + hc, + uc); break; - case USE_PHASE_PARSE_REQUEST: - { - enum GNUNET_GenericReturnValue res = GNUNET_SYSERR; - - // FIXME: share more logic between these, just skip (or run) individual - // phases depending on the template type! - switch (uc->template_type) - { - case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: - res = parse_using_templates_fixed_request (connection, - hc, - uc); - break; - case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: - /* TODO: PAIVANA handle paivana-specific instantiation. */ - res = parse_using_templates_paivana_request (connection, - hc, - uc); - break; - case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: - res = parse_using_templates_inventory_request (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; - } - if (GNUNET_OK == res) - uc->phase = USE_PHASE_DB_FETCH; - break; - } case USE_PHASE_DB_FETCH: - { - enum GNUNET_GenericReturnValue res = GNUNET_OK; - - switch (uc->template_type) - { - case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: - case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: - break; - 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; - } - if (GNUNET_OK == res) - uc->phase = USE_PHASE_VERIFY; - break; - } + handle_phase_db_fetch (connection, + hc, + uc); + break; case USE_PHASE_VERIFY: - { - enum GNUNET_GenericReturnValue res = GNUNET_SYSERR; - - switch (uc->template_type) - { - 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; - } - if (GNUNET_OK == res) - uc->phase = USE_PHASE_CREATE_ORDER; - break; - } + handle_phase_verify (connection, + uc); + break; case USE_PHASE_CREATE_ORDER: - { - enum GNUNET_GenericReturnValue res = GNUNET_SYSERR; - - switch (uc->template_type) - { - case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: - res = create_using_templates_fixed (uc); - break; - case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: - /* TODO: PAIVANA handle paivana-specific instantiation. */ - res = create_using_templates_paivana (uc); - break; - case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: - res = create_using_templates_inventory (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; - } - if (GNUNET_OK == res) - uc->phase = USE_PHASE_SUBMIT_ORDER; - break; - } + handle_phase_create_order (connection, + uc); + break; case USE_PHASE_SUBMIT_ORDER: return TMH_private_post_orders ( NULL, /* not even used */