merchant

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

commit 63e43ba0856ce50f833b20344f7d1dd38f699d7b
parent ebe98bd39046e88bb6b2605670cc38e425ccfe4e
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date:   Wed, 14 Jan 2026 01:37:46 +0100

some updates

Diffstat:
Msrc/backend/taler-merchant-httpd_post-using-templates.c | 2028++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
1 file changed, 1245 insertions(+), 783 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_post-using-templates.c b/src/backend/taler-merchant-httpd_post-using-templates.c @@ -30,16 +30,145 @@ #include "taler-merchant-httpd_exchanges.h" #include <taler/taler_json_lib.h> -// FIXME: introduce PHASES (like we do in other handlers) -// and break up the gigantic (and sometimes redundant) -// handlers into clear parts, like parsing the request, -// fetching template, checking inventory/computing price, -// returning errors, etc. +// 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. + */ +struct InventoryTemplateContext +{ + /** + * 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. + */ + const json_t *selected_categories; + + /** + * Selected products from the template contract. + */ + const json_t *selected_products; + + /** + * Whether all products are selectable. + */ + bool selected_all; + + /** + * Optional pre-calculated amount provided by the client. + */ + struct TALER_Amount amount; + + /** + * Optional tip amount. + */ + struct TALER_Amount tip; + + /** + * Currency totals shared across selected products. + * Without amount.currency + */ + struct InventoryTemplateCurrency + { + /** + * Total amount for this currency (without tips) + */ + struct TALER_Amount total; + } *currencies; + + /** + * Length of @e currencies. + */ + unsigned int currencies_len; +}; /** * Our context. */ +enum UsePhase +{ + /** + * Fetch template details from the database. + */ + USE_PHASE_LOOKUP_TEMPLATE, + /** + * Parse request payload into context fields. + */ + USE_PHASE_PARSE_REQUEST, + /** + * Load DB-backed details needed for verification. + */ + USE_PHASE_DB_FETCH, + /** + * Validate request and template compatibility. + */ + USE_PHASE_VERIFY, + /** + * 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. + */ + USE_PHASE_FINISHED_MHD_NO +}; + struct UseContext { /** @@ -49,6 +178,110 @@ struct UseContext struct TMH_HandlerContext ihc; /** + * Phase we are currently in. + */ + enum UsePhase phase; + + /** + * Template type from the contract. + */ + 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. + */ + 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. + */ + 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; + + /** + * Parsed fields for inventory templates. + */ + struct + { + /** + * True if @e amount was not provided. + */ + bool no_amount; + /** + * True if @e tip was not provided. + */ + bool no_tip; + /** + * 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; + + /** + * Inventory template processing context. + */ + struct InventoryTemplateContext itc; + + /** * Our template details from the DB. */ struct TALER_MERCHANTDB_TemplateDetails etp; @@ -60,6 +293,10 @@ struct UseContext }; +static void +cleanup_inventory_template_context (void *cls); + + /** * Clean up a `struct UseContext *` * @@ -71,6 +308,7 @@ cleanup_use_context (void *cls) struct UseContext *uc = cls; TALER_MERCHANTDB_template_details_free (&uc->etp); + cleanup_inventory_template_context (&uc->itc); if (NULL != uc->ihc.cc) uc->ihc.cc (uc->ihc.ctx); json_decref (uc->ihc.request_body); @@ -79,6 +317,66 @@ cleanup_use_context (void *cls) /** + * Finalize a template use request. + * + * @param[in,out] uc use context + * @param ret handler return value + */ +static void +use_finalize (struct UseContext *uc, + MHD_RESULT ret) +{ + uc->phase = (MHD_YES == ret) + ? USE_PHASE_FINISHED_MHD_YES + : USE_PHASE_FINISHED_MHD_NO; +} + + +/** + * Finalize after JSON parsing result. + * + * @param[in,out] uc use context + * @param res parse result + */ +static void +use_finalize_parse (struct UseContext *uc, + enum GNUNET_GenericReturnValue res) +{ + use_finalize (uc, + (GNUNET_NO == res) + ? MHD_YES + : MHD_NO); +} + + +/** + * 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, + http_status, + ec, + detail); + use_finalize (uc, + mret); +} + + +/** * Check if a product ID appears in the selected_products list. * * @param selected_products JSON array of selected product IDs, may be NULL @@ -160,19 +458,29 @@ compute_line_total (const struct TALER_Amount *unit_price, { struct TALER_Amount tmp; - if (quantity > UINT32_MAX) - return GNUNET_SYSERR; 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) { @@ -180,7 +488,13 @@ compute_line_total (const struct TALER_Amount *unit_price, 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); @@ -188,152 +502,49 @@ compute_line_total (const struct TALER_Amount *unit_price, TALER_amount_add (line_total, line_total, &tmp)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "compute_line_total: frac add failed in %s\n", + unit_price->currency); return GNUNET_SYSERR; + } } return GNUNET_OK; } /** - * Context for inventory template processing. + * Clean up a `struct InventoryTemplateContext *`. + * + * @param cls a `struct InventoryTemplateContext *` */ -struct InventoryTemplateContext +static void +cleanup_inventory_template_context (void *cls) { - /** - * 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; + struct InventoryTemplateContext *ctx = cls; - /** - * Parsed integer quantity. - */ - uint64_t quantity_value; + if (NULL == ctx) + return; + for (unsigned int i = 0; i < ctx->items_len; i++) + { + struct InventoryTemplateItem *item = &ctx->items[i]; - /** - * Parsed fractional quantity. - */ - uint32_t quantity_frac; - - /** - * Product details from the DB (includes price array). - */ - struct TALER_MERCHANTDB_ProductDetails pd; - } *items; - - /** - * Length of @e items. - */ - unsigned int items_len; - - /** - * 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; - - /** - * Amount provided by the client. - * FIXME: I don't see it in the spec that the client MUST provide this amount. - */ - struct TALER_Amount amount; - - /** - * Optional tip amount. - */ - struct TALER_Amount tip; - - /** - * Total amount for the requested currency (includes tip when present). - * - * FIXME: this makes no sense, we MAY still have multiple choices - * (and thus different totals!) *if* the user didn't specify a tip - * and there are products in multiple currencies! - */ - struct TALER_Amount total; - - /** - * Currency totals shared across selected products. - * Without amount.currency - */ - struct InventoryTemplateCurrency - { - /** - * Currency string. - */ - char *currency; - - /** - * Total amount for this currency (without tips). - */ - struct TALER_Amount total; - } *currencies; - - /** - * Length of @e currencies. - */ - unsigned int currencies_len; -}; - - -/** - * 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 (ctx->items); - for (unsigned int i = 0; i < ctx->currencies_len; i++) - GNUNET_free (ctx->currencies[i].currency); - GNUNET_free (ctx->currencies); - memset (ctx, - 0, - sizeof (*ctx)); -} + 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. * - * FIXME: this is wild. If the client _already_ provided an "amount" - * this is pointless: we know the currency the payment will be made in. - * (but should still check the total, alas below you just 'skip' - * if the currency matches the client-given amount); - * However, if the client did NOT give us an amount, we need to compute - * per-currency totals. - * * @param[in,out] ctx inventory template context * @return #GNUNET_OK on success */ @@ -352,17 +563,12 @@ compute_totals_per_currency (struct InventoryTemplateContext *ctx) { const char *currency = ctx->items[0].pd.price_array[i].currency; - if (0 == strcmp (currency, - ctx->amount.currency)) - continue; - if (! TMH_test_exchange_configured_for_currency (currency)) continue; + GNUNET_array_append (ctx->currencies, ctx->currencies_len, - (struct InventoryTemplateCurrency) { - .currency = GNUNET_strdup (currency) - }); + (struct InventoryTemplateCurrency) { 0 }); GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (currency, &ctx->currencies[ctx->currencies_len @@ -370,12 +576,10 @@ compute_totals_per_currency (struct InventoryTemplateContext *ctx) } if (0 == ctx->currencies_len) - return GNUNET_OK; // If there is only main currency, all good we just return + return GNUNET_OK; - /* Just loop through items, for each item check that all currencies from item[0] - is in this item, if not remove the currency, in the process pre-calculate - total per currency except the requested currency */ - for (unsigned int i = 1; i < ctx->items_len; i++) + /* Loop through items, ensure each currency exists and sum totals. */ + for (unsigned int i = 0; i < ctx->items_len; i++) { const struct InventoryTemplateItem *item = &ctx->items[i]; for (unsigned int c = 0; c < ctx->currencies_len;) @@ -385,7 +589,7 @@ compute_totals_per_currency (struct InventoryTemplateContext *ctx) for (size_t j = 0; j < item->pd.price_array_length; j++) { if (0 == strcmp (item->pd.price_array[j].currency, - ctx->currencies[c].currency)) + ctx->currencies[c].total.currency)) { unit_price = &item->pd.price_array[j]; break; @@ -393,8 +597,7 @@ compute_totals_per_currency (struct InventoryTemplateContext *ctx) } if (NULL == unit_price) { - // FIXME: some logging would be nice... - GNUNET_free (ctx->currencies[c].currency); + /* 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--; continue; @@ -435,10 +638,21 @@ compute_inventory_total (const struct InventoryTemplateContext *ctx, { 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]; @@ -454,18 +668,36 @@ compute_inventory_total (const struct InventoryTemplateContext *ctx, } } 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; @@ -473,60 +705,41 @@ compute_inventory_total (const struct InventoryTemplateContext *ctx, /** - * Handle POST /templates/$ID for inventory templates. + * Parse request data for inventory templates. * * @param connection connection to reply on * @param hc handler context * @param uc use context - * @param summary summary override (optional) - * @param fulfillment_url fulfillment URL (optional) - * @param fulfillment_message fulfillment message (optional) - * @return MHD result + * @return #GNUNET_OK on success */ -static MHD_RESULT -handle_using_templates_inventory (struct MHD_Connection *connection, - struct TMH_HandlerContext *hc, - struct UseContext *uc, - const char *summary, - const char *fulfillment_url, - const char *fulfillment_message) +static enum GNUNET_GenericReturnValue +parse_using_templates_inventory_request (struct MHD_Connection *connection, + struct TMH_HandlerContext *hc, + struct UseContext *uc) { - struct TMH_MerchantInstance *mi = hc->instance; const json_t *inventory_selection = NULL; - const char *tsummary = NULL; - bool no_tip; - bool choose_one = false; - bool request_tip = false; - bool no_summary; - struct InventoryTemplateContext itc; + 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", - &summary), + &uc->summary), NULL), - // FIXME: spec clearly says "amount" is *optional*! - // The (very common!) new inventory-cart case is one - // where the client did NOT specify any currency yet - // (unless tipping, I guess), and we should still - // generate a v1 order with choices for different - // currencies. - TALER_JSON_spec_amount_any ("amount", - &itc.amount), + 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", - &itc.tip), - &no_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; - json_t *inventory_products; - json_t *tip_products = NULL; - - memset (&itc, - 0, - sizeof (itc)); res = TALER_MHD_parse_json_data (connection, hc->request_body, @@ -534,150 +747,52 @@ handle_using_templates_inventory (struct MHD_Connection *connection, if (GNUNET_OK != res) { GNUNET_break_op (0); - return (GNUNET_NO == res) - ? MHD_YES - : MHD_NO; + use_finalize_parse (uc, + res); + return GNUNET_SYSERR; } + if ( (! uc->inventory.no_amount) || + (! uc->inventory.no_tip) ) { - struct GNUNET_JSON_Specification tcspec[] = { - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("summary", - &tsummary), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_array_const ("selected_categories", - &itc.selected_categories), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_array_const ("selected_products", - &itc.selected_products), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("choose_one", - &choose_one), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("request_tip", - &request_tip), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_bool ("selected_all", - &itc.selected_all), - NULL), - GNUNET_JSON_spec_end () - }; - const char *err_name; - unsigned int err_line; - - res = GNUNET_JSON_parse (uc->etp.template_contract, - tcspec, - &err_name, - &err_line); - if (GNUNET_OK != res) + if (! TMH_test_exchange_configured_for_currency ( + (! uc->inventory.no_amount) + ? uc->itc.amount.currency + : uc->itc.tip.currency)) { - GNUNET_break (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - err_name); + 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 ( (NULL != summary) && - (NULL != tsummary) ) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_POST_USING_TEMPLATES_SUMMARY_CONFLICT_TEMPLATES_CONTRACT_SUBJECT, - NULL); - } - if ( (NULL == summary) && - (NULL == tsummary) ) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_SUMMARY, - NULL); - } - no_summary = (NULL == summary); - - if (! no_tip && ! request_tip) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_CONFLICT, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "tip"); - } - - if (! TMH_test_exchange_configured_for_currency (itc.amount.currency)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, - "Currency for amount is not supported by backend"); - } - - if (! no_tip) + if (! uc->inventory.no_tip) { - if (GNUNET_YES != - TALER_amount_cmp_currency (&itc.amount, - &itc.tip)) + if (! uc->inventory.no_amount && + (GNUNET_YES != + TALER_amount_cmp_currency (&uc->itc.amount, + &uc->itc.tip))) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, - "Mismatch of currencies between the amount and tip"); + 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; } } - if (0 == json_array_size (inventory_selection)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MISSING, - "inventory_selection"); - } - if (choose_one && - (1 != json_array_size (inventory_selection))) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_CONFLICT, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "inventory_selection"); - } - for (size_t i = 0; i < json_array_size (inventory_selection); i++) { const char *product_id; const char *quantity; - struct TALER_MERCHANTDB_ProductDetails pd; - enum GNUNET_DB_QueryStatus qs; - size_t num_categories = 0; - uint64_t *categories = NULL; struct GNUNET_JSON_Specification ispec[] = { GNUNET_JSON_spec_string ("product_id", &product_id), @@ -687,9 +802,6 @@ handle_using_templates_inventory (struct MHD_Connection *connection, }; const char *err_name; unsigned int err_line; - uint64_t quantity_value = 0; - uint32_t quantity_frac = 0; - const char *eparam = NULL; res = GNUNET_JSON_parse (json_array_get (inventory_selection, i), @@ -699,18 +811,69 @@ handle_using_templates_inventory (struct MHD_Connection *connection, if (GNUNET_OK != res) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - cleanup_inventory_template_context (&itc); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "inventory_selection"); + 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 + * @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->itc.items_len; i++) + { + 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; qs = TMH_db->lookup_product (TMH_db->cls, mi->settings.id, - product_id, + item->product_id, &pd, &num_categories, &categories); @@ -736,160 +899,303 @@ handle_using_templates_inventory (struct MHD_Connection *connection, case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: GNUNET_assert (0); } - GNUNET_JSON_parse_free (spec); - cleanup_inventory_template_context (&itc); - return TALER_MHD_reply_with_error (connection, - http_status, - ec, - product_id); + use_reply_with_error (uc, + connection, + http_status, + ec, + item->product_id); + return GNUNET_SYSERR; } - if (! itc.selected_all) - { - bool allowed = false; + item->pd = pd; + item->categories = categories; + item->num_categories = num_categories; + } + return GNUNET_OK; +} - if (product_id_selected (itc.selected_products, - product_id)) - allowed = true; - else if (categories_selected (itc.selected_categories, - num_categories, - categories)) - allowed = true; - if (! allowed) - { - GNUNET_JSON_parse_free (spec); - GNUNET_free (categories); - TALER_MERCHANTDB_product_details_free (&pd); - cleanup_inventory_template_context (&itc); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_CONFLICT, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "inventory_selection(selected product is not available for this template)"); - } - } +/** + * Verify request data for inventory templates. + * + * @param connection connection to reply on + * @param uc use context + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +verify_using_templates_inventory (struct MHD_Connection *connection, + struct UseContext *uc) +{ + struct GNUNET_JSON_Specification tcspec[] = { + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("summary", + &uc->inventory.tsummary), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("selected_categories", + &uc->itc.selected_categories), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_array_const ("selected_products", + &uc->itc.selected_products), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("choose_one", + &uc->inventory.choose_one), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("request_tip", + &uc->inventory.request_tip), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("selected_all", + &uc->itc.selected_all), + NULL), + GNUNET_JSON_spec_end () + }; + const char *err_name; + unsigned int err_line; + enum GNUNET_GenericReturnValue res; - if (GNUNET_OK != - TALER_MERCHANT_vk_process_quantity_inputs ( - TMH_VK_QUANTITY, - pd.allow_fractional_quantity, - true, - 0, - false, - quantity, - &quantity_value, - &quantity_frac, - &eparam)) - { - GNUNET_JSON_parse_free (spec); - GNUNET_free (categories); - TALER_MERCHANTDB_product_details_free (&pd); - cleanup_inventory_template_context (&itc); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - eparam); - } + 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, + tcspec, + &err_name, + &err_line); + if (GNUNET_OK != res) + { + 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 (0 == pd.price_array_length) - { - GNUNET_break (0); - GNUNET_JSON_parse_free (spec); - GNUNET_free (categories); - TALER_MERCHANTDB_product_details_free (&pd); - cleanup_inventory_template_context (&itc); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - "price_array"); + if ( (NULL != uc->summary) && + (NULL != uc->inventory.tsummary) ) + { + 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; + } + if ( (NULL == uc->summary) && + (NULL == uc->inventory.tsummary) ) + { + GNUNET_break_op (0); + use_reply_with_error (uc, + connection, + MHD_HTTP_CONFLICT, + TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_SUMMARY, + NULL); + return GNUNET_SYSERR; + } + uc->inventory.no_summary = (NULL == uc->summary); + + if (! uc->inventory.no_tip && + ! uc->inventory.request_tip) + { + GNUNET_break_op (0); + use_reply_with_error (uc, + connection, + MHD_HTTP_CONFLICT, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "tip"); + return GNUNET_SYSERR; + } + + if (uc->inventory.choose_one && + (1 != uc->itc.items_len)) + { + GNUNET_break_op (0); + use_reply_with_error (uc, + connection, + MHD_HTTP_CONFLICT, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "inventory_selection"); + return GNUNET_SYSERR; + } + + for (unsigned int i = 0; i < uc->itc.items_len; i++) + { + struct InventoryTemplateItem *item = &uc->itc.items[i]; + const char *eparam = NULL; + + if (! uc->itc.selected_all) + { + bool allowed = false; + + if (product_id_selected (uc->itc.selected_products, + item->product_id)) + allowed = true; + else if (categories_selected (uc->itc.selected_categories, + item->num_categories, + item->categories)) + allowed = true; + + if (! allowed) + { + use_reply_with_error ( + uc, + connection, + MHD_HTTP_CONFLICT, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "inventory_selection(selected product is not available for this template)"); + 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)) { - struct InventoryTemplateItem item; + use_reply_with_error (uc, + connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + eparam); + return GNUNET_SYSERR; + } - memset (&item, - 0, - sizeof (item)); - item.product_id = GNUNET_strdup (product_id); - item.unit_quantity = GNUNET_strdup (quantity); - item.quantity_value = quantity_value; - item.quantity_frac = quantity_frac; - item.pd = pd; - GNUNET_array_append (itc.items, - itc.items_len, - item); - memset (&pd, - 0, - sizeof (pd)); - GNUNET_free (categories); - categories = NULL; + 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 (GNUNET_OK != - compute_totals_per_currency (&itc)) + if (uc->inventory.no_amount && + uc->inventory.no_tip) { - GNUNET_JSON_parse_free (spec); - cleanup_inventory_template_context (&itc); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - "calculation of currency totals failed"); + if (GNUNET_OK != + compute_totals_per_currency (&uc->itc)) + { + 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; + } } - // FIXME: itc.amount MAY not have been specified by the client here, - // we MAY still be in a multi-currency scenario. You cannot just - // use itc.amount - if (GNUNET_OK != - compute_inventory_total (&itc, - itc.amount.currency, - &itc.total)) + if (! uc->inventory.no_amount || + ! uc->inventory.no_tip) { - GNUNET_JSON_parse_free (spec); - cleanup_inventory_template_context (&itc); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - "total amount calculation failed"); + 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 }); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (primary_currency, + &uc->itc.currencies[0].total)); + if (GNUNET_OK != + compute_inventory_total (&uc->itc, + primary_currency, + &uc->itc.currencies[0].total)) + { + 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 (! no_tip) + if (! uc->inventory.no_tip) { - // TODO: HMMMMM... If we have tip, we can't really go for another choice... - // or we will have to indicate it somehow in the wallet, when user changes the choice... - // FIXME: yes, so if we have a tip, only generate that exact choice! if (GNUNET_YES != - TALER_amount_cmp_currency (&itc.tip, - &itc.total)) + TALER_amount_cmp_currency (&uc->itc.tip, + &uc->itc.currencies[0].total)) { + /* YOU NEVER SUPPOSED TO REACH IT */ GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - cleanup_inventory_template_context (&itc); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, - itc.tip.currency); + 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 (&itc.total, - &itc.total, - &itc.tip)) + TALER_amount_add (&uc->itc.currencies[0].total, + &uc->itc.currencies[0].total, + &uc->itc.tip)) { GNUNET_break (0); - GNUNET_JSON_parse_free (spec); - cleanup_inventory_template_context (&itc); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT, - "tip"); + 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 ( @@ -900,28 +1206,14 @@ handle_using_templates_inventory (struct MHD_Connection *connection, GNUNET_JSON_pack_uint64 ("quantity", 1), TALER_JSON_pack_amount ("price", - &itc.tip)))); - } - - if (0 != TALER_amount_cmp (&itc.amount, - &itc.total)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - json_decref (tip_products); - cleanup_inventory_template_context (&itc); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT, - NULL); + &uc->itc.tip)))); } inventory_products = json_array (); GNUNET_assert (NULL != inventory_products); - for (size_t i = 0; i < itc.items_len; i++) + for (size_t i = 0; i < uc->itc.items_len; i++) { - const struct InventoryTemplateItem *item = &itc.items[i]; + const struct InventoryTemplateItem *item = &uc->itc.items[i]; GNUNET_assert (0 == json_array_append_new ( @@ -939,24 +1231,28 @@ handle_using_templates_inventory (struct MHD_Connection *connection, choices = json_array (); GNUNET_assert (NULL != choices); - GNUNET_assert (0 == - json_array_append_new (choices, - GNUNET_JSON_PACK ( - TALER_JSON_pack_amount ("amount", - &itc.total)) - )); - - for (size_t i = 0; i < itc.currencies_len; i++) + if (! multi_choices) { - if (0 == strcmp (itc.currencies[i].currency, - itc.amount.currency)) - continue; GNUNET_assert (0 == - json_array_append_new ( - choices, - GNUNET_JSON_PACK ( - TALER_JSON_pack_amount ("amount", - &itc.currencies[i].total)))); + 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 ( @@ -973,71 +1269,58 @@ handle_using_templates_inventory (struct MHD_Connection *connection, GNUNET_JSON_pack_array_steal ("choices", choices), GNUNET_JSON_pack_string ("summary", - no_summary - ? tsummary - : summary), + uc->inventory.no_summary + ? uc->inventory.tsummary + : uc->summary), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("fulfillment_url", - fulfillment_url)), + uc->fulfillment_url)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("fulfillment_message", - 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 (&itc); - GNUNET_JSON_parse_free (spec); - return TMH_private_post_orders ( - NULL, /* not even used */ - connection, - &uc->ihc); + cleanup_inventory_template_context (&uc->itc); + return GNUNET_OK; } /** - * Handle POST /templates/$ID for fixed-order templates. + * Parse request data for fixed-order templates. * * @param connection connection to reply on * @param hc handler context * @param uc use context - * @param summary summary override (optional) - * @param fulfillment_url fulfillment URL (optional) - * @param fulfillment_message fulfillment message (optional) - * @return MHD result + * @return #GNUNET_OK on success */ -static MHD_RESULT -handle_using_templates_fixed (struct MHD_Connection *connection, - struct TMH_HandlerContext *hc, - struct UseContext *uc, - const char *summary, - const char *fulfillment_url, - const char *fulfillment_message) +static enum GNUNET_GenericReturnValue +parse_using_templates_fixed_request (struct MHD_Connection *connection, + struct TMH_HandlerContext *hc, + struct UseContext *uc) { - struct TALER_Amount amount; - struct TALER_Amount tip; - bool no_amount; - bool no_tip; - bool no_summary; + 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", - &summary), + &uc->summary), NULL), GNUNET_JSON_spec_mark_optional ( TALER_JSON_spec_amount_any ("amount", - &amount), - &no_amount), + &uc->fixed.amount), + &uc->fixed.no_amount), GNUNET_JSON_spec_mark_optional ( TALER_JSON_spec_amount_any ("tip", - &tip), - &no_tip), + &uc->fixed.tip), + &uc->fixed.no_tip), GNUNET_JSON_spec_end () }; enum GNUNET_GenericReturnValue res; - json_t *tip_products = NULL; res = TALER_MHD_parse_json_data (connection, hc->request_body, @@ -1045,223 +1328,270 @@ handle_using_templates_fixed (struct MHD_Connection *connection, if (GNUNET_OK != res) { GNUNET_break_op (0); - return (GNUNET_NO == res) - ? MHD_YES - : MHD_NO; + use_finalize_parse (uc, + res); + return GNUNET_SYSERR; } + return GNUNET_OK; +} + + +/** + * Verify request data for fixed-order templates. + * + * @param connection connection to reply on + * @param uc use context + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +verify_using_templates_fixed (struct MHD_Connection *connection, + 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->fixed.tsummary), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_string ("currency", + &uc->fixed.tcurrency), + NULL), + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_amount_any ("amount", + &uc->fixed.tamount), + &uc->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; - if (NULL == uc->ihc.request_body) + uc->fixed.tsummary = NULL; + uc->fixed.tcurrency = NULL; + uc->fixed.no_tamount = false; + + res = GNUNET_JSON_parse (uc->etp.template_contract, + tspec, + &err_name, + &err_line); + if (GNUNET_OK != res) { - /* template */ - const char *tsummary = NULL; - const char *tcurrency = NULL; - uint32_t min_age; - struct GNUNET_TIME_Relative pay_duration; - struct TALER_Amount tamount; - bool no_tamount; - struct GNUNET_JSON_Specification tspec[] = { - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("summary", - &tsummary), - NULL), - GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_string ("currency", - &tcurrency), - NULL), - GNUNET_JSON_spec_mark_optional ( - TALER_JSON_spec_amount_any ("amount", - &tamount), - &no_tamount), - GNUNET_JSON_spec_uint32 ("minimum_age", - &min_age), - GNUNET_JSON_spec_relative_time ("pay_duration", - &pay_duration), - GNUNET_JSON_spec_end () - }; + GNUNET_break (0); + json_dumpf (uc->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; + } - { - const char *err_name; - unsigned int err_line; - - res = GNUNET_JSON_parse (uc->etp.template_contract, - tspec, - &err_name, - &err_line); - if (GNUNET_OK != res) - { - GNUNET_break (0); - json_dumpf (uc->etp.template_contract, - stderr, - JSON_INDENT (2)); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - err_name); - } - } + if ( (! uc->fixed.no_amount) && + (! uc->fixed.no_tamount) ) + { + 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; + } - if ( (! no_amount) && - (! no_tamount) ) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT, - NULL); - } + if ( (! uc->fixed.no_amount) && + (NULL != uc->fixed.tcurrency) && + (0 != strcmp (uc->fixed.tcurrency, + uc->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); + return GNUNET_SYSERR; + } - if ( (! no_amount) && - (NULL != tcurrency) && - (0 != strcmp (tcurrency, - amount.currency)) ) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, - tcurrency); - } + if (uc->fixed.no_amount && uc->fixed.no_tamount) + { + 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; + } + + if (! uc->fixed.no_tip) + { + const struct TALER_Amount *total_amount; - if (no_amount && no_tamount) + total_amount = uc->fixed.no_amount + ? &uc->fixed.tamount + : &uc->fixed.amount; + if (GNUNET_YES != + TALER_amount_cmp_currency (&uc->fixed.tip, + total_amount)) { GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_AMOUNT, - NULL); + use_reply_with_error (uc, + connection, + MHD_HTTP_CONFLICT, + TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, + uc->fixed.tip.currency); + return GNUNET_SYSERR; } + } - if (! no_tip) - { - const struct TALER_Amount *total_amount; + if ( (NULL != uc->summary) && + (NULL != uc->fixed.tsummary) ) + { + 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; + } - total_amount = no_amount ? &tamount : &amount; - if (GNUNET_YES != - TALER_amount_cmp_currency (&tip, - total_amount)) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - json_decref (tip_products); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH, - tip.currency); - } - } + if ( (NULL == uc->summary) && + (NULL == uc->fixed.tsummary) ) + { + GNUNET_break_op (0); + use_reply_with_error (uc, + connection, + MHD_HTTP_CONFLICT, + TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_SUMMARY, + NULL); + return GNUNET_SYSERR; + } + uc->fixed.no_summary = (NULL == uc->summary); - if ( (NULL != summary) && - (NULL != tsummary) ) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_POST_USING_TEMPLATES_SUMMARY_CONFLICT_TEMPLATES_CONTRACT_SUBJECT, - NULL); - } + return GNUNET_OK; +} - if ( (NULL == summary) && - (NULL == tsummary) ) - { - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_CONFLICT, - TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_SUMMARY, - NULL); - } - no_summary = (NULL == summary); - { - json_t *body; - if (! 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", - &tip)))); - } - body = GNUNET_JSON_PACK ( - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("otp_id", - uc->etp.otp_id)), - GNUNET_JSON_pack_object_steal ( - "order", - GNUNET_JSON_PACK ( - TALER_JSON_pack_amount ("amount", - no_amount - ? &tamount - : &amount), - GNUNET_JSON_pack_string ("summary", - no_summary - ? tsummary - : summary), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("fulfillment_url", - fulfillment_url)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("fulfillment_message", - fulfillment_message)), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_array_steal ("products", - tip_products))))); - uc->ihc.request_body = body; - } +/** + * Create order request for fixed-order templates. + * + * @param uc use context + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +create_using_templates_fixed (struct UseContext *uc) +{ + json_t *tip_products = NULL; + + if (NULL != uc->ihc.request_body) + return GNUNET_OK; + + if (! uc->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->fixed.tip)))); + } + { + json_t *body; + + body = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("otp_id", + uc->etp.otp_id)), + 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_string ("summary", + uc->fixed.no_summary + ? uc->fixed.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))))); + uc->ihc.request_body = body; } - return TMH_private_post_orders ( - NULL, /* not even used */ - connection, - &uc->ihc); + return GNUNET_OK; } /** - * Handle POST /templates/$ID for paivana templates. + * Parse request data for paivana templates. * * @param connection connection to reply on * @param hc handler context * @param uc use context - * @param summary summary override (optional) - * @param fulfillment_url fulfillment URL (optional) - * @param fulfillment_message fulfillment message (optional) - * @return MHD result + * @return #GNUNET_OK on success */ -static MHD_RESULT -handle_using_templates_paivana (struct MHD_Connection *connection, - struct TMH_HandlerContext *hc, - struct UseContext *uc, - const char *summary, - const char *fulfillment_url, - const char *fulfillment_message) +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 handle_using_templates_fixed (connection, - hc, - uc, - summary, - fulfillment_url, - fulfillment_message); + return parse_using_templates_fixed_request (connection, + hc, + uc); +} + + +/** + * 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); +} + + +/** + * Create order request for paivana templates. + * + * @param uc use context + * @return #GNUNET_OK on success + */ +static enum GNUNET_GenericReturnValue +create_using_templates_paivana (struct UseContext *uc) +{ + /* TODO: PAIVANA include paivana-specific order creation. */ + return create_using_templates_fixed (uc); } @@ -1272,9 +1602,6 @@ TMH_post_using_templates_ID (const struct TMH_RequestHandler *rh, { struct TMH_MerchantInstance *mi = hc->instance; const char *template_id = hc->infix; - const char *summary = NULL; - const char *fulfillment_url = NULL; - const char *fulfillment_message = NULL; struct UseContext *uc = hc->ctx; if (NULL == uc) @@ -1283,82 +1610,217 @@ 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->template_type = TALER_MERCHANT_TEMPLATE_TYPE_INVALID; + uc->summary = NULL; + uc->fulfillment_url = NULL; + uc->fulfillment_message = NULL; } - if (! uc->have_etp) + while (1) { - 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) + switch (uc->phase) { - case GNUNET_DB_STATUS_HARD_ERROR: - /* Clean up and fail hard */ - GNUNET_break (0); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - NULL); - case GNUNET_DB_STATUS_SOFT_ERROR: - /* this should be impossible (single select) */ - GNUNET_break (0); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - NULL); - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - /* template not found! */ - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_NOT_FOUND, - TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN, - template_id); - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - /* all good */ - uc->have_etp = true; + 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; break; - } /* End of the switch */ - } - // FIXME: share more logic between these, just skip (or run) individual - // phases depending on the template type! - switch (TMH_template_type_from_contract (uc->etp.template_contract)) - { - case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER: - return handle_using_templates_fixed (connection, - hc, - uc, - summary, - fulfillment_url, - fulfillment_message); - case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA: - /* TODO: PAIVANA handle paivana-specific instantiation. */ - return handle_using_templates_paivana (connection, - hc, - uc, - summary, - fulfillment_url, - fulfillment_message); - case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART: - return handle_using_templates_inventory (connection, - hc, - uc, - summary, - fulfillment_url, - fulfillment_message); - case TALER_MERCHANT_TEMPLATE_TYPE_INVALID: - 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; + } + 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; + } + 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; + } + case USE_PHASE_SUBMIT_ORDER: + return TMH_private_post_orders ( + NULL, /* not even used */ + connection, + &uc->ihc); + case USE_PHASE_FINISHED_MHD_YES: + return MHD_YES; + case USE_PHASE_FINISHED_MHD_NO: + return MHD_NO; + } } - GNUNET_break (0); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, - "template_contract has unexpected type"); }