commit 1680844affef7b5ce63effb5f6cbf274c835dfae
parent 9f6610110475dfc0e6ef9bf35d0f3ca71b7f4dbf
Author: Christian Grothoff <grothoff@gnunet.org>
Date: Thu, 29 Jan 2026 18:05:24 +0900
rename for consistency
Diffstat:
6 files changed, 1825 insertions(+), 1825 deletions(-)
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
@@ -237,8 +237,8 @@ taler_merchant_httpd_SOURCES = \
taler-merchant-httpd_post-orders-ID-paid.h \
taler-merchant-httpd_post-orders-ID-refund.c \
taler-merchant-httpd_post-orders-ID-refund.h \
- taler-merchant-httpd_post-using-templates.c \
- taler-merchant-httpd_post-using-templates.h \
+ taler-merchant-httpd_post-templates-ID.c \
+ taler-merchant-httpd_post-templates-ID.h \
taler-merchant-httpd_post-reports-ID.c \
taler-merchant-httpd_post-reports-ID.h \
taler-merchant-httpd_private-get-statistics-amount-SLUG.c \
diff --git a/src/backend/taler-merchant-httpd_dispatcher.c b/src/backend/taler-merchant-httpd_dispatcher.c
@@ -98,7 +98,7 @@
#include "taler-merchant-httpd_post-orders-ID-claim.h"
#include "taler-merchant-httpd_post-orders-ID-paid.h"
#include "taler-merchant-httpd_post-orders-ID-pay.h"
-#include "taler-merchant-httpd_post-using-templates.h"
+#include "taler-merchant-httpd_post-templates-ID.h"
#include "taler-merchant-httpd_post-orders-ID-refund.h"
#include "taler-merchant-httpd_spa.h"
#include "taler-merchant-httpd_statics.h"
diff --git a/src/backend/taler-merchant-httpd_post-templates-ID.c b/src/backend/taler-merchant-httpd_post-templates-ID.c
@@ -0,0 +1,1782 @@
+/*
+ This file is part of TALER
+ (C) 2022-2026 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file taler-merchant-httpd_post-using-templates.c
+ * @brief implementing POST /using-templates request handling
+ * @author Priscilla HUANG
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_post-templates-ID.h"
+#include "taler-merchant-httpd_private-post-orders.h"
+#include "taler-merchant-httpd_helper.h"
+#include "taler-merchant-httpd_exchanges.h"
+#include "taler_merchant_util.h"
+#include <taler/taler_json_lib.h>
+#include <regex.h>
+
+
+/**
+ * Item selected from inventory_selection.
+ */
+struct InventoryTemplateItemContext
+{
+ /**
+ * Product ID as referenced in inventory.
+ */
+ const char *product_id;
+
+ /**
+ * Unit quantity string as provided by the client.
+ */
+ const 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;
+};
+
+
+/**
+ * Our context.
+ */
+enum UsePhase
+{
+ /**
+ * Parse request payload into context fields.
+ */
+ USE_PHASE_PARSE_REQUEST,
+
+ /**
+ * Fetch template details from the database.
+ */
+ USE_PHASE_LOOKUP_TEMPLATE,
+
+ /**
+ * Parse template.
+ */
+ USE_PHASE_PARSE_TEMPLATE,
+
+ /**
+ * Load additional details (like products and
+ * categories) needed for verification and
+ * price computation.
+ */
+ USE_PHASE_DB_FETCH,
+
+ /**
+ * Validate request and template compatibility.
+ */
+ USE_PHASE_VERIFY,
+
+ /**
+ * Compute price of the order.
+ */
+ USE_PHASE_COMPUTE_PRICE,
+
+ /**
+ * Handle tip.
+ */
+ USE_PHASE_CHECK_TIP,
+
+ /**
+ * Check if client-supplied total amount matches
+ * our calculation (if we did any).
+ */
+ USE_PHASE_CHECK_TOTAL,
+
+ /**
+ * Construct the internal order request body.
+ */
+ USE_PHASE_CREATE_ORDER,
+
+ /**
+ * Submit the order to the shared order handler.
+ */
+ USE_PHASE_SUBMIT_ORDER,
+
+ /**
+ * Finished successfully with MHD_YES.
+ */
+ USE_PHASE_FINISHED_MHD_YES,
+
+ /**
+ * Finished with MHD_NO.
+ */
+ USE_PHASE_FINISHED_MHD_NO
+};
+
+struct UseContext
+{
+ /**
+ * Context for our handler.
+ */
+ struct TMH_HandlerContext *hc;
+
+ /**
+ * Internal handler context we are passing into the
+ * POST /private/orders handler.
+ */
+ struct TMH_HandlerContext ihc;
+
+ /**
+ * Phase we are currently in.
+ */
+ enum UsePhase phase;
+
+ /**
+ * Template type from the contract.
+ */
+ enum TALER_MERCHANT_TemplateType template_type;
+
+ /**
+ * Information set in the #USE_PHASE_PARSE_REQUEST phase.
+ */
+ struct
+ {
+ /**
+ * Summary override from request, if any.
+ */
+ const char *summary;
+
+ /**
+ * 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;
+
+ /**
+ * Parsed fields for inventory templates.
+ */
+ struct
+ {
+ /**
+ * Selected products from inventory_selection.
+ */
+ struct InventoryTemplateItemContext *items;
+
+ /**
+ * Length of @e items.
+ */
+ unsigned int items_len;
+
+ } inventory;
+
+ /**
+ * Request details if this is a paivana instantiation.
+ */
+ struct
+ {
+
+ /**
+ * Target website for the request.
+ */
+ const char *website;
+
+ /**
+ * Unique client identifier, consisting of
+ * current time, "-", and the hash of a nonce,
+ * the website and the current time.
+ */
+ const char *paivana_id;
+
+ } paivana;
+
+ } parse_request;
+
+ /**
+ * Information set in the #USE_PHASE_LOOKUP_TEMPLATE phase.
+ */
+ struct
+ {
+
+ /**
+ * Our template details from the DB.
+ */
+ struct TALER_MERCHANTDB_TemplateDetails etp;
+
+ } lookup_template;
+
+ /**
+ * Information set in the #USE_PHASE_PARSE_TEMPLATE phase.
+ */
+ struct TALER_MERCHANT_TemplateContract template_contract;
+
+ /**
+ * Information set in the #USE_PHASE_COMPUTE_PRICE phase.
+ */
+ struct
+ {
+
+ /**
+ * Per-currency totals across selected products (without tips).
+ */
+ struct TALER_Amount *totals;
+
+ /**
+ * Length of @e totals.
+ */
+ unsigned int totals_len;
+
+ /**
+ * Array of payment choices, used with Paviana.
+ */
+ json_t *choices;
+
+ } compute_price;
+
+};
+
+
+/**
+ * Clean up inventory items.
+ *
+ * @param items_len length of @a items
+ * @param[in] items item array to free
+ */
+static void
+cleanup_inventory_items (unsigned int items_len,
+ struct InventoryTemplateItemContext items[static
+ items_len])
+{
+ for (unsigned int i = 0; i < items_len; i++)
+ {
+ struct InventoryTemplateItemContext *item = &items[i];
+
+ TALER_MERCHANTDB_product_details_free (&item->pd);
+ GNUNET_free (item->categories);
+ }
+ GNUNET_free (items);
+}
+
+
+/**
+ * Clean up a `struct UseContext *`
+ *
+ * @param[in] cls a `struct UseContext *`
+ */
+static void
+cleanup_use_context (void *cls)
+{
+ struct UseContext *uc = cls;
+
+ TALER_MERCHANTDB_template_details_free (&uc->lookup_template.etp);
+ if (NULL !=
+ uc->parse_request.inventory.items)
+ cleanup_inventory_items (uc->parse_request.inventory.items_len,
+ uc->parse_request.inventory.items);
+ GNUNET_free (uc->compute_price.totals);
+ uc->compute_price.totals_len = 0;
+ json_decref (uc->compute_price.choices);
+ if (NULL != uc->ihc.cc)
+ uc->ihc.cc (uc->ihc.ctx);
+ GNUNET_free (uc->ihc.infix);
+ json_decref (uc->ihc.request_body);
+ GNUNET_free (uc);
+}
+
+
+/**
+ * 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)
+{
+ GNUNET_assert (GNUNET_OK != 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 http_status HTTP status code
+ * @param ec error code
+ * @param detail error detail
+ */
+static void
+use_reply_with_error (struct UseContext *uc,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const char *detail)
+{
+ MHD_RESULT mret;
+
+ mret = TALER_MHD_reply_with_error (uc->hc->connection,
+ http_status,
+ ec,
+ detail);
+ use_finalize (uc,
+ mret);
+}
+
+
+/* ***************** USE_PHASE_PARSE_REQUEST **************** */
+
+/**
+ * Parse request data for inventory templates.
+ *
+ * @param[in,out] uc use context
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_using_templates_inventory_request (
+ struct UseContext *uc)
+{
+ const json_t *inventory_selection;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("inventory_selection",
+ &inventory_selection),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+
+ GNUNET_assert (NULL == uc->ihc.request_body);
+ res = TALER_MHD_parse_json_data (uc->hc->connection,
+ uc->hc->request_body,
+ spec);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break_op (0);
+ use_finalize_parse (uc,
+ res);
+ return GNUNET_SYSERR;
+ }
+
+ if ( (! uc->parse_request.no_amount) &&
+ (! TMH_test_exchange_configured_for_currency (
+ uc->parse_request.amount.currency)) )
+ {
+ GNUNET_break_op (0);
+ use_reply_with_error (uc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+ "Currency is not supported by backend");
+ return GNUNET_SYSERR;
+ }
+
+ for (size_t i = 0; i < json_array_size (inventory_selection); i++)
+ {
+ struct InventoryTemplateItemContext item = { 0 };
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("product_id",
+ &item.product_id),
+ GNUNET_JSON_spec_string ("quantity",
+ &item.unit_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);
+ use_reply_with_error (uc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "inventory_selection");
+ return GNUNET_SYSERR;
+ }
+
+ GNUNET_array_append (uc->parse_request.inventory.items,
+ uc->parse_request.inventory.items_len,
+ item);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Parse request data for paivana templates.
+ *
+ * @param[in,out] uc use context
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_using_templates_paivana_request (
+ struct UseContext *uc)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("website",
+ &uc->parse_request.paivana.website),
+ GNUNET_JSON_spec_string ("paivana_id",
+ &uc->parse_request.paivana.paivana_id),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+ struct GNUNET_HashCode sh;
+ unsigned long long tv;
+ const char *dash;
+
+ GNUNET_assert (NULL == uc->ihc.request_body);
+ res = TALER_MHD_parse_json_data (uc->hc->connection,
+ uc->hc->request_body,
+ spec);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break_op (0);
+ use_finalize_parse (uc,
+ res);
+ return GNUNET_SYSERR;
+ }
+ if (1 !=
+ sscanf (uc->parse_request.paivana.paivana_id,
+ "%llu-",
+ &tv))
+ {
+ GNUNET_break_op (0);
+ use_reply_with_error (uc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "paivana_id");
+ return GNUNET_SYSERR;
+ }
+ dash = strchr (uc->parse_request.paivana.paivana_id,
+ '-');
+ GNUNET_assert (NULL != dash);
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (dash + 1,
+ strlen (dash + 1),
+ &sh,
+ sizeof (sh)))
+ {
+ GNUNET_break_op (0);
+ use_reply_with_error (uc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "paivana_id");
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Main function for the #USE_PHASE_PARSE_REQUEST.
+ *
+ * @param[in,out] uc context to update
+ */
+static void
+handle_phase_parse_request (
+ struct UseContext *uc)
+{
+ const char *template_type = NULL;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("template_type",
+ &template_type),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_amount_any ("tip",
+ &uc->parse_request.tip),
+ &uc->parse_request.no_tip),
+ 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.amount),
+ &uc->parse_request.no_amount),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (uc->hc->connection,
+ uc->hc->request_body,
+ spec);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break_op (0);
+ use_finalize_parse (uc,
+ res);
+ return;
+ }
+ if (NULL == template_type)
+ template_type = "fixed-order";
+ uc->template_type
+ = TALER_MERCHANT_template_type_from_string (
+ template_type);
+ switch (uc->template_type)
+ {
+ case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
+ /* nothig left to do */
+ uc->phase++;
+ return;
+ case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
+ res = parse_using_templates_paivana_request (uc);
+ if (GNUNET_OK == res)
+ uc->phase++;
+ return;
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
+ res = parse_using_templates_inventory_request (uc);
+ if (GNUNET_OK == res)
+ uc->phase++;
+ return;
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
+ break;
+ }
+ GNUNET_break (0);
+ use_reply_with_error (
+ uc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "template_type");
+}
+
+
+/* ***************** USE_PHASE_LOOKUP_TEMPLATE **************** */
+
+/**
+ * Main function for the #USE_PHASE_LOOKUP_TEMPLATE.
+ *
+ * @param[in,out] uc context to update
+ */
+static void
+handle_phase_lookup_template (
+ struct UseContext *uc)
+{
+ struct TMH_MerchantInstance *mi = uc->hc->instance;
+ const char *template_id = uc->hc->infix;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TMH_db->lookup_template (TMH_db->cls,
+ mi->settings.id,
+ template_id,
+ &uc->lookup_template.etp);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ /* Clean up and fail hard */
+ GNUNET_break (0);
+ use_reply_with_error (uc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_template");
+ return;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ /* this should be impossible (single select) */
+ GNUNET_break (0);
+ use_reply_with_error (uc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_template");
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* template not found! */
+ use_reply_with_error (uc,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN,
+ template_id);
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* all good */
+ break;
+ }
+ if (uc->template_type !=
+ TALER_MERCHANT_template_type_from_contract (
+ uc->lookup_template.etp.template_contract))
+ {
+ GNUNET_break_op (0);
+ use_reply_with_error (
+ uc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_POST_USING_TEMPLATES_WRONG_TYPE,
+ "template_contract has different type");
+ return;
+ }
+ uc->phase++;
+}
+
+
+/* ***************** USE_PHASE_PARSE_TEMPLATE **************** */
+
+
+/**
+ * Parse template.
+ *
+ * @param[in,out] uc use context
+ */
+static void
+handle_phase_template_contract (struct UseContext *uc)
+{
+ const char *err_name;
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MERCHANT_template_contract_parse (
+ uc->lookup_template.etp.template_contract,
+ &uc->template_contract,
+ &err_name);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break (0);
+ use_reply_with_error (uc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ err_name);
+ return;
+ }
+ uc->phase++;
+}
+
+
+/* ***************** USE_PHASE_DB_FETCH **************** */
+
+/**
+ * Fetch DB data for inventory templates.
+ *
+ * @param[in,out] uc use context
+ */
+static void
+handle_phase_db_fetch (struct UseContext *uc)
+{
+ struct TMH_MerchantInstance *mi = uc->hc->instance;
+
+ switch (uc->template_type)
+ {
+ case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
+ uc->phase++;
+ return;
+ case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
+ uc->phase++;
+ return;
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
+ break;
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
+ GNUNET_assert (0);
+ }
+
+ for (unsigned int i = 0;
+ i < uc->parse_request.inventory.items_len;
+ i++)
+ {
+ struct InventoryTemplateItemContext *item =
+ &uc->parse_request.inventory.items[i];
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TMH_db->lookup_product (TMH_db->cls,
+ mi->settings.id,
+ item->product_id,
+ &item->pd,
+ &item->num_categories,
+ &item->categories);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ use_reply_with_error (uc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_product");
+ return;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ use_reply_with_error (uc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_product");
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ use_reply_with_error (uc,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN,
+ item->product_id);
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ }
+ uc->phase++;
+}
+
+
+/* *************** Helpers for USE_PHASE_VERIFY ***************** */
+
+/**
+ * Check if the given product ID appears in the array of allowed_products.
+ *
+ * @param allowed_products JSON array of product IDs allowed by the template, may be NULL
+ * @param product_id product ID to check
+ * @return true if the product ID is in the list
+ */
+static bool
+product_id_allowed (const json_t *allowed_products,
+ const char *product_id)
+{
+ const json_t *entry;
+ size_t idx;
+
+ if (NULL == allowed_products)
+ return false;
+ json_array_foreach ((json_t *) allowed_products, idx, entry)
+ {
+ if (! json_is_string (entry))
+ {
+ GNUNET_break (0);
+ continue;
+ }
+ if (0 == strcmp (json_string_value (entry),
+ product_id))
+ return true;
+ }
+ return false;
+}
+
+
+/**
+ * Check if any product category is in the selected_categories list.
+ *
+ * @param allowed_categories JSON array of categories allowed by the template, may be NULL
+ * @param num_categories length of @a categories
+ * @param categories list of categories of the selected product
+ * @return true if any category of the product is in the list of allowed categories matches
+ */
+static bool
+category_allowed (const json_t *allowed_categories,
+ size_t num_categories,
+ const uint64_t categories[num_categories])
+{
+ const json_t *entry;
+ size_t idx;
+
+ if (NULL == allowed_categories)
+ return false;
+ json_array_foreach ((json_t *) allowed_categories,
+ idx,
+ entry)
+ {
+ uint64_t selected_id;
+
+ if (! json_is_integer (entry))
+ {
+ GNUNET_break (0);
+ continue;
+ }
+ if (0 > json_integer_value (entry))
+ {
+ GNUNET_break (0);
+ continue;
+ }
+ selected_id = (uint64_t) json_integer_value (entry);
+ for (size_t i = 0; i < num_categories; i++)
+ {
+ if (categories[i] == selected_id)
+ return true;
+ }
+ }
+ return false;
+}
+
+
+/**
+ * Verify request data for inventory templates.
+ * Checks that the selected products are allowed
+ * for this template.
+ *
+ * @param[in,out] uc use context
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+verify_using_templates_inventory (struct UseContext *uc)
+{
+ if (uc->template_contract.details.inventory.choose_one &&
+ (1 != uc->parse_request.inventory.items_len))
+ {
+ GNUNET_break_op (0);
+ use_reply_with_error (uc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "inventory_selection");
+ return GNUNET_SYSERR;
+ }
+ if (uc->template_contract.details.inventory.selected_all)
+ return GNUNET_OK;
+ for (unsigned int i = 0;
+ i < uc->parse_request.inventory.items_len;
+ i++)
+ {
+ struct InventoryTemplateItemContext *item =
+ &uc->parse_request.inventory.items[i];
+ const char *eparam = NULL;
+
+ if (GNUNET_OK !=
+ TALER_MERCHANT_vk_process_quantity_inputs (
+ TALER_MERCHANT_VK_QUANTITY,
+ item->pd.allow_fractional_quantity,
+ true,
+ 0,
+ false,
+ item->unit_quantity,
+ &item->quantity_value,
+ &item->quantity_frac,
+ &eparam))
+ {
+ GNUNET_break_op (0);
+ use_reply_with_error (uc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ eparam);
+ return GNUNET_SYSERR;
+ }
+
+ if (0 == item->pd.price_array_length)
+ {
+ GNUNET_break (0);
+ use_reply_with_error (uc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "price_array");
+ return GNUNET_SYSERR;
+ }
+ }
+
+ for (unsigned int i = 0;
+ i < uc->parse_request.inventory.items_len;
+ i++)
+ {
+ struct InventoryTemplateItemContext *item =
+ &uc->parse_request.inventory.items[i];
+
+ if (product_id_allowed (uc->template_contract.details.inventory.
+ selected_products,
+ item->product_id))
+ continue;
+ if (category_allowed (
+ uc->template_contract.details.inventory.selected_categories,
+ item->num_categories,
+ item->categories))
+ continue;
+ GNUNET_break_op (0);
+ use_reply_with_error (
+ uc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_POST_USING_TEMPLATES_WRONG_PRODUCT,
+ item->product_id);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Verify request data for fixed-order templates.
+ * As here we cannot compute the total amount, either
+ * the template or the client request must provide it.
+ *
+ * @param[in,out] uc use context
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+verify_using_templates_fixed (
+ struct UseContext *uc)
+{
+ if ( (! uc->parse_request.no_amount) &&
+ (! uc->template_contract.no_amount) )
+ {
+ GNUNET_break_op (0);
+ use_reply_with_error (uc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+ if (uc->parse_request.no_amount &&
+ uc->template_contract.no_amount)
+ {
+ GNUNET_break_op (0);
+ use_reply_with_error (uc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_AMOUNT,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Verify request data for paivana templates.
+ *
+ * @param[in,out] uc use context
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+verify_using_templates_paivana (
+ struct UseContext *uc)
+{
+ if (NULL != uc->template_contract.details.paivana.website_regex)
+ {
+ regex_t ex;
+ bool allowed = false;
+
+ if (0 != regcomp (&ex,
+ uc->template_contract.details.paivana.website_regex,
+ REG_NOSUB | REG_EXTENDED))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 ==
+ regexec (&ex,
+ uc->parse_request.paivana.website,
+ 0, NULL,
+ 0))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Website `%s' allowed by template\n",
+ uc->parse_request.paivana.website);
+ allowed = true;
+ }
+ regfree (&ex);
+ if (! allowed)
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Verify that the client request is structurally acceptable for the specified
+ * template. Does NOT check the total amount being reasonable.
+ *
+ * @param[in,out] uc use context
+ */
+static void
+handle_phase_verify (
+ struct UseContext *uc)
+{
+ enum GNUNET_GenericReturnValue res = GNUNET_SYSERR;
+
+ if ( (NULL != uc->parse_request.summary) &&
+ (NULL != uc->template_contract.summary) )
+ {
+ GNUNET_break_op (0);
+ use_reply_with_error (uc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_POST_USING_TEMPLATES_SUMMARY_CONFLICT_TEMPLATES_CONTRACT_SUBJECT,
+ NULL);
+ return;
+ }
+ if ( (NULL == uc->parse_request.summary) &&
+ (NULL == uc->template_contract.summary) )
+ {
+ GNUNET_break_op (0);
+ use_reply_with_error (uc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_SUMMARY,
+ NULL);
+ return;
+ }
+ if ( (! uc->parse_request.no_amount) &&
+ (NULL != uc->template_contract.currency) &&
+ (0 != strcasecmp (uc->template_contract.currency,
+ uc->parse_request.amount.currency)) )
+ {
+ GNUNET_break_op (0);
+ use_reply_with_error (uc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+ uc->template_contract.currency);
+ return;
+ }
+ switch (uc->template_type)
+ {
+ case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
+ res = verify_using_templates_fixed (uc);
+ break;
+ case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
+ res = verify_using_templates_paivana (uc);
+ break;
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
+ res = verify_using_templates_inventory (uc);
+ break;
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
+ GNUNET_assert (0);
+ }
+ if (GNUNET_OK == res)
+ uc->phase++;
+}
+
+
+/* ***************** USE_PHASE_COMPUTE_PRICE **************** */
+
+
+/**
+ * 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;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (unit_price->currency,
+ line_total));
+ if ( (0 != quantity) &&
+ (0 >
+ TALER_amount_multiply (line_total,
+ unit_price,
+ (uint32_t) quantity)) )
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 == quantity_frac)
+ return GNUNET_OK;
+ if (0 >
+ TALER_amount_multiply (&tmp,
+ unit_price,
+ quantity_frac))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ TALER_amount_divide (&tmp,
+ &tmp,
+ TALER_MERCHANT_UNIT_FRAC_BASE);
+ if (0 >
+ TALER_amount_add (line_total,
+ line_total,
+ &tmp))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Find the price of the given @a item in the specified
+ * @a currency.
+ *
+ * @param currency currency to search price in
+ * @param item item to check prices of
+ * @return NULL if a suitable price was not found
+ */
+static const struct TALER_Amount *
+find_item_price_in_currency (
+ const char *currency,
+ const struct InventoryTemplateItemContext *item)
+{
+ for (size_t j = 0; j < item->pd.price_array_length; j++)
+ {
+ if (0 == strcasecmp (item->pd.price_array[j].currency,
+ currency))
+ return &item->pd.price_array[j];
+ }
+ return NULL;
+}
+
+
+/**
+ * Compute totals for all currencies shared across selected products.
+ *
+ * @param[in,out] uc use context
+ * @return #GNUNET_OK on success (including no price due to no items)
+ * #GNUNET_NO if we could not find a price in any accepted currency
+ * for all selected products
+ * #GNUNET_SYSERR on arithmetic issues (internal error)
+ */
+static enum GNUNET_GenericReturnValue
+compute_totals_per_currency (struct UseContext *uc)
+{
+ const struct InventoryTemplateItemContext *items
+ = uc->parse_request.inventory.items;
+ unsigned int items_len = uc->parse_request.inventory.items_len;
+
+ if (0 == items_len)
+ return GNUNET_OK;
+ for (size_t i = 0; i < items[0].pd.price_array_length; i++)
+ {
+ const struct TALER_Amount *price
+ = &items[0].pd.price_array[i];
+ struct TALER_Amount zero;
+
+ if (! TMH_test_exchange_configured_for_currency (price->currency))
+ continue;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (price->currency,
+ &zero));
+ GNUNET_array_append (uc->compute_price.totals,
+ uc->compute_price.totals_len,
+ zero);
+ }
+ if (0 == uc->compute_price.totals_len)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No currency supported by our configuration in which we have prices for first selected product!\n");
+ return GNUNET_NO;
+ }
+ /* Loop through items, ensure each currency exists and sum totals. */
+ for (unsigned int i = 0; i < items_len; i++)
+ {
+ const struct InventoryTemplateItemContext *item = &items[i];
+ unsigned int c = 0;
+
+ while (c < uc->compute_price.totals_len)
+ {
+ struct TALER_Amount *total = &uc->compute_price.totals[c];
+ const struct TALER_Amount *unit_price;
+ struct TALER_Amount line_total;
+
+ unit_price = find_item_price_in_currency (total->currency,
+ item);
+ if (NULL == unit_price)
+ {
+ /* Drop the currency: we have no price in one of
+ the selected products */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Product `%s' has no price in %s: dropping currency\n",
+ item->product_id,
+ total->currency);
+ *total = uc->compute_price.totals[--uc->compute_price.totals_len];
+ continue;
+ }
+ if (GNUNET_OK !=
+ compute_line_total (unit_price,
+ item->quantity_value,
+ item->quantity_frac,
+ &line_total))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 >
+ TALER_amount_add (total,
+ total,
+ &line_total))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ c++;
+ }
+ }
+ if (0 == uc->compute_price.totals_len)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No currency available in which we have prices for all selected products!\n");
+ GNUNET_free (uc->compute_price.totals);
+ }
+ return (0 == uc->compute_price.totals_len)
+ ? GNUNET_NO
+ : GNUNET_OK;
+}
+
+
+/**
+ * Compute total for only the given @a currency.
+ *
+ * @param items_len length of @a items
+ * @param items inventory items
+ * @param currency currency to total
+ * @param[out] total computed total
+ * @return #GNUNET_OK on success
+ * #GNUNET_NO if we could not find a price in any accepted currency
+ * for all selected products
+ * #GNUNET_SYSERR on arithmetic issues (internal error)
+ */
+static enum GNUNET_GenericReturnValue
+compute_inventory_total (unsigned int items_len,
+ const struct InventoryTemplateItemContext *items,
+ const char *currency,
+ struct TALER_Amount *total)
+{
+ GNUNET_assert (NULL != currency);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (currency,
+ total));
+ for (unsigned int i = 0; i < items_len; i++)
+ {
+ const struct InventoryTemplateItemContext *item = &items[i];
+ const struct TALER_Amount *unit_price;
+ struct TALER_Amount line_total;
+
+ unit_price = find_item_price_in_currency (currency,
+ item);
+ if (NULL == unit_price)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "compute_inventory_total: no price in %s for product `%s'\n",
+ currency,
+ item->product_id);
+ return GNUNET_NO;
+ }
+ if (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_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Compute total price.
+ *
+ * @param[in,out] uc use context
+ */
+static void
+handle_phase_compute_price (struct UseContext *uc)
+{
+ const char *primary_currency;
+ enum GNUNET_GenericReturnValue ret;
+
+ switch (uc->template_type)
+ {
+ case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
+ uc->compute_price.totals
+ = GNUNET_new (struct TALER_Amount);
+ uc->compute_price.totals_len
+ = 1;
+ if (uc->parse_request.no_amount)
+ {
+ GNUNET_assert (! uc->template_contract.no_amount);
+ *uc->compute_price.totals
+ = uc->template_contract.amount;
+ }
+ else
+ {
+ GNUNET_assert (uc->template_contract.no_amount);
+ *uc->compute_price.totals
+ = uc->parse_request.amount;
+ }
+ uc->phase++;
+ return;
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
+ /* handled below */
+ break;
+ case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
+ {
+ const struct TALER_MERCHANT_TemplateContractPaivana *tcp
+ = &uc->template_contract.details.paivana;
+ json_t *choices;
+
+ choices = json_array ();
+ GNUNET_assert (NULL != choices);
+ for (size_t i = 0; i < tcp->choices_len; i++)
+ {
+ /* Make deep copy, we're going to MODIFY it! */
+ struct TALER_MERCHANT_ContractChoice choice
+ = tcp->choices[i];
+
+ choice.no_tip = uc->parse_request.no_tip;
+ if (! uc->parse_request.no_tip)
+ {
+ if (GNUNET_YES !=
+ TALER_amount_cmp_currency (&choice.amount,
+ &uc->parse_request.tip))
+ continue; /* tip does not match choice currency */
+ choice.tip = uc->parse_request.tip;
+ if (0 >
+ TALER_amount_add (&choice.amount,
+ &choice.amount,
+ &uc->parse_request.tip))
+ {
+ GNUNET_break (0);
+ use_reply_with_error (uc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
+ "tip");
+ return;
+ }
+ }
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ choices,
+ TALER_MERCHANT_json_from_contract_choice (&choice,
+ true)));
+ }
+ if (0 == json_array_size (choices))
+ {
+ GNUNET_break_op (0);
+ json_decref (choices);
+ use_reply_with_error (uc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_CURRENCY,
+ "tip");
+ return;
+ }
+ uc->compute_price.choices = choices;
+ }
+ /* Note: we already did the tip and pricing
+ fully here, so we skip these phases. */
+ uc->phase = USE_PHASE_CREATE_ORDER;
+ return;
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
+ GNUNET_assert (0);
+ }
+ primary_currency = uc->template_contract.currency;
+ if (! uc->parse_request.no_amount)
+ primary_currency = uc->parse_request.amount.currency;
+ if (! uc->parse_request.no_tip)
+ primary_currency = uc->parse_request.tip.currency;
+ if (NULL == primary_currency)
+ {
+ ret = compute_totals_per_currency (uc);
+ }
+ else
+ {
+ uc->compute_price.totals
+ = GNUNET_new (struct TALER_Amount);
+ uc->compute_price.totals_len
+ = 1;
+ ret = compute_inventory_total (
+ uc->parse_request.inventory.items_len,
+ uc->parse_request.inventory.items,
+ primary_currency,
+ uc->compute_price.totals);
+ }
+ if (GNUNET_SYSERR == ret)
+ {
+ use_reply_with_error (
+ uc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
+ "calculation of currency totals failed");
+ return;
+ }
+ if (GNUNET_NO == ret)
+ {
+ use_reply_with_error (uc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_CURRENCY,
+ NULL);
+ return;
+ }
+
+ uc->phase++;
+}
+
+
+/* ***************** USE_PHASE_CHECK_TIP **************** */
+
+
+/**
+ * Check that tip specified is reasonable and add to total.
+ *
+ * @param[in,out] uc use context
+ */
+static void
+handle_phase_check_tip (struct UseContext *uc)
+{
+ struct TALER_Amount *total_amount;
+
+ if (uc->parse_request.no_tip)
+ {
+ uc->phase++;
+ return;
+ }
+ if (0 == uc->compute_price.totals_len)
+ {
+ if (! TMH_test_exchange_configured_for_currency (
+ uc->parse_request.tip.currency))
+ {
+ GNUNET_break_op (0);
+ use_reply_with_error (uc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+ "Tip currency is not supported by backend");
+ return;
+ }
+ uc->compute_price.totals
+ = GNUNET_new (struct TALER_Amount);
+ uc->compute_price.totals_len
+ = 1;
+ *uc->compute_price.totals
+ = uc->parse_request.tip;
+ uc->phase++;
+ return;
+ }
+ GNUNET_assert (1 == uc->compute_price.totals_len);
+ total_amount = &uc->compute_price.totals[0];
+ if (GNUNET_YES !=
+ TALER_amount_cmp_currency (&uc->parse_request.tip,
+ total_amount))
+ {
+ GNUNET_break_op (0);
+ use_reply_with_error (uc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
+ uc->parse_request.tip.currency);
+ return;
+ }
+ if (0 >
+ TALER_amount_add (total_amount,
+ total_amount,
+ &uc->parse_request.tip))
+ {
+ GNUNET_break (0);
+ use_reply_with_error (uc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
+ "tip");
+ return;
+ }
+ uc->phase++;
+}
+
+
+/* ***************** USE_PHASE_CHECK_TOTAL **************** */
+
+/**
+ * Check that if the client specified a total,
+ * it matches our own calculation.
+ *
+ * @param[in,out] uc use context
+ */
+static void
+handle_phase_check_total (struct UseContext *uc)
+{
+ GNUNET_assert (1 <= uc->compute_price.totals_len);
+ if (! uc->parse_request.no_amount)
+ {
+ GNUNET_assert (1 == uc->compute_price.totals_len);
+ GNUNET_assert (GNUNET_YES ==
+ TALER_amount_cmp_currency (&uc->parse_request.amount,
+ &uc->compute_price.totals[0]));
+ if (0 !=
+ TALER_amount_cmp (&uc->parse_request.amount,
+ &uc->compute_price.totals[0]))
+ {
+ GNUNET_break_op (0);
+ use_reply_with_error (uc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
+ TALER_amount2s (&uc->compute_price.totals[0]));
+ return;
+ }
+ }
+ uc->phase++;
+}
+
+
+/* ***************** USE_PHASE_CREATE_ORDER **************** */
+
+
+/**
+ * Create order request for inventory templates.
+ *
+ * @param[in,out] uc use context
+ */
+static void
+create_using_templates_inventory (struct UseContext *uc)
+{
+ json_t *inventory_products;
+ json_t *choices;
+
+ inventory_products = json_array ();
+ GNUNET_assert (NULL != inventory_products);
+ for (unsigned int i = 0;
+ i < uc->parse_request.inventory.items_len;
+ i++)
+ {
+ const struct InventoryTemplateItemContext *item =
+ &uc->parse_request.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))));
+ }
+ choices = json_array ();
+ GNUNET_assert (NULL != choices);
+ for (unsigned int i = 0;
+ i < uc->compute_price.totals_len;
+ i++)
+ {
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ choices,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("amount",
+ &uc->compute_price.totals[i]),
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_amount ("tip",
+ uc->parse_request.no_tip
+ ? NULL
+ : &uc->parse_request.tip))
+ )));
+ }
+
+ uc->ihc.request_body
+ = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("otp_id",
+ uc->lookup_template.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",
+ NULL == uc->parse_request.summary
+ ? uc->template_contract.summary
+ : uc->parse_request.summary))));
+}
+
+
+/**
+ * Create order request for fixed-order templates.
+ *
+ * @param[in,out] uc use context
+ */
+static void
+create_using_templates_fixed (struct UseContext *uc)
+{
+ uc->ihc.request_body
+ = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("otp_id",
+ uc->lookup_template.etp.otp_id)),
+ GNUNET_JSON_pack_object_steal (
+ "order",
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount (
+ "amount",
+ &uc->compute_price.totals[0]),
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_amount ("tip",
+ uc->parse_request.no_tip
+ ? NULL
+ : &uc->parse_request.tip)),
+ GNUNET_JSON_pack_string (
+ "summary",
+ NULL == uc->parse_request.summary
+ ? uc->template_contract.summary
+ : uc->parse_request.summary))));
+}
+
+
+/**
+ * Create order request for paivana templates.
+ *
+ * @param[in,out] uc use context
+ */
+static void
+create_using_templates_paivana (struct UseContext *uc)
+{
+ uc->ihc.request_body
+ = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string (
+ "session_id",
+ uc->parse_request.paivana.paivana_id),
+ GNUNET_JSON_pack_object_steal (
+ "order",
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("version",
+ 1),
+ GNUNET_JSON_pack_array_incref ("choices",
+ uc->compute_price.choices),
+ GNUNET_JSON_pack_string (
+ "summary",
+ NULL == uc->parse_request.summary
+ ? uc->template_contract.summary
+ : uc->parse_request.summary),
+ GNUNET_JSON_pack_string ("fulfillment_url",
+ uc->parse_request.paivana.website))));
+}
+
+
+static void
+handle_phase_create_order (struct UseContext *uc)
+{
+ GNUNET_assert (NULL == uc->ihc.request_body);
+ switch (uc->template_type)
+ {
+ case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
+ create_using_templates_fixed (uc);
+ break;
+ case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
+ create_using_templates_paivana (uc);
+ break;
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
+ create_using_templates_inventory (uc);
+ break;
+ case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
+ GNUNET_assert (0);
+ }
+ uc->phase++;
+}
+
+
+/* ***************** Main handler **************** */
+
+MHD_RESULT
+TMH_post_using_templates_ID (
+ const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct UseContext *uc = hc->ctx;
+
+ (void) rh;
+ if (NULL == uc)
+ {
+ uc = GNUNET_new (struct UseContext);
+ uc->hc = hc;
+ hc->ctx = uc;
+ hc->cc = &cleanup_use_context;
+ uc->ihc.instance = hc->instance;
+ uc->phase = USE_PHASE_PARSE_REQUEST;
+ uc->template_type = TALER_MERCHANT_TEMPLATE_TYPE_INVALID;
+ }
+
+ while (1)
+ {
+ switch (uc->phase)
+ {
+ case USE_PHASE_PARSE_REQUEST:
+ handle_phase_parse_request (uc);
+ break;
+ case USE_PHASE_LOOKUP_TEMPLATE:
+ handle_phase_lookup_template (uc);
+ break;
+ case USE_PHASE_PARSE_TEMPLATE:
+ handle_phase_template_contract (uc);
+ break;
+ case USE_PHASE_DB_FETCH:
+ handle_phase_db_fetch (uc);
+ break;
+ case USE_PHASE_VERIFY:
+ handle_phase_verify (uc);
+ break;
+ case USE_PHASE_COMPUTE_PRICE:
+ handle_phase_compute_price (uc);
+ break;
+ case USE_PHASE_CHECK_TIP:
+ handle_phase_check_tip (uc);
+ break;
+ case USE_PHASE_CHECK_TOTAL:
+ handle_phase_check_total (uc);
+ break;
+ case USE_PHASE_CREATE_ORDER:
+ handle_phase_create_order (uc);
+ 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;
+ }
+ }
+}
diff --git a/src/backend/taler-merchant-httpd_post-templates-ID.h b/src/backend/taler-merchant-httpd_post-templates-ID.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ (C) 2022 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler-merchant-httpd_post-using-templates.h
+ * @brief headers for POST /using-templates handler
+ * @author Priscilla Huang
+ */
+#ifndef TALER_MERCHANT_HTTPD_POST_TEMPLATES_ID_H
+#define TALER_MERCHANT_HTTPD_POST_TEMPLATES_ID_H
+
+#include "taler-merchant-httpd.h"
+
+/**
+ * Generate a template that customer can use it. Returns an MHD_RESULT.
+ *
+ * @param rh details about this request handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_post_using_templates_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_post-using-templates.c b/src/backend/taler-merchant-httpd_post-using-templates.c
@@ -1,1782 +0,0 @@
-/*
- This file is part of TALER
- (C) 2022-2026 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation; either version 3,
- or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public
- License along with TALER; see the file COPYING. If not,
- see <http://www.gnu.org/licenses/>
-*/
-
-/**
- * @file taler-merchant-httpd_post-using-templates.c
- * @brief implementing POST /using-templates request handling
- * @author Priscilla HUANG
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler-merchant-httpd_post-using-templates.h"
-#include "taler-merchant-httpd_private-post-orders.h"
-#include "taler-merchant-httpd_helper.h"
-#include "taler-merchant-httpd_exchanges.h"
-#include "taler_merchant_util.h"
-#include <taler/taler_json_lib.h>
-#include <regex.h>
-
-
-/**
- * Item selected from inventory_selection.
- */
-struct InventoryTemplateItemContext
-{
- /**
- * Product ID as referenced in inventory.
- */
- const char *product_id;
-
- /**
- * Unit quantity string as provided by the client.
- */
- const 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;
-};
-
-
-/**
- * Our context.
- */
-enum UsePhase
-{
- /**
- * Parse request payload into context fields.
- */
- USE_PHASE_PARSE_REQUEST,
-
- /**
- * Fetch template details from the database.
- */
- USE_PHASE_LOOKUP_TEMPLATE,
-
- /**
- * Parse template.
- */
- USE_PHASE_PARSE_TEMPLATE,
-
- /**
- * Load additional details (like products and
- * categories) needed for verification and
- * price computation.
- */
- USE_PHASE_DB_FETCH,
-
- /**
- * Validate request and template compatibility.
- */
- USE_PHASE_VERIFY,
-
- /**
- * Compute price of the order.
- */
- USE_PHASE_COMPUTE_PRICE,
-
- /**
- * Handle tip.
- */
- USE_PHASE_CHECK_TIP,
-
- /**
- * Check if client-supplied total amount matches
- * our calculation (if we did any).
- */
- USE_PHASE_CHECK_TOTAL,
-
- /**
- * Construct the internal order request body.
- */
- USE_PHASE_CREATE_ORDER,
-
- /**
- * Submit the order to the shared order handler.
- */
- USE_PHASE_SUBMIT_ORDER,
-
- /**
- * Finished successfully with MHD_YES.
- */
- USE_PHASE_FINISHED_MHD_YES,
-
- /**
- * Finished with MHD_NO.
- */
- USE_PHASE_FINISHED_MHD_NO
-};
-
-struct UseContext
-{
- /**
- * Context for our handler.
- */
- struct TMH_HandlerContext *hc;
-
- /**
- * Internal handler context we are passing into the
- * POST /private/orders handler.
- */
- struct TMH_HandlerContext ihc;
-
- /**
- * Phase we are currently in.
- */
- enum UsePhase phase;
-
- /**
- * Template type from the contract.
- */
- enum TALER_MERCHANT_TemplateType template_type;
-
- /**
- * Information set in the #USE_PHASE_PARSE_REQUEST phase.
- */
- struct
- {
- /**
- * Summary override from request, if any.
- */
- const char *summary;
-
- /**
- * 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;
-
- /**
- * Parsed fields for inventory templates.
- */
- struct
- {
- /**
- * Selected products from inventory_selection.
- */
- struct InventoryTemplateItemContext *items;
-
- /**
- * Length of @e items.
- */
- unsigned int items_len;
-
- } inventory;
-
- /**
- * Request details if this is a paivana instantiation.
- */
- struct
- {
-
- /**
- * Target website for the request.
- */
- const char *website;
-
- /**
- * Unique client identifier, consisting of
- * current time, "-", and the hash of a nonce,
- * the website and the current time.
- */
- const char *paivana_id;
-
- } paivana;
-
- } parse_request;
-
- /**
- * Information set in the #USE_PHASE_LOOKUP_TEMPLATE phase.
- */
- struct
- {
-
- /**
- * Our template details from the DB.
- */
- struct TALER_MERCHANTDB_TemplateDetails etp;
-
- } lookup_template;
-
- /**
- * Information set in the #USE_PHASE_PARSE_TEMPLATE phase.
- */
- struct TALER_MERCHANT_TemplateContract template_contract;
-
- /**
- * Information set in the #USE_PHASE_COMPUTE_PRICE phase.
- */
- struct
- {
-
- /**
- * Per-currency totals across selected products (without tips).
- */
- struct TALER_Amount *totals;
-
- /**
- * Length of @e totals.
- */
- unsigned int totals_len;
-
- /**
- * Array of payment choices, used with Paviana.
- */
- json_t *choices;
-
- } compute_price;
-
-};
-
-
-/**
- * Clean up inventory items.
- *
- * @param items_len length of @a items
- * @param[in] items item array to free
- */
-static void
-cleanup_inventory_items (unsigned int items_len,
- struct InventoryTemplateItemContext items[static
- items_len])
-{
- for (unsigned int i = 0; i < items_len; i++)
- {
- struct InventoryTemplateItemContext *item = &items[i];
-
- TALER_MERCHANTDB_product_details_free (&item->pd);
- GNUNET_free (item->categories);
- }
- GNUNET_free (items);
-}
-
-
-/**
- * Clean up a `struct UseContext *`
- *
- * @param[in] cls a `struct UseContext *`
- */
-static void
-cleanup_use_context (void *cls)
-{
- struct UseContext *uc = cls;
-
- TALER_MERCHANTDB_template_details_free (&uc->lookup_template.etp);
- if (NULL !=
- uc->parse_request.inventory.items)
- cleanup_inventory_items (uc->parse_request.inventory.items_len,
- uc->parse_request.inventory.items);
- GNUNET_free (uc->compute_price.totals);
- uc->compute_price.totals_len = 0;
- json_decref (uc->compute_price.choices);
- if (NULL != uc->ihc.cc)
- uc->ihc.cc (uc->ihc.ctx);
- GNUNET_free (uc->ihc.infix);
- json_decref (uc->ihc.request_body);
- GNUNET_free (uc);
-}
-
-
-/**
- * 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)
-{
- GNUNET_assert (GNUNET_OK != 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 http_status HTTP status code
- * @param ec error code
- * @param detail error detail
- */
-static void
-use_reply_with_error (struct UseContext *uc,
- unsigned int http_status,
- enum TALER_ErrorCode ec,
- const char *detail)
-{
- MHD_RESULT mret;
-
- mret = TALER_MHD_reply_with_error (uc->hc->connection,
- http_status,
- ec,
- detail);
- use_finalize (uc,
- mret);
-}
-
-
-/* ***************** USE_PHASE_PARSE_REQUEST **************** */
-
-/**
- * Parse request data for inventory templates.
- *
- * @param[in,out] uc use context
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_using_templates_inventory_request (
- struct UseContext *uc)
-{
- const json_t *inventory_selection;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_array_const ("inventory_selection",
- &inventory_selection),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_GenericReturnValue res;
-
- GNUNET_assert (NULL == uc->ihc.request_body);
- res = TALER_MHD_parse_json_data (uc->hc->connection,
- uc->hc->request_body,
- spec);
- if (GNUNET_OK != res)
- {
- GNUNET_break_op (0);
- use_finalize_parse (uc,
- res);
- return GNUNET_SYSERR;
- }
-
- if ( (! uc->parse_request.no_amount) &&
- (! TMH_test_exchange_configured_for_currency (
- uc->parse_request.amount.currency)) )
- {
- GNUNET_break_op (0);
- use_reply_with_error (uc,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
- "Currency is not supported by backend");
- return GNUNET_SYSERR;
- }
-
- for (size_t i = 0; i < json_array_size (inventory_selection); i++)
- {
- struct InventoryTemplateItemContext item = { 0 };
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_string ("product_id",
- &item.product_id),
- GNUNET_JSON_spec_string ("quantity",
- &item.unit_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);
- use_reply_with_error (uc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "inventory_selection");
- return GNUNET_SYSERR;
- }
-
- GNUNET_array_append (uc->parse_request.inventory.items,
- uc->parse_request.inventory.items_len,
- item);
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Parse request data for paivana templates.
- *
- * @param[in,out] uc use context
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-parse_using_templates_paivana_request (
- struct UseContext *uc)
-{
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("website",
- &uc->parse_request.paivana.website),
- GNUNET_JSON_spec_string ("paivana_id",
- &uc->parse_request.paivana.paivana_id),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_GenericReturnValue res;
- struct GNUNET_HashCode sh;
- unsigned long long tv;
- const char *dash;
-
- GNUNET_assert (NULL == uc->ihc.request_body);
- res = TALER_MHD_parse_json_data (uc->hc->connection,
- uc->hc->request_body,
- spec);
- if (GNUNET_OK != res)
- {
- GNUNET_break_op (0);
- use_finalize_parse (uc,
- res);
- return GNUNET_SYSERR;
- }
- if (1 !=
- sscanf (uc->parse_request.paivana.paivana_id,
- "%llu-",
- &tv))
- {
- GNUNET_break_op (0);
- use_reply_with_error (uc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "paivana_id");
- return GNUNET_SYSERR;
- }
- dash = strchr (uc->parse_request.paivana.paivana_id,
- '-');
- GNUNET_assert (NULL != dash);
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (dash + 1,
- strlen (dash + 1),
- &sh,
- sizeof (sh)))
- {
- GNUNET_break_op (0);
- use_reply_with_error (uc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "paivana_id");
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Main function for the #USE_PHASE_PARSE_REQUEST.
- *
- * @param[in,out] uc context to update
- */
-static void
-handle_phase_parse_request (
- struct UseContext *uc)
-{
- const char *template_type = NULL;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("template_type",
- &template_type),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_amount_any ("tip",
- &uc->parse_request.tip),
- &uc->parse_request.no_tip),
- 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.amount),
- &uc->parse_request.no_amount),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (uc->hc->connection,
- uc->hc->request_body,
- spec);
- if (GNUNET_OK != res)
- {
- GNUNET_break_op (0);
- use_finalize_parse (uc,
- res);
- return;
- }
- if (NULL == template_type)
- template_type = "fixed-order";
- uc->template_type
- = TALER_MERCHANT_template_type_from_string (
- template_type);
- switch (uc->template_type)
- {
- case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
- /* nothig left to do */
- uc->phase++;
- return;
- case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
- res = parse_using_templates_paivana_request (uc);
- if (GNUNET_OK == res)
- uc->phase++;
- return;
- case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
- res = parse_using_templates_inventory_request (uc);
- if (GNUNET_OK == res)
- uc->phase++;
- return;
- case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
- break;
- }
- GNUNET_break (0);
- use_reply_with_error (
- uc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "template_type");
-}
-
-
-/* ***************** USE_PHASE_LOOKUP_TEMPLATE **************** */
-
-/**
- * Main function for the #USE_PHASE_LOOKUP_TEMPLATE.
- *
- * @param[in,out] uc context to update
- */
-static void
-handle_phase_lookup_template (
- struct UseContext *uc)
-{
- struct TMH_MerchantInstance *mi = uc->hc->instance;
- const char *template_id = uc->hc->infix;
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TMH_db->lookup_template (TMH_db->cls,
- mi->settings.id,
- template_id,
- &uc->lookup_template.etp);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- /* Clean up and fail hard */
- GNUNET_break (0);
- use_reply_with_error (uc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_template");
- return;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- /* this should be impossible (single select) */
- GNUNET_break (0);
- use_reply_with_error (uc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_template");
- return;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* template not found! */
- use_reply_with_error (uc,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN,
- template_id);
- return;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* all good */
- break;
- }
- if (uc->template_type !=
- TALER_MERCHANT_template_type_from_contract (
- uc->lookup_template.etp.template_contract))
- {
- GNUNET_break_op (0);
- use_reply_with_error (
- uc,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_POST_USING_TEMPLATES_WRONG_TYPE,
- "template_contract has different type");
- return;
- }
- uc->phase++;
-}
-
-
-/* ***************** USE_PHASE_PARSE_TEMPLATE **************** */
-
-
-/**
- * Parse template.
- *
- * @param[in,out] uc use context
- */
-static void
-handle_phase_template_contract (struct UseContext *uc)
-{
- const char *err_name;
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MERCHANT_template_contract_parse (
- uc->lookup_template.etp.template_contract,
- &uc->template_contract,
- &err_name);
- if (GNUNET_OK != res)
- {
- GNUNET_break (0);
- use_reply_with_error (uc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- err_name);
- return;
- }
- uc->phase++;
-}
-
-
-/* ***************** USE_PHASE_DB_FETCH **************** */
-
-/**
- * Fetch DB data for inventory templates.
- *
- * @param[in,out] uc use context
- */
-static void
-handle_phase_db_fetch (struct UseContext *uc)
-{
- struct TMH_MerchantInstance *mi = uc->hc->instance;
-
- switch (uc->template_type)
- {
- case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
- uc->phase++;
- return;
- case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
- uc->phase++;
- return;
- case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
- break;
- case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
- GNUNET_assert (0);
- }
-
- for (unsigned int i = 0;
- i < uc->parse_request.inventory.items_len;
- i++)
- {
- struct InventoryTemplateItemContext *item =
- &uc->parse_request.inventory.items[i];
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TMH_db->lookup_product (TMH_db->cls,
- mi->settings.id,
- item->product_id,
- &item->pd,
- &item->num_categories,
- &item->categories);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- use_reply_with_error (uc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_product");
- return;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (0);
- use_reply_with_error (uc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_product");
- return;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- use_reply_with_error (uc,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN,
- item->product_id);
- return;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- }
- }
- uc->phase++;
-}
-
-
-/* *************** Helpers for USE_PHASE_VERIFY ***************** */
-
-/**
- * Check if the given product ID appears in the array of allowed_products.
- *
- * @param allowed_products JSON array of product IDs allowed by the template, may be NULL
- * @param product_id product ID to check
- * @return true if the product ID is in the list
- */
-static bool
-product_id_allowed (const json_t *allowed_products,
- const char *product_id)
-{
- const json_t *entry;
- size_t idx;
-
- if (NULL == allowed_products)
- return false;
- json_array_foreach ((json_t *) allowed_products, idx, entry)
- {
- if (! json_is_string (entry))
- {
- GNUNET_break (0);
- continue;
- }
- if (0 == strcmp (json_string_value (entry),
- product_id))
- return true;
- }
- return false;
-}
-
-
-/**
- * Check if any product category is in the selected_categories list.
- *
- * @param allowed_categories JSON array of categories allowed by the template, may be NULL
- * @param num_categories length of @a categories
- * @param categories list of categories of the selected product
- * @return true if any category of the product is in the list of allowed categories matches
- */
-static bool
-category_allowed (const json_t *allowed_categories,
- size_t num_categories,
- const uint64_t categories[num_categories])
-{
- const json_t *entry;
- size_t idx;
-
- if (NULL == allowed_categories)
- return false;
- json_array_foreach ((json_t *) allowed_categories,
- idx,
- entry)
- {
- uint64_t selected_id;
-
- if (! json_is_integer (entry))
- {
- GNUNET_break (0);
- continue;
- }
- if (0 > json_integer_value (entry))
- {
- GNUNET_break (0);
- continue;
- }
- selected_id = (uint64_t) json_integer_value (entry);
- for (size_t i = 0; i < num_categories; i++)
- {
- if (categories[i] == selected_id)
- return true;
- }
- }
- return false;
-}
-
-
-/**
- * Verify request data for inventory templates.
- * Checks that the selected products are allowed
- * for this template.
- *
- * @param[in,out] uc use context
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-verify_using_templates_inventory (struct UseContext *uc)
-{
- if (uc->template_contract.details.inventory.choose_one &&
- (1 != uc->parse_request.inventory.items_len))
- {
- GNUNET_break_op (0);
- use_reply_with_error (uc,
- MHD_HTTP_CONFLICT,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "inventory_selection");
- return GNUNET_SYSERR;
- }
- if (uc->template_contract.details.inventory.selected_all)
- return GNUNET_OK;
- for (unsigned int i = 0;
- i < uc->parse_request.inventory.items_len;
- i++)
- {
- struct InventoryTemplateItemContext *item =
- &uc->parse_request.inventory.items[i];
- const char *eparam = NULL;
-
- if (GNUNET_OK !=
- TALER_MERCHANT_vk_process_quantity_inputs (
- TALER_MERCHANT_VK_QUANTITY,
- item->pd.allow_fractional_quantity,
- true,
- 0,
- false,
- item->unit_quantity,
- &item->quantity_value,
- &item->quantity_frac,
- &eparam))
- {
- GNUNET_break_op (0);
- use_reply_with_error (uc,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- eparam);
- return GNUNET_SYSERR;
- }
-
- if (0 == item->pd.price_array_length)
- {
- GNUNET_break (0);
- use_reply_with_error (uc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- "price_array");
- return GNUNET_SYSERR;
- }
- }
-
- for (unsigned int i = 0;
- i < uc->parse_request.inventory.items_len;
- i++)
- {
- struct InventoryTemplateItemContext *item =
- &uc->parse_request.inventory.items[i];
-
- if (product_id_allowed (uc->template_contract.details.inventory.
- selected_products,
- item->product_id))
- continue;
- if (category_allowed (
- uc->template_contract.details.inventory.selected_categories,
- item->num_categories,
- item->categories))
- continue;
- GNUNET_break_op (0);
- use_reply_with_error (
- uc,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_POST_USING_TEMPLATES_WRONG_PRODUCT,
- item->product_id);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Verify request data for fixed-order templates.
- * As here we cannot compute the total amount, either
- * the template or the client request must provide it.
- *
- * @param[in,out] uc use context
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-verify_using_templates_fixed (
- struct UseContext *uc)
-{
- if ( (! uc->parse_request.no_amount) &&
- (! uc->template_contract.no_amount) )
- {
- GNUNET_break_op (0);
- use_reply_with_error (uc,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
- NULL);
- return GNUNET_SYSERR;
- }
- if (uc->parse_request.no_amount &&
- uc->template_contract.no_amount)
- {
- GNUNET_break_op (0);
- use_reply_with_error (uc,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_AMOUNT,
- NULL);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Verify request data for paivana templates.
- *
- * @param[in,out] uc use context
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-verify_using_templates_paivana (
- struct UseContext *uc)
-{
- if (NULL != uc->template_contract.details.paivana.website_regex)
- {
- regex_t ex;
- bool allowed = false;
-
- if (0 != regcomp (&ex,
- uc->template_contract.details.paivana.website_regex,
- REG_NOSUB | REG_EXTENDED))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
- if (0 ==
- regexec (&ex,
- uc->parse_request.paivana.website,
- 0, NULL,
- 0))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Website `%s' allowed by template\n",
- uc->parse_request.paivana.website);
- allowed = true;
- }
- regfree (&ex);
- if (! allowed)
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Verify that the client request is structurally acceptable for the specified
- * template. Does NOT check the total amount being reasonable.
- *
- * @param[in,out] uc use context
- */
-static void
-handle_phase_verify (
- struct UseContext *uc)
-{
- enum GNUNET_GenericReturnValue res = GNUNET_SYSERR;
-
- if ( (NULL != uc->parse_request.summary) &&
- (NULL != uc->template_contract.summary) )
- {
- GNUNET_break_op (0);
- use_reply_with_error (uc,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_POST_USING_TEMPLATES_SUMMARY_CONFLICT_TEMPLATES_CONTRACT_SUBJECT,
- NULL);
- return;
- }
- if ( (NULL == uc->parse_request.summary) &&
- (NULL == uc->template_contract.summary) )
- {
- GNUNET_break_op (0);
- use_reply_with_error (uc,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_SUMMARY,
- NULL);
- return;
- }
- if ( (! uc->parse_request.no_amount) &&
- (NULL != uc->template_contract.currency) &&
- (0 != strcasecmp (uc->template_contract.currency,
- uc->parse_request.amount.currency)) )
- {
- GNUNET_break_op (0);
- use_reply_with_error (uc,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
- uc->template_contract.currency);
- return;
- }
- switch (uc->template_type)
- {
- case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
- res = verify_using_templates_fixed (uc);
- break;
- case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
- res = verify_using_templates_paivana (uc);
- break;
- case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
- res = verify_using_templates_inventory (uc);
- break;
- case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
- GNUNET_assert (0);
- }
- if (GNUNET_OK == res)
- uc->phase++;
-}
-
-
-/* ***************** USE_PHASE_COMPUTE_PRICE **************** */
-
-
-/**
- * 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;
-
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (unit_price->currency,
- line_total));
- if ( (0 != quantity) &&
- (0 >
- TALER_amount_multiply (line_total,
- unit_price,
- (uint32_t) quantity)) )
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (0 == quantity_frac)
- return GNUNET_OK;
- if (0 >
- TALER_amount_multiply (&tmp,
- unit_price,
- quantity_frac))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- TALER_amount_divide (&tmp,
- &tmp,
- TALER_MERCHANT_UNIT_FRAC_BASE);
- if (0 >
- TALER_amount_add (line_total,
- line_total,
- &tmp))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Find the price of the given @a item in the specified
- * @a currency.
- *
- * @param currency currency to search price in
- * @param item item to check prices of
- * @return NULL if a suitable price was not found
- */
-static const struct TALER_Amount *
-find_item_price_in_currency (
- const char *currency,
- const struct InventoryTemplateItemContext *item)
-{
- for (size_t j = 0; j < item->pd.price_array_length; j++)
- {
- if (0 == strcasecmp (item->pd.price_array[j].currency,
- currency))
- return &item->pd.price_array[j];
- }
- return NULL;
-}
-
-
-/**
- * Compute totals for all currencies shared across selected products.
- *
- * @param[in,out] uc use context
- * @return #GNUNET_OK on success (including no price due to no items)
- * #GNUNET_NO if we could not find a price in any accepted currency
- * for all selected products
- * #GNUNET_SYSERR on arithmetic issues (internal error)
- */
-static enum GNUNET_GenericReturnValue
-compute_totals_per_currency (struct UseContext *uc)
-{
- const struct InventoryTemplateItemContext *items
- = uc->parse_request.inventory.items;
- unsigned int items_len = uc->parse_request.inventory.items_len;
-
- if (0 == items_len)
- return GNUNET_OK;
- for (size_t i = 0; i < items[0].pd.price_array_length; i++)
- {
- const struct TALER_Amount *price
- = &items[0].pd.price_array[i];
- struct TALER_Amount zero;
-
- if (! TMH_test_exchange_configured_for_currency (price->currency))
- continue;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (price->currency,
- &zero));
- GNUNET_array_append (uc->compute_price.totals,
- uc->compute_price.totals_len,
- zero);
- }
- if (0 == uc->compute_price.totals_len)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "No currency supported by our configuration in which we have prices for first selected product!\n");
- return GNUNET_NO;
- }
- /* Loop through items, ensure each currency exists and sum totals. */
- for (unsigned int i = 0; i < items_len; i++)
- {
- const struct InventoryTemplateItemContext *item = &items[i];
- unsigned int c = 0;
-
- while (c < uc->compute_price.totals_len)
- {
- struct TALER_Amount *total = &uc->compute_price.totals[c];
- const struct TALER_Amount *unit_price;
- struct TALER_Amount line_total;
-
- unit_price = find_item_price_in_currency (total->currency,
- item);
- if (NULL == unit_price)
- {
- /* Drop the currency: we have no price in one of
- the selected products */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Product `%s' has no price in %s: dropping currency\n",
- item->product_id,
- total->currency);
- *total = uc->compute_price.totals[--uc->compute_price.totals_len];
- continue;
- }
- if (GNUNET_OK !=
- compute_line_total (unit_price,
- item->quantity_value,
- item->quantity_frac,
- &line_total))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (0 >
- TALER_amount_add (total,
- total,
- &line_total))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- c++;
- }
- }
- if (0 == uc->compute_price.totals_len)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "No currency available in which we have prices for all selected products!\n");
- GNUNET_free (uc->compute_price.totals);
- }
- return (0 == uc->compute_price.totals_len)
- ? GNUNET_NO
- : GNUNET_OK;
-}
-
-
-/**
- * Compute total for only the given @a currency.
- *
- * @param items_len length of @a items
- * @param items inventory items
- * @param currency currency to total
- * @param[out] total computed total
- * @return #GNUNET_OK on success
- * #GNUNET_NO if we could not find a price in any accepted currency
- * for all selected products
- * #GNUNET_SYSERR on arithmetic issues (internal error)
- */
-static enum GNUNET_GenericReturnValue
-compute_inventory_total (unsigned int items_len,
- const struct InventoryTemplateItemContext *items,
- const char *currency,
- struct TALER_Amount *total)
-{
- GNUNET_assert (NULL != currency);
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (currency,
- total));
- for (unsigned int i = 0; i < items_len; i++)
- {
- const struct InventoryTemplateItemContext *item = &items[i];
- const struct TALER_Amount *unit_price;
- struct TALER_Amount line_total;
-
- unit_price = find_item_price_in_currency (currency,
- item);
- if (NULL == unit_price)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "compute_inventory_total: no price in %s for product `%s'\n",
- currency,
- item->product_id);
- return GNUNET_NO;
- }
- if (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_break (0);
- return GNUNET_SYSERR;
- }
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Compute total price.
- *
- * @param[in,out] uc use context
- */
-static void
-handle_phase_compute_price (struct UseContext *uc)
-{
- const char *primary_currency;
- enum GNUNET_GenericReturnValue ret;
-
- switch (uc->template_type)
- {
- case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
- uc->compute_price.totals
- = GNUNET_new (struct TALER_Amount);
- uc->compute_price.totals_len
- = 1;
- if (uc->parse_request.no_amount)
- {
- GNUNET_assert (! uc->template_contract.no_amount);
- *uc->compute_price.totals
- = uc->template_contract.amount;
- }
- else
- {
- GNUNET_assert (uc->template_contract.no_amount);
- *uc->compute_price.totals
- = uc->parse_request.amount;
- }
- uc->phase++;
- return;
- case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
- /* handled below */
- break;
- case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
- {
- const struct TALER_MERCHANT_TemplateContractPaivana *tcp
- = &uc->template_contract.details.paivana;
- json_t *choices;
-
- choices = json_array ();
- GNUNET_assert (NULL != choices);
- for (size_t i = 0; i < tcp->choices_len; i++)
- {
- /* Make deep copy, we're going to MODIFY it! */
- struct TALER_MERCHANT_ContractChoice choice
- = tcp->choices[i];
-
- choice.no_tip = uc->parse_request.no_tip;
- if (! uc->parse_request.no_tip)
- {
- if (GNUNET_YES !=
- TALER_amount_cmp_currency (&choice.amount,
- &uc->parse_request.tip))
- continue; /* tip does not match choice currency */
- choice.tip = uc->parse_request.tip;
- if (0 >
- TALER_amount_add (&choice.amount,
- &choice.amount,
- &uc->parse_request.tip))
- {
- GNUNET_break (0);
- use_reply_with_error (uc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
- "tip");
- return;
- }
- }
- GNUNET_assert (0 ==
- json_array_append_new (
- choices,
- TALER_MERCHANT_json_from_contract_choice (&choice,
- true)));
- }
- if (0 == json_array_size (choices))
- {
- GNUNET_break_op (0);
- json_decref (choices);
- use_reply_with_error (uc,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_CURRENCY,
- "tip");
- return;
- }
- uc->compute_price.choices = choices;
- }
- /* Note: we already did the tip and pricing
- fully here, so we skip these phases. */
- uc->phase = USE_PHASE_CREATE_ORDER;
- return;
- case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
- GNUNET_assert (0);
- }
- primary_currency = uc->template_contract.currency;
- if (! uc->parse_request.no_amount)
- primary_currency = uc->parse_request.amount.currency;
- if (! uc->parse_request.no_tip)
- primary_currency = uc->parse_request.tip.currency;
- if (NULL == primary_currency)
- {
- ret = compute_totals_per_currency (uc);
- }
- else
- {
- uc->compute_price.totals
- = GNUNET_new (struct TALER_Amount);
- uc->compute_price.totals_len
- = 1;
- ret = compute_inventory_total (
- uc->parse_request.inventory.items_len,
- uc->parse_request.inventory.items,
- primary_currency,
- uc->compute_price.totals);
- }
- if (GNUNET_SYSERR == ret)
- {
- use_reply_with_error (
- uc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
- "calculation of currency totals failed");
- return;
- }
- if (GNUNET_NO == ret)
- {
- use_reply_with_error (uc,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_POST_USING_TEMPLATES_NO_CURRENCY,
- NULL);
- return;
- }
-
- uc->phase++;
-}
-
-
-/* ***************** USE_PHASE_CHECK_TIP **************** */
-
-
-/**
- * Check that tip specified is reasonable and add to total.
- *
- * @param[in,out] uc use context
- */
-static void
-handle_phase_check_tip (struct UseContext *uc)
-{
- struct TALER_Amount *total_amount;
-
- if (uc->parse_request.no_tip)
- {
- uc->phase++;
- return;
- }
- if (0 == uc->compute_price.totals_len)
- {
- if (! TMH_test_exchange_configured_for_currency (
- uc->parse_request.tip.currency))
- {
- GNUNET_break_op (0);
- use_reply_with_error (uc,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
- "Tip currency is not supported by backend");
- return;
- }
- uc->compute_price.totals
- = GNUNET_new (struct TALER_Amount);
- uc->compute_price.totals_len
- = 1;
- *uc->compute_price.totals
- = uc->parse_request.tip;
- uc->phase++;
- return;
- }
- GNUNET_assert (1 == uc->compute_price.totals_len);
- total_amount = &uc->compute_price.totals[0];
- if (GNUNET_YES !=
- TALER_amount_cmp_currency (&uc->parse_request.tip,
- total_amount))
- {
- GNUNET_break_op (0);
- use_reply_with_error (uc,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_GENERIC_CURRENCY_MISMATCH,
- uc->parse_request.tip.currency);
- return;
- }
- if (0 >
- TALER_amount_add (total_amount,
- total_amount,
- &uc->parse_request.tip))
- {
- GNUNET_break (0);
- use_reply_with_error (uc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
- "tip");
- return;
- }
- uc->phase++;
-}
-
-
-/* ***************** USE_PHASE_CHECK_TOTAL **************** */
-
-/**
- * Check that if the client specified a total,
- * it matches our own calculation.
- *
- * @param[in,out] uc use context
- */
-static void
-handle_phase_check_total (struct UseContext *uc)
-{
- GNUNET_assert (1 <= uc->compute_price.totals_len);
- if (! uc->parse_request.no_amount)
- {
- GNUNET_assert (1 == uc->compute_price.totals_len);
- GNUNET_assert (GNUNET_YES ==
- TALER_amount_cmp_currency (&uc->parse_request.amount,
- &uc->compute_price.totals[0]));
- if (0 !=
- TALER_amount_cmp (&uc->parse_request.amount,
- &uc->compute_price.totals[0]))
- {
- GNUNET_break_op (0);
- use_reply_with_error (uc,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_POST_USING_TEMPLATES_AMOUNT_CONFLICT_TEMPLATES_CONTRACT_AMOUNT,
- TALER_amount2s (&uc->compute_price.totals[0]));
- return;
- }
- }
- uc->phase++;
-}
-
-
-/* ***************** USE_PHASE_CREATE_ORDER **************** */
-
-
-/**
- * Create order request for inventory templates.
- *
- * @param[in,out] uc use context
- */
-static void
-create_using_templates_inventory (struct UseContext *uc)
-{
- json_t *inventory_products;
- json_t *choices;
-
- inventory_products = json_array ();
- GNUNET_assert (NULL != inventory_products);
- for (unsigned int i = 0;
- i < uc->parse_request.inventory.items_len;
- i++)
- {
- const struct InventoryTemplateItemContext *item =
- &uc->parse_request.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))));
- }
- choices = json_array ();
- GNUNET_assert (NULL != choices);
- for (unsigned int i = 0;
- i < uc->compute_price.totals_len;
- i++)
- {
- GNUNET_assert (0 ==
- json_array_append_new (
- choices,
- GNUNET_JSON_PACK (
- TALER_JSON_pack_amount ("amount",
- &uc->compute_price.totals[i]),
- GNUNET_JSON_pack_allow_null (
- TALER_JSON_pack_amount ("tip",
- uc->parse_request.no_tip
- ? NULL
- : &uc->parse_request.tip))
- )));
- }
-
- uc->ihc.request_body
- = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("otp_id",
- uc->lookup_template.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",
- NULL == uc->parse_request.summary
- ? uc->template_contract.summary
- : uc->parse_request.summary))));
-}
-
-
-/**
- * Create order request for fixed-order templates.
- *
- * @param[in,out] uc use context
- */
-static void
-create_using_templates_fixed (struct UseContext *uc)
-{
- uc->ihc.request_body
- = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("otp_id",
- uc->lookup_template.etp.otp_id)),
- GNUNET_JSON_pack_object_steal (
- "order",
- GNUNET_JSON_PACK (
- TALER_JSON_pack_amount (
- "amount",
- &uc->compute_price.totals[0]),
- GNUNET_JSON_pack_allow_null (
- TALER_JSON_pack_amount ("tip",
- uc->parse_request.no_tip
- ? NULL
- : &uc->parse_request.tip)),
- GNUNET_JSON_pack_string (
- "summary",
- NULL == uc->parse_request.summary
- ? uc->template_contract.summary
- : uc->parse_request.summary))));
-}
-
-
-/**
- * Create order request for paivana templates.
- *
- * @param[in,out] uc use context
- */
-static void
-create_using_templates_paivana (struct UseContext *uc)
-{
- uc->ihc.request_body
- = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string (
- "session_id",
- uc->parse_request.paivana.paivana_id),
- GNUNET_JSON_pack_object_steal (
- "order",
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_uint64 ("version",
- 1),
- GNUNET_JSON_pack_array_incref ("choices",
- uc->compute_price.choices),
- GNUNET_JSON_pack_string (
- "summary",
- NULL == uc->parse_request.summary
- ? uc->template_contract.summary
- : uc->parse_request.summary),
- GNUNET_JSON_pack_string ("fulfillment_url",
- uc->parse_request.paivana.website))));
-}
-
-
-static void
-handle_phase_create_order (struct UseContext *uc)
-{
- GNUNET_assert (NULL == uc->ihc.request_body);
- switch (uc->template_type)
- {
- case TALER_MERCHANT_TEMPLATE_TYPE_FIXED_ORDER:
- create_using_templates_fixed (uc);
- break;
- case TALER_MERCHANT_TEMPLATE_TYPE_PAIVANA:
- create_using_templates_paivana (uc);
- break;
- case TALER_MERCHANT_TEMPLATE_TYPE_INVENTORY_CART:
- create_using_templates_inventory (uc);
- break;
- case TALER_MERCHANT_TEMPLATE_TYPE_INVALID:
- GNUNET_assert (0);
- }
- uc->phase++;
-}
-
-
-/* ***************** Main handler **************** */
-
-MHD_RESULT
-TMH_post_using_templates_ID (
- const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
-{
- struct UseContext *uc = hc->ctx;
-
- (void) rh;
- if (NULL == uc)
- {
- uc = GNUNET_new (struct UseContext);
- uc->hc = hc;
- hc->ctx = uc;
- hc->cc = &cleanup_use_context;
- uc->ihc.instance = hc->instance;
- uc->phase = USE_PHASE_PARSE_REQUEST;
- uc->template_type = TALER_MERCHANT_TEMPLATE_TYPE_INVALID;
- }
-
- while (1)
- {
- switch (uc->phase)
- {
- case USE_PHASE_PARSE_REQUEST:
- handle_phase_parse_request (uc);
- break;
- case USE_PHASE_LOOKUP_TEMPLATE:
- handle_phase_lookup_template (uc);
- break;
- case USE_PHASE_PARSE_TEMPLATE:
- handle_phase_template_contract (uc);
- break;
- case USE_PHASE_DB_FETCH:
- handle_phase_db_fetch (uc);
- break;
- case USE_PHASE_VERIFY:
- handle_phase_verify (uc);
- break;
- case USE_PHASE_COMPUTE_PRICE:
- handle_phase_compute_price (uc);
- break;
- case USE_PHASE_CHECK_TIP:
- handle_phase_check_tip (uc);
- break;
- case USE_PHASE_CHECK_TOTAL:
- handle_phase_check_total (uc);
- break;
- case USE_PHASE_CREATE_ORDER:
- handle_phase_create_order (uc);
- 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;
- }
- }
-}
diff --git a/src/backend/taler-merchant-httpd_post-using-templates.h b/src/backend/taler-merchant-httpd_post-using-templates.h
@@ -1,40 +0,0 @@
-/*
- This file is part of TALER
- (C) 2022 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-merchant-httpd_post-using-templates.h
- * @brief headers for POST /using-templates handler
- * @author Priscilla Huang
- */
-#ifndef TALER_MERCHANT_HTTPD_POST_USING_TEMPLATES_H
-#define TALER_MERCHANT_HTTPD_POST_USING_TEMPLATES_H
-
-#include "taler-merchant-httpd.h"
-
-/**
- * Generate a template that customer can use it. Returns an MHD_RESULT.
- *
- * @param rh details about this request handler
- * @param connection the MHD connection to handle
- * @param[in,out] hc context with further information about the request
- * @return MHD result code
- */
-MHD_RESULT
-TMH_post_using_templates_ID (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc);
-
-
-#endif