summaryrefslogtreecommitdiff
path: root/src/backend/taler-merchant-httpd_private-post-orders.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/taler-merchant-httpd_private-post-orders.c')
-rw-r--r--src/backend/taler-merchant-httpd_private-post-orders.c3282
1 files changed, 2298 insertions, 984 deletions
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c
index bb64fb02..eedece55 100644
--- a/src/backend/taler-merchant-httpd_private-post-orders.c
+++ b/src/backend/taler-merchant-httpd_private-post-orders.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2014, 2015, 2016, 2018, 2020, 2021 Taler Systems SA
+ (C) 2014-2023 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
@@ -22,16 +22,25 @@
* @brief the POST /orders handler
* @author Christian Grothoff
* @author Marcello Stanisci
+ * @author Christian Blättler
*/
#include "platform.h"
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <gnunet/gnunet_time_lib.h>
#include <jansson.h>
+#include <microhttpd.h>
+#include <string.h>
+#include <taler/taler_error_codes.h>
#include <taler/taler_signatures.h>
#include <taler/taler_json_lib.h>
+#include "taler-merchant-httpd.h"
#include "taler-merchant-httpd_private-post-orders.h"
-#include "taler-merchant-httpd_auditors.h"
#include "taler-merchant-httpd_exchanges.h"
+#include "taler-merchant-httpd_contract.h"
#include "taler-merchant-httpd_helper.h"
#include "taler-merchant-httpd_private-get-orders.h"
+#include "taler_merchantdb_plugin.h"
/**
@@ -40,6 +49,11 @@
#define MAX_RETRIES 3
/**
+ * Maximum number of inventory products per order.
+ */
+#define MAX_PRODUCTS 1024
+
+/**
* What is the label under which we find/place the merchant's
* jurisdiction in the locations list by default?
*/
@@ -53,109 +67,6 @@
/**
- * Check that the given JSON array of products is well-formed.
- *
- * @param products JSON array to check
- * @return #GNUNET_OK if all is fine
- */
-static enum GNUNET_GenericReturnValue
-check_products (const json_t *products)
-{
- size_t index;
- json_t *value;
-
- if (! json_is_array (products))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- json_array_foreach (products, index, value) {
- const char *description;
- const char *product_id = NULL;
- uint64_t quantity;
- const char *unit = NULL;
- struct TALER_Amount price;
- const char *image = NULL;
- json_t *taxes = NULL;
- struct GNUNET_TIME_Timestamp delivery_date;
- const char *error_name;
- unsigned int error_line;
- enum GNUNET_GenericReturnValue res;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("product_id",
- &product_id),
- NULL),
- TALER_JSON_spec_i18n_str ("description",
- &description),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_uint64 ("quantity",
- &quantity),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("unit",
- &unit),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_amount ("price",
- TMH_currency,
- &price),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("image",
- &image),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("taxes",
- &taxes),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("delivery_date",
- &delivery_date),
- NULL),
- GNUNET_JSON_spec_end ()
- };
-
- /* extract fields we need to sign separately */
- res = GNUNET_JSON_parse (value,
- spec,
- &error_name,
- &error_line);
- if (GNUNET_OK != res)
- {
- GNUNET_break (0);
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Product parsing failed at #%u: %s:%u\n",
- (unsigned int) index,
- error_name,
- error_line);
- return GNUNET_SYSERR;
- }
- if ( (NULL != taxes) &&
- (! TMH_taxes_array_valid (taxes) ) )
- {
- GNUNET_break (0);
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Product parsing failed for taxes\n");
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- if ( (NULL != image) &&
- (! TMH_image_data_url_valid (image) ) )
- {
- GNUNET_break (0);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Product parsing failed for image\n");
- GNUNET_JSON_parse_free (spec);
- return GNUNET_SYSERR;
- }
- GNUNET_JSON_parse_free (spec);
- }
- return GNUNET_OK;
-}
-
-
-/**
* Generate the base URL for the given merchant instance.
*
* @param connection the MHD connection
@@ -166,46 +77,13 @@ static char *
make_merchant_base_url (struct MHD_Connection *connection,
const char *instance_id)
{
- const char *host;
- const char *forwarded_host;
- const char *uri_path;
- struct GNUNET_Buffer buf = { 0 };
+ struct GNUNET_Buffer buf;
- if (GNUNET_YES == TALER_mhd_is_https (connection))
- GNUNET_buffer_write_str (&buf, "https://");
- else
- GNUNET_buffer_write_str (&buf, "http://");
- host = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_HOST);
- forwarded_host = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- "X-Forwarded-Host");
- if (NULL != forwarded_host)
- {
- GNUNET_buffer_write_str (&buf,
- forwarded_host);
- }
- else
- {
- GNUNET_assert (NULL != host);
- GNUNET_buffer_write_str (&buf,
- host);
- }
- uri_path = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- "X-Forwarded-Prefix");
- if (NULL != uri_path)
- GNUNET_buffer_write_path (&buf, uri_path);
-
- if (0 != strcmp (instance_id,
- "default"))
- {
- GNUNET_buffer_write_path (&buf,
- "/instances/");
- GNUNET_buffer_write_str (&buf,
- instance_id);
- }
+ if (GNUNET_OK !=
+ TMH_base_url_by_connection (connection,
+ instance_id,
+ &buf))
+ return NULL;
GNUNET_buffer_write_path (&buf,
"");
return GNUNET_buffer_reap_str (&buf);
@@ -231,33 +109,607 @@ struct InventoryProduct
/**
+ * Handle for a rekey operation where we (re)request
+ * the /keys from the exchange.
+ */
+struct RekeyExchange
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct RekeyExchange *prev;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct RekeyExchange *next;
+
+ /**
+ * order this is for.
+ */
+ struct OrderContext *oc;
+
+ /**
+ * Base URL of the exchange.
+ */
+ char *url;
+
+ /**
+ * Request for keys.
+ */
+ struct TMH_EXCHANGES_KeysOperation *fo;
+
+};
+
+
+/**
+ * Information we keep per order we are processing.
+ */
+struct OrderContext
+{
+ /**
+ * Information set in the ORDER_PHASE_PARSE_REQUEST phase.
+ */
+ struct
+ {
+ /**
+ * Order field of the request
+ */
+ json_t *order;
+
+ /**
+ * Set to how long refunds will be allowed.
+ */
+ struct GNUNET_TIME_Relative refund_delay;
+
+ /**
+ * RFC8905 payment target type to find a matching merchant account
+ */
+ const char *payment_target;
+
+ /**
+ * Shared key to use with @e pos_algorithm.
+ */
+ const char *pos_key;
+
+ /**
+ * Selected algorithm (by template) when we are to
+ * generate an OTP code for payment confirmation.
+ */
+ enum TALER_MerchantConfirmationAlgorithm pos_algorithm;
+
+ /**
+ * Hash of the POST request data, used to detect
+ * idempotent requests.
+ */
+ struct TALER_MerchantPostDataHashP h_post_data;
+
+ /**
+ * Length of the @e inventory_products array.
+ */
+ unsigned int inventory_products_length;
+
+ /**
+ * Specifies that some products are to be included in the
+ * order from the inventory. For these inventory management
+ * is performed (so the products must be in stock).
+ */
+ struct InventoryProduct *inventory_products;
+
+ /**
+ * Length of the @e uuids array.
+ */
+ unsigned int uuids_length;
+
+ /**
+ * array of UUIDs used to reserve products from @a inventory_products.
+ */
+ struct GNUNET_Uuid *uuids;
+
+ /**
+ * Claim token for the request.
+ */
+ struct TALER_ClaimTokenP claim_token;
+
+ /**
+ * Session ID (optional) to use for the order.
+ */
+ const char *session_id;
+
+ } parse_request;
+
+
+ /**
+ * Information set in the ORDER_PHASE_ADD_PAYMENT_DETAILS phase.
+ */
+ struct
+ {
+ /**
+ * Wire method (and our bank account) we have selected
+ * to be included for this order.
+ */
+ const struct TMH_WireMethod *wm;
+ } add_payment_details;
+
+ /**
+ * Information set in the ORDER_PHASE_PARSE_ORDER phase.
+ */
+ struct
+ {
+ /**
+ * Version of the contract terms.
+ */
+ enum TALER_MerchantContractVersion version;
+
+ /**
+ * Our order ID.
+ */
+ const char *order_id;
+
+ /**
+ * Summary of the contract.
+ */
+ const char *summary;
+
+ /**
+ * Internationalized summary.
+ */
+ json_t *summary_i18n;
+
+ /**
+ * URL that will show that the contract was successful
+ * after it has been paid for.
+ */
+ const char *fulfillment_url;
+
+ /**
+ * Message shown to the customer after paying for the contract.
+ * Either fulfillment_url or fulfillment_message must be specified.
+ */
+ const char *fulfillment_message;
+
+ /**
+ * Map from IETF BCP 47 language tags to localized fulfillment messages.
+ */
+ json_t *fulfillment_message_i18n;
+
+ /**
+ * Array of products that are part of the purchase.
+ */
+ const json_t *products;
+
+ /**
+ * URL where the same contract could be ordered again (if available).
+ */
+ const char *public_reorder_url;
+
+ /**
+ * Array of contract choices. Is null for v0 contracts.
+ */
+ const json_t *choices;
+
+ /**
+ * Merchant base URL.
+ */
+ char *merchant_base_url;
+
+ /**
+ * Timestamp of the order.
+ */
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ /**
+ * Deadline for refunds.
+ */
+ struct GNUNET_TIME_Timestamp refund_deadline;
+
+ /**
+ * Payment deadline.
+ */
+ struct GNUNET_TIME_Timestamp pay_deadline;
+
+ /**
+ * Wire transfer deadline.
+ */
+ struct GNUNET_TIME_Timestamp wire_deadline;
+
+ /**
+ * Delivery date.
+ */
+ struct GNUNET_TIME_Timestamp delivery_date;
+
+ /**
+ * Delivery location.
+ */
+ json_t *delivery_location;
+
+ /**
+ * Gross amount value of the contract. Used to
+ * compute @e max_stefan_fee.
+ */
+ struct TALER_Amount brutto;
+
+ /**
+ * Maximum fee as given by the client request.
+ */
+ struct TALER_Amount max_fee;
+
+ /**
+ * Specifies for how long the wallet should try to get an
+ * automatic refund for the purchase.
+ */
+ struct GNUNET_TIME_Relative auto_refund;
+
+ /**
+ * Nonce generated by the wallet and echoed by the merchant
+ * in this field when the proposal is generated.
+ */
+ const char *nonce;
+
+ /**
+ * Extra data that is only interpreted by the merchant frontend.
+ */
+ const json_t *extra;
+
+ /**
+ * Minimum age required by the order.
+ */
+ uint32_t minimum_age;
+
+ } parse_order;
+
+ /**
+ * Information set in the ORDER_PHASE_PARSE_CHOICES phase.
+ */
+ struct
+ {
+ /**
+ * Array of possible specific contracts the wallet/customer may choose
+ * from by selecting the respective index when signing the deposit
+ * confirmation.
+ */
+ struct TALER_MerchantContractChoice *choices;
+
+ /**
+ * Length of the @e choices array.
+ */
+ unsigned int choices_len;
+
+ /**
+ * Array of token types referenced in the contract.
+ */
+ struct TALER_MerchantContractTokenAuthority *authorities;
+
+ /**
+ * Length of the @e authorities array.
+ */
+ unsigned int authorities_len;
+ } parse_choices;
+
+ /**
+ * Information set in the ORDER_PHASE_MERGE_INVENTORY phase.
+ */
+ struct
+ {
+ /**
+ * Merged array of products in the @e order.
+ */
+ json_t *products;
+ } merge_inventory;
+
+ /**
+ * Information set in the ORDER_PHASE_SET_EXCHANGES phase.
+ */
+ struct
+ {
+ /**
+ * Array of exchanges we find acceptable for this
+ * order.
+ */
+ json_t *exchanges;
+
+ /**
+ * Forced requests to /keys to update our exchange
+ * information.
+ */
+ struct RekeyExchange *pending_reload_head;
+
+ /**
+ * Forced requests to /keys to update our exchange
+ * information.
+ */
+ struct RekeyExchange *pending_reload_tail;
+
+ /**
+ * Did we previously force reloading of /keys from
+ * all exchanges? Set to 'true' to prevent us from
+ * doing it again (and again...).
+ */
+ bool forced_reload;
+
+ /**
+ * Set to true once we are sure that we have at
+ * least one good exchange.
+ */
+ bool exchange_good;
+
+ /**
+ * Maximum fee for @e order based on STEFAN curves.
+ * Used to set @e max_fee if not provided as part of
+ * @e order.
+ */
+ struct TALER_Amount max_stefan_fee;
+ } set_exchanges;
+
+ /**
+ * Information set in the ORDER_PHASE_SET_MAX_FEE phase.
+ */
+ struct
+ {
+ /**
+ * Maximum fee
+ */
+ struct TALER_Amount max_fee;
+ } set_max_fee;
+
+ /**
+ * Information set in the ORDER_PHASE_EXECUTE_ORDER phase.
+ */
+ struct
+ {
+ /**
+ * Which product (by offset) is out of stock, UINT_MAX if all were in-stock.
+ */
+ unsigned int out_of_stock_index;
+
+ /**
+ * Set to a previous claim token *if* @e idempotent
+ * is also true.
+ */
+ struct TALER_ClaimTokenP token;
+
+ /**
+ * Set to true if the order was idempotent and there
+ * was an equivalent one before.
+ */
+ bool idempotent;
+
+ /**
+ * Set to true if the order is in conflict with a
+ * previous order with the same order ID.
+ */
+ bool conflict;
+ } execute_order;
+
+ struct
+ {
+ /**
+ * Contract terms to store in the database.
+ */
+ json_t *contract;
+ } serialize_order;
+
+ /**
+ * Connection of the request.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Kept in a DLL while suspended.
+ */
+ struct OrderContext *next;
+
+ /**
+ * Kept in a DLL while suspended.
+ */
+ struct OrderContext *prev;
+
+ /**
+ * Handler context for the request.
+ */
+ struct TMH_HandlerContext *hc;
+
+ /**
+ * #GNUNET_YES if suspended.
+ */
+ enum GNUNET_GenericReturnValue suspended;
+
+ /**
+ * Current phase of setting up the order.
+ */
+ enum
+ {
+ ORDER_PHASE_PARSE_REQUEST,
+ ORDER_PHASE_PARSE_ORDER,
+ ORDER_PHASE_PARSE_CHOICES,
+ ORDER_PHASE_MERGE_INVENTORY,
+ ORDER_PHASE_ADD_PAYMENT_DETAILS,
+ ORDER_PHASE_SET_EXCHANGES,
+ ORDER_PHASE_SET_MAX_FEE,
+ ORDER_PHASE_SERIALIZE_ORDER,
+ ORDER_PHASE_SALT_FORGETTABLE,
+ ORDER_PHASE_CHECK_CONTRACT,
+ ORDER_PHASE_EXECUTE_ORDER,
+
+ /**
+ * Processing is done, we should return #MHD_YES.
+ */
+ ORDER_PHASE_FINISHED_MHD_YES,
+
+ /**
+ * Processing is done, we should return #MHD_NO.
+ */
+ ORDER_PHASE_FINISHED_MHD_NO
+ } phase;
+
+
+};
+
+
+/**
+ * Kept in a DLL while suspended.
+ */
+static struct OrderContext *oc_head;
+
+/**
+ * Kept in a DLL while suspended.
+ */
+static struct OrderContext *oc_tail;
+
+
+void
+TMH_force_orders_resume ()
+{
+ struct OrderContext *oc;
+
+ while (NULL != (oc = oc_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (oc_head,
+ oc_tail,
+ oc);
+ oc->suspended = GNUNET_SYSERR;
+ MHD_resume_connection (oc->connection);
+ }
+}
+
+
+/**
+ * Update the phase of @a oc based on @a mret.
+ *
+ * @param[in,out] oc order to update phase for
+ * @param mret #MHD_NO to close with #MHD_NO
+ * #MHD_YES to close with #MHD_YES
+ */
+static void
+finalize_order (struct OrderContext *oc,
+ MHD_RESULT mret)
+{
+ oc->phase = (MHD_YES == mret)
+ ? ORDER_PHASE_FINISHED_MHD_YES
+ : ORDER_PHASE_FINISHED_MHD_NO;
+}
+
+
+/**
+ * Update the phase of @a oc based on @a ret.
+ *
+ * @param[in,out] oc order to update phase for
+ * @param ret #GNUNET_SYSERR to close with #MHD_NO
+ * #GNUNET_NO to close with #MHD_YES
+ * #GNUNET_OK is not allowed!
+ */
+static void
+finalize_order2 (struct OrderContext *oc,
+ enum GNUNET_GenericReturnValue ret)
+{
+ GNUNET_assert (GNUNET_OK != ret);
+ oc->phase = (GNUNET_NO == ret)
+ ? ORDER_PHASE_FINISHED_MHD_YES
+ : ORDER_PHASE_FINISHED_MHD_NO;
+}
+
+
+/**
+ * Generate an error response for @a oc.
+ *
+ * @param[in,out] oc order context to respond to
+ * @param http_status HTTP status code to set
+ * @param ec error code to set
+ * @param detail error message detail to set
+ */
+static void
+reply_with_error (struct OrderContext *oc,
+ unsigned int http_status,
+ enum TALER_ErrorCode ec,
+ const char *detail)
+{
+ MHD_RESULT mret;
+
+ mret = TALER_MHD_reply_with_error (oc->connection,
+ http_status,
+ ec,
+ detail);
+ finalize_order (oc,
+ mret);
+}
+
+
+/**
+ * Clean up memory used by @a cls.
+ *
+ * @param[in] cls the `struct OrderContext` to clean up
+ */
+static void
+clean_order (void *cls)
+{
+ struct OrderContext *oc = cls;
+ struct RekeyExchange *rx;
+
+ while (NULL != (rx = oc->set_exchanges.pending_reload_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (oc->set_exchanges.pending_reload_head,
+ oc->set_exchanges.pending_reload_tail,
+ rx);
+ TMH_EXCHANGES_keys4exchange_cancel (rx->fo);
+ GNUNET_free (rx->url);
+ GNUNET_free (rx);
+ }
+ if (NULL != oc->set_exchanges.exchanges)
+ {
+ json_decref (oc->set_exchanges.exchanges);
+ oc->set_exchanges.exchanges = NULL;
+ }
+ if (NULL != oc->parse_order.fulfillment_message_i18n)
+ {
+ json_decref (oc->parse_order.fulfillment_message_i18n);
+ oc->parse_order.fulfillment_message_i18n = NULL;
+ }
+ if (NULL != oc->parse_order.summary_i18n)
+ {
+ json_decref (oc->parse_order.summary_i18n);
+ oc->parse_order.summary_i18n = NULL;
+ }
+ if (NULL != oc->parse_order.delivery_location)
+ {
+ json_decref (oc->parse_order.delivery_location);
+ oc->parse_order.delivery_location = NULL;
+ }
+ if (NULL != oc->merge_inventory.products)
+ {
+ json_decref (oc->merge_inventory.products);
+ oc->merge_inventory.products = NULL;
+ }
+ // TODO: Check if this is even correct
+ for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
+ {
+ GNUNET_array_grow (oc->parse_choices.choices[i].inputs,
+ oc->parse_choices.choices[i].inputs_len,
+ 0);
+ GNUNET_array_grow (oc->parse_choices.choices[i].outputs,
+ oc->parse_choices.choices[i].outputs_len,
+ 0);
+ }
+ GNUNET_array_grow (oc->parse_request.inventory_products,
+ oc->parse_request.inventory_products_length,
+ 0);
+ GNUNET_array_grow (oc->parse_request.uuids,
+ oc->parse_request.uuids_length,
+ 0);
+ json_decref (oc->parse_request.order);
+ json_decref (oc->serialize_order.contract);
+ GNUNET_free (oc->parse_order.merchant_base_url);
+ GNUNET_free (oc);
+}
+
+
+/**
* Execute the database transaction to setup the order.
*
- * @param hc handler context for the request
- * @param order_id unique ID for the order
- * @param h_post_data hash of the client's POST request, for idempotency checks
- * @param pay_deadline until when does the order have to be paid
- * @param[in] order order to process (not modified)
- * @param claim_token token to use for access control
- * @param inventory_products_length length of the @a inventory_products array
- * @param inventory_products array of products to add to @a order from our inventory
- * @param uuids_length length of the @a uuids array
- * @param uuids array of UUIDs used to reserve products from @a inventory_products
- * @param[out] out_of_stock_index which product (by offset) is out of stock, UINT_MAX if all were in-stock
- * @return transaction status, 0 if @a uuids were insufficient to reserve required inventory
+ * @param[in,out] oc order context
+ * @return transaction status, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if @a uuids were insufficient to reserve required inventory
*/
static enum GNUNET_DB_QueryStatus
-execute_transaction (struct TMH_HandlerContext *hc,
- const char *order_id,
- const struct TALER_MerchantPostDataHashP *h_post_data,
- struct GNUNET_TIME_Timestamp pay_deadline,
- const json_t *order,
- const struct TALER_ClaimTokenP *claim_token,
- unsigned int inventory_products_length,
- const struct InventoryProduct inventory_products[],
- unsigned int uuids_length,
- const struct GNUNET_Uuid uuids[],
- unsigned int *out_of_stock_index)
+execute_transaction (struct OrderContext *oc)
{
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_TIME_Timestamp timestamp;
@@ -270,14 +722,56 @@ execute_transaction (struct TMH_HandlerContext *hc,
GNUNET_break (0);
return GNUNET_DB_STATUS_HARD_ERROR;
}
+
+ /* Test if we already have an order with this id */
+ {
+ json_t *contract_terms;
+ struct TALER_MerchantPostDataHashP orig_post;
+
+ qs = TMH_db->lookup_order (TMH_db->cls,
+ oc->hc->instance->settings.id,
+ oc->parse_order.order_id,
+ &oc->execute_order.token,
+ &orig_post,
+ &contract_terms);
+ /* If yes, check for idempotency */
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ TMH_db->rollback (TMH_db->cls);
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ json_decref (contract_terms);
+ /* Comparing the contract terms is sufficient because all the other
+ params get added to it at some point. */
+ if (0 == GNUNET_memcmp (&orig_post,
+ &oc->parse_request.h_post_data))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order creation idempotent\n");
+ oc->execute_order.idempotent = true;
+ return qs;
+ }
+ GNUNET_break_op (0);
+ oc->execute_order.conflict = true;
+ return qs;
+ }
+ }
+
/* Setup order */
qs = TMH_db->insert_order (TMH_db->cls,
- hc->instance->settings.id,
- order_id,
- h_post_data,
- pay_deadline,
- claim_token,
- order); // called 'contract terms' at database.
+ oc->hc->instance->settings.id,
+ oc->parse_order.order_id,
+ oc->parse_request.session_id,
+ &oc->parse_request.h_post_data,
+ oc->parse_order.pay_deadline,
+ &oc->parse_request.claim_token,
+ oc->serialize_order.contract, /* called 'contract terms' at database. */
+ oc->parse_request.pos_key,
+ oc->parse_request.pos_algorithm);
if (qs <= 0)
{
/* qs == 0: probably instance does not exist (anymore) */
@@ -285,10 +779,10 @@ execute_transaction (struct TMH_HandlerContext *hc,
return qs;
}
/* Migrate locks from UUIDs to new order: first release old locks */
- for (unsigned int i = 0; i<uuids_length; i++)
+ for (unsigned int i = 0; i<oc->parse_request.uuids_length; i++)
{
qs = TMH_db->unlock_inventory (TMH_db->cls,
- &uuids[i]);
+ &oc->parse_request.uuids[i]);
if (qs < 0)
{
TMH_db->rollback (TMH_db->cls);
@@ -301,13 +795,14 @@ execute_transaction (struct TMH_HandlerContext *hc,
(note: this can basically ONLY fail on serializability OR
because the UUID locks were insufficient for the desired
quantities). */
- for (unsigned int i = 0; i<inventory_products_length; i++)
+ for (unsigned int i = 0; i<oc->parse_request.inventory_products_length; i++)
{
- qs = TMH_db->insert_order_lock (TMH_db->cls,
- hc->instance->settings.id,
- order_id,
- inventory_products[i].product_id,
- inventory_products[i].quantity);
+ qs = TMH_db->insert_order_lock (
+ TMH_db->cls,
+ oc->hc->instance->settings.id,
+ oc->parse_order.order_id,
+ oc->parse_request.inventory_products[i].product_id,
+ oc->parse_request.inventory_products[i].quantity);
if (qs < 0)
{
TMH_db->rollback (TMH_db->cls);
@@ -317,17 +812,17 @@ execute_transaction (struct TMH_HandlerContext *hc,
{
/* qs == 0: lock acquisition failed due to insufficient stocks */
TMH_db->rollback (TMH_db->cls);
- *out_of_stock_index = i; /* indicate which product is causing the issue */
+ oc->execute_order.out_of_stock_index = i; /* indicate which product is causing the issue */
return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
}
}
- *out_of_stock_index = UINT_MAX;
+ oc->execute_order.out_of_stock_index = UINT_MAX;
/* Get the order serial and timestamp for the order we just created to
update long-poll clients. */
qs = TMH_db->lookup_order_summary (TMH_db->cls,
- hc->instance->settings.id,
- order_id,
+ oc->hc->instance->settings.id,
+ oc->parse_order.order_id,
&timestamp,
&order_serial);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
@@ -335,7 +830,7 @@ execute_transaction (struct TMH_HandlerContext *hc,
TMH_db->rollback (TMH_db->cls);
return qs;
}
- TMH_notify_order_change (hc->instance,
+ TMH_notify_order_change (oc->hc->instance,
TMH_OSF_NONE,
timestamp,
order_serial);
@@ -353,268 +848,123 @@ execute_transaction (struct TMH_HandlerContext *hc,
* database. Write the resulting proposal or an error message
* of a MHD connection.
*
- * @param connection connection to write the result or error to
- * @param hc handler context for the request
- * @param h_post_data hash of the client's POST request, for idempotency checks
- * @param[in,out] order order to process (can be modified)
- * @param claim_token token to use for access control
- * @param inventory_products_length length of the @a inventory_products array
- * @param inventory_products array of products to add to @a order from our inventory
- * @param uuids_length length of the @a uuids array
- * @param uuids array of UUIDs used to reserve products from @a inventory_products
- * @return MHD result code
+ * @param[in,out] oc order context
*/
-static MHD_RESULT
-execute_order (struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc,
- const struct TALER_MerchantPostDataHashP *h_post_data,
- json_t *order,
- const struct TALER_ClaimTokenP *claim_token,
- unsigned int inventory_products_length,
- const struct InventoryProduct inventory_products[],
- unsigned int uuids_length,
- const struct GNUNET_Uuid uuids[])
+static void
+execute_order (struct OrderContext *oc)
{
const struct TALER_MERCHANTDB_InstanceSettings *settings =
- &hc->instance->settings;
- struct TALER_Amount total;
- const char *order_id;
- const char *summary;
- const char *fulfillment_msg = NULL;
- json_t *products;
- json_t *merchant;
- json_t *summary_i18n = NULL;
- json_t *fulfillment_i18n = NULL;
- struct GNUNET_TIME_Timestamp timestamp;
- struct GNUNET_TIME_Timestamp refund_deadline = { 0 };
- struct GNUNET_TIME_Timestamp wire_transfer_deadline;
- struct GNUNET_TIME_Timestamp pay_deadline;
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount ("amount",
- TMH_currency,
- &total),
- GNUNET_JSON_spec_string ("order_id",
- &order_id),
- GNUNET_JSON_spec_string ("summary",
- &summary),
- /**
- * The following entries we don't actually need,
- * except to check that the order is well-formed */
- GNUNET_JSON_spec_json ("products",
- &products),
- GNUNET_JSON_spec_json ("merchant",
- &merchant),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("summary_i18n",
- &summary_i18n),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("fulfillment_message",
- &fulfillment_msg),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("fulfillment_message_i18n",
- &fulfillment_i18n),
- NULL),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &timestamp),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("refund_deadline",
- &refund_deadline),
- NULL),
- GNUNET_JSON_spec_timestamp ("pay_deadline",
- &pay_deadline),
- GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
- &wire_transfer_deadline),
- GNUNET_JSON_spec_end ()
- };
+ &oc->hc->instance->settings;
enum GNUNET_DB_QueryStatus qs;
- unsigned int out_of_stock_index;
-
- /* extract fields we need to sign separately */
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- order,
- spec);
- if (GNUNET_OK != res)
- {
- GNUNET_break_op (0);
- return (GNUNET_NO == res)
- ? MHD_YES
- : MHD_NO;
- }
- }
-
- /* check product list in contract is well-formed */
- if (GNUNET_OK != check_products (products))
- {
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "order:products");
- }
-
- if ( (NULL != fulfillment_i18n) &&
- (! TALER_JSON_check_i18n (fulfillment_i18n)) )
- {
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "order:fulfillment_message_i18n");
- }
- if ( (NULL != summary_i18n) &&
- (! TALER_JSON_check_i18n (summary_i18n)) )
- {
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "order:summary_i18n");
- }
-
- /* Test if we already have an order with this id */
- {
- struct TALER_ClaimTokenP token;
- json_t *contract_terms;
- struct TALER_MerchantPostDataHashP orig_post;
-
- TMH_db->preflight (TMH_db->cls);
- qs = TMH_db->lookup_order (TMH_db->cls,
- hc->instance->settings.id,
- order_id,
- &token,
- &orig_post,
- &contract_terms);
- /* If yes, check for idempotency */
- if (0 > qs)
- {
- GNUNET_break (0);
- TMH_db->rollback (TMH_db->cls);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_order");
- }
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- MHD_RESULT ret;
- json_decref (contract_terms);
- /* Comparing the contract terms is sufficient because all the other
- params get added to it at some point. */
- if (0 == GNUNET_memcmp (&orig_post,
- h_post_data))
- {
- ret = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_string ("order_id",
- order_id),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_data_varsize (
- "token",
- GNUNET_is_zero (&token)
- ? NULL
- : &token,
- sizeof (token))));
- }
- else
- {
- /* This request is not idempotent */
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS,
- order_id);
- }
- GNUNET_JSON_parse_free (spec);
- return ret;
- }
- }
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Executing database transaction to create order '%s' for instance '%s'\n",
- order_id,
+ oc->parse_order.order_id,
settings->id);
for (unsigned int i = 0; i<MAX_RETRIES; i++)
{
TMH_db->preflight (TMH_db->cls);
- qs = execute_transaction (hc,
- order_id,
- h_post_data,
- pay_deadline,
- order,
- claim_token,
- inventory_products_length,
- inventory_products,
- uuids_length,
- uuids,
- &out_of_stock_index);
+ qs = execute_transaction (oc);
if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
break;
}
if (0 >= qs)
{
- GNUNET_JSON_parse_free (spec);
/* Special report if retries insufficient */
if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
{
GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_SOFT_FAILURE,
- NULL);
+ reply_with_error (oc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ NULL);
+ return;
}
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
/* should be: contract (!) with same order ID
already exists */
- return TALER_MHD_reply_with_error (
- connection,
+ reply_with_error (
+ oc,
MHD_HTTP_CONFLICT,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS,
- order_id);
+ oc->parse_order.order_id);
+ return;
}
/* Other hard transaction error (disk full, etc.) */
GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- connection,
+ reply_with_error (
+ oc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_COMMIT_FAILED,
NULL);
+ return;
+ }
+
+ /* DB transaction succeeded, check for idempotent */
+ if (oc->execute_order.idempotent)
+ {
+ MHD_RESULT ret;
+
+ ret = TALER_MHD_REPLY_JSON_PACK (
+ oc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("order_id",
+ oc->parse_order.order_id),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_varsize (
+ "token",
+ GNUNET_is_zero (&oc->execute_order.token)
+ ? NULL
+ : &oc->execute_order.token,
+ sizeof (oc->execute_order.token))));
+ finalize_order (oc,
+ ret);
+ return;
+ }
+ if (oc->execute_order.conflict)
+ {
+ reply_with_error (
+ oc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS,
+ oc->parse_order.order_id);
+ return;
}
/* DB transaction succeeded, check for out-of-stock */
- if (out_of_stock_index < UINT_MAX)
+ if (oc->execute_order.out_of_stock_index < UINT_MAX)
{
/* We had a product that has insufficient quantities,
generate the details for the response. */
struct TALER_MERCHANTDB_ProductDetails pd;
MHD_RESULT ret;
+ const struct InventoryProduct *ip;
- memset (&pd, 0, sizeof (pd));
+ ip = &oc->parse_request.inventory_products[
+ oc->execute_order.out_of_stock_index];
+ memset (&pd,
+ 0,
+ sizeof (pd));
qs = TMH_db->lookup_product (
TMH_db->cls,
- hc->instance->settings.id,
- inventory_products[out_of_stock_index].product_id,
+ oc->hc->instance->settings.id,
+ ip->product_id,
&pd);
- GNUNET_JSON_parse_free (spec);
switch (qs)
{
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order creation failed: product out of stock\n");
ret = TALER_MHD_REPLY_JSON_PACK (
- connection,
+ oc->connection,
MHD_HTTP_GONE,
GNUNET_JSON_pack_string (
"product_id",
- inventory_products[out_of_stock_index].product_id),
+ ip->product_id),
GNUNET_JSON_pack_uint64 (
"requested_quantity",
- inventory_products[out_of_stock_index].quantity),
+ ip->quantity),
GNUNET_JSON_pack_uint64 (
"available_quantity",
pd.total_stock - pd.total_sold - pd.total_lost),
@@ -623,188 +973,1037 @@ execute_order (struct MHD_Connection *connection,
"restock_expected",
pd.next_restock)));
TALER_MERCHANTDB_product_details_free (&pd);
- return ret;
+ finalize_order (oc,
+ ret);
+ return;
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_GONE,
- GNUNET_JSON_pack_string (
- "product_id",
- inventory_products[out_of_stock_index].product_id),
- GNUNET_JSON_pack_uint64 (
- "requested_quantity",
- inventory_products[out_of_stock_index].quantity),
- GNUNET_JSON_pack_uint64 (
- "available_quantity",
- 0));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order creation failed: unknown product out of stock\n");
+ finalize_order (oc,
+ TALER_MHD_REPLY_JSON_PACK (
+ oc->connection,
+ MHD_HTTP_GONE,
+ GNUNET_JSON_pack_string (
+ "product_id",
+ ip->product_id),
+ GNUNET_JSON_pack_uint64 (
+ "requested_quantity",
+ ip->quantity),
+ GNUNET_JSON_pack_uint64 (
+ "available_quantity",
+ 0)));
+ return;
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- connection,
+ reply_with_error (
+ oc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_SOFT_FAILURE,
NULL);
+ return;
case GNUNET_DB_STATUS_HARD_ERROR:
- return TALER_MHD_reply_with_error (
- connection,
+ GNUNET_break (0);
+ reply_with_error (
+ oc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_GENERIC_DB_FETCH_FAILED,
NULL);
+ return;
}
GNUNET_break (0);
- return MHD_NO;
- }
+ oc->phase = ORDER_PHASE_FINISHED_MHD_NO;
+ return;
+ } /* end 'out of stock' case */
/* Everything in-stock, generate positive response */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order creation succeeded\n");
{
MHD_RESULT ret;
ret = TALER_MHD_REPLY_JSON_PACK (
- connection,
+ oc->connection,
MHD_HTTP_OK,
GNUNET_JSON_pack_string ("order_id",
- order_id),
+ oc->parse_order.order_id),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_data_varsize (
"token",
- GNUNET_is_zero (claim_token)
+ GNUNET_is_zero (&oc->parse_request.claim_token)
? NULL
- : claim_token,
- sizeof (*claim_token))));
- GNUNET_JSON_parse_free (spec);
- return ret;
+ : &oc->parse_request.claim_token,
+ sizeof (oc->parse_request.claim_token))));
+ finalize_order (oc,
+ ret);
}
}
/**
- * Add missing fields to the order. Upon success, continue
+ * Check that the contract is now well-formed. Upon success, continue
* processing with execute_order().
*
- * @param connection connection to write the result or error to
- * @param hc handler context for the request
- * @param h_post_data hash of the client's POST request, for idempotency checks
- * @param[in,out] order order to process (can be modified)
- * @param claim_token token to use for access control
- * @param refund_delay refund delay
- * @param inventory_products_length length of the @a inventory_products array
- * @param inventory_products array of products to add to @a order from our inventory
- * @param uuids_length length of the @a uuids array
- * @param uuids array of UUIDs used to reserve products from @a inventory_products
- * @return MHD result code
+ * @param[in,out] oc order context
+ */
+static void
+check_contract (struct OrderContext *oc)
+{
+ struct TALER_PrivateContractHashP h_control;
+
+ json_dumpf (oc->serialize_order.contract,
+ stderr,
+ JSON_INDENT (2));
+ switch (TALER_JSON_contract_hash (oc->serialize_order.contract,
+ &h_control))
+ {
+ case GNUNET_SYSERR:
+ GNUNET_break (0);
+ reply_with_error (
+ oc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
+ "could not compute hash of serialized order");
+ return;
+ case GNUNET_NO:
+ GNUNET_break_op (0);
+ reply_with_error (
+ oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
+ "order contained unallowed values");
+ return;
+ case GNUNET_OK:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Contract hash is %s\n",
+ GNUNET_h2s (&h_control.hash));
+ oc->phase++;
+ return;
+ }
+ GNUNET_assert (0);
+}
+
+
+/**
+ * Modify the final contract terms adding salts for
+ * items that are forgettable.
+ *
+ * @param[in,out] oc order context
+ */
+static void
+salt_forgettable (struct OrderContext *oc)
+{
+ if (GNUNET_OK !=
+ TALER_JSON_contract_seed_forgettable (oc->parse_request.order,
+ oc->serialize_order.contract))
+ {
+ GNUNET_break_op (0);
+ reply_with_error (
+ oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_JSON_INVALID,
+ "could not compute hash of order due to bogus forgettable fields");
+ return;
+ }
+ oc->phase++;
+}
+
+
+/**
+ * Update MAX STEFAN fees based on @a keys.
+ *
+ * @param[in,out] oc order context to update
+ * @param keys keys to derive STEFAN fees from
+ */
+static void
+update_stefan (struct OrderContext *oc,
+ const struct TALER_EXCHANGE_Keys *keys)
+{
+ struct TALER_Amount net;
+
+ if (GNUNET_SYSERR !=
+ TALER_EXCHANGE_keys_stefan_b2n (keys,
+ &oc->parse_order.brutto,
+ &net))
+ {
+ struct TALER_Amount fee;
+
+ TALER_EXCHANGE_keys_stefan_round (keys,
+ &net);
+ if (-1 == TALER_amount_cmp (&oc->parse_order.brutto,
+ &net))
+ {
+ /* brutto < netto! */
+ /* => after rounding, there is no real difference */
+ net = oc->parse_order.brutto;
+ }
+ GNUNET_assert (0 <=
+ TALER_amount_subtract (&fee,
+ &oc->parse_order.brutto,
+ &net));
+ if ( (GNUNET_OK !=
+ TALER_amount_is_valid (&oc->set_exchanges.max_stefan_fee)) ||
+ (-1 == TALER_amount_cmp (&oc->set_exchanges.max_stefan_fee,
+ &fee)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Updated STEFAN-based fee to %s\n",
+ TALER_amount2s (&fee));
+ oc->set_exchanges.max_stefan_fee = fee;
+ }
+ }
+}
+
+
+/**
+ * Compute the set of exchanges that would be acceptable
+ * for this order.
+ *
+ * @param cls our `struct OrderContext`
+ * @param url base URL of an exchange (not used)
+ * @param exchange internal handle for the exchange
+ */
+static void
+get_acceptable (void *cls,
+ const char *url,
+ const struct TMH_Exchange *exchange)
+{
+ struct OrderContext *oc = cls;
+ unsigned int priority = 42; /* make compiler happy */
+ json_t *j_exchange;
+ enum GNUNET_GenericReturnValue res;
+
+ res = TMH_exchange_check_debit (exchange,
+ oc->add_payment_details.wm);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Exchange %s evaluated at %d\n",
+ url,
+ res);
+ switch (res)
+ {
+ case GNUNET_OK:
+ priority = 1024; /* high */
+ oc->set_exchanges.exchange_good = true;
+ break;
+ case GNUNET_NO:
+ if (oc->set_exchanges.forced_reload)
+ priority = 0; /* fresh negative response */
+ else
+ priority = 512; /* stale negative response */
+ break;
+ case GNUNET_SYSERR:
+ if (oc->set_exchanges.forced_reload)
+ priority = 256; /* fresh, no accounts yet */
+ else
+ priority = 768; /* stale, no accounts yet */
+ break;
+ }
+ j_exchange = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("url",
+ url),
+ GNUNET_JSON_pack_uint64 ("priority",
+ priority),
+ GNUNET_JSON_pack_data_auto ("master_pub",
+ TMH_EXCHANGES_get_master_pub (exchange)));
+ GNUNET_assert (NULL != j_exchange);
+ GNUNET_assert (0 ==
+ json_array_append_new (oc->set_exchanges.exchanges,
+ j_exchange));
+}
+
+
+/**
+ * Exchange `/keys` processing is done, resume handling
+ * the order.
+ *
+ * @param[in,out] oc context to resume
+ */
+static void
+resume_with_keys (struct OrderContext *oc)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resuming order processing after /keys downloads (now have %u accounts)\n",
+ (unsigned int) json_array_size (oc->set_exchanges.exchanges));
+ GNUNET_assert (GNUNET_YES == oc->suspended);
+ GNUNET_CONTAINER_DLL_remove (oc_head,
+ oc_tail,
+ oc);
+ oc->suspended = GNUNET_NO;
+ MHD_resume_connection (oc->connection);
+ TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
+}
+
+
+/**
+ * Function called with the result of a #TMH_EXCHANGES_keys4exchange()
+ * operation.
+ *
+ * @param cls closure with our `struct RekeyExchange *`
+ * @param keys the keys of the exchange
+ * @param exchange representation of the exchange
+ */
+static void
+keys_cb (
+ void *cls,
+ struct TALER_EXCHANGE_Keys *keys,
+ struct TMH_Exchange *exchange)
+{
+ struct RekeyExchange *rx = cls;
+ struct OrderContext *oc = rx->oc;
+ const struct TALER_MERCHANTDB_InstanceSettings *settings =
+ &oc->hc->instance->settings;
+
+ rx->fo = NULL;
+ GNUNET_CONTAINER_DLL_remove (oc->set_exchanges.pending_reload_head,
+ oc->set_exchanges.pending_reload_tail,
+ rx);
+ if (NULL == keys)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to download %skeys\n",
+ rx->url);
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got response for %skeys\n",
+ rx->url);
+ if ( (settings->use_stefan) &&
+ (GNUNET_OK !=
+ TALER_amount_is_valid (&oc->parse_order.max_fee)) )
+ update_stefan (oc,
+ keys);
+ get_acceptable (oc,
+ rx->url,
+ exchange);
+ }
+ GNUNET_free (rx->url);
+ GNUNET_free (rx);
+ if (NULL != oc->set_exchanges.pending_reload_head)
+ return;
+ resume_with_keys (oc);
+}
+
+
+/**
+ * Force re-downloading of /keys from @a exchange,
+ * we currently have no acceptable exchange, so we
+ * should try to get one.
+ *
+ * @param cls closure with our `struct OrderContext`
+ * @param url base URL of the exchange
+ * @param exchange internal handle for the exchange
+ */
+static void
+get_exchange_keys (void *cls,
+ const char *url,
+ const struct TMH_Exchange *exchange)
+{
+ struct OrderContext *oc = cls;
+ struct RekeyExchange *rx;
+
+ rx = GNUNET_new (struct RekeyExchange);
+ rx->oc = oc;
+ rx->url = GNUNET_strdup (url);
+ GNUNET_CONTAINER_DLL_insert (oc->set_exchanges.pending_reload_head,
+ oc->set_exchanges.pending_reload_tail,
+ rx);
+ if (oc->set_exchanges.forced_reload)
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Forcing download of %skeys\n",
+ url);
+ rx->fo = TMH_EXCHANGES_keys4exchange (url,
+ oc->set_exchanges.forced_reload,
+ &keys_cb,
+ rx);
+}
+
+/**
+ * Fetch details about the token family with the given @a slug
+ * and add them to the list of token authorities. Check if the
+ * token family already has a valid key configured and if not,
+ * create a new one.
+ *
+ * @param[in,out] oc order context
+ * @param slug slug of the token family
+ * @param start_date validity start date of the token
*/
static MHD_RESULT
-patch_order (struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc,
- const struct TALER_MerchantPostDataHashP *h_post_data,
- json_t *order,
- const struct TALER_ClaimTokenP *claim_token,
- struct GNUNET_TIME_Relative refund_delay,
- unsigned int inventory_products_length,
- const struct InventoryProduct inventory_products[],
- unsigned int uuids_length,
- const struct GNUNET_Uuid uuids[])
+set_token_authority (struct OrderContext *oc,
+ const char *slug,
+ struct GNUNET_TIME_Timestamp start_date)
+{
+ struct TALER_MERCHANTDB_TokenFamilyKeyDetails key_details;
+ struct TALER_MerchantContractTokenAuthority authority;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Absolute min_start_date = GNUNET_TIME_absolute_subtract (
+ start_date.abs_time,
+ // TODO: make this configurable. This is the granularity of token
+ // expiration dates.
+ GNUNET_TIME_UNIT_DAYS
+ );
+
+ qs = TMH_db->lookup_token_family_key (TMH_db->cls,
+ oc->hc->instance->settings.id,
+ slug,
+ GNUNET_TIME_absolute_to_timestamp (min_start_date),
+ start_date,
+ &key_details);
+
+ if (qs <= 0)
+ {
+ enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ unsigned int http_status = 0;
+
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ ec = TALER_EC_GENERIC_DB_FETCH_FAILED;
+ break;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ ec = TALER_EC_GENERIC_DB_SOFT_FAILURE;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Token family slug unknown\n");
+ http_status = MHD_HTTP_NOT_FOUND;
+ ec = TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_SLUG_UNKNOWN;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* case listed to make compilers happy */
+ GNUNET_assert (0);
+ }
+ GNUNET_break (0);
+ reply_with_error (oc,
+ http_status,
+ ec,
+ "token_family_slug");
+ return MHD_NO;
+ }
+
+ if (NULL == key_details.pub)
+ {
+ /* If public key is NULL, private key must also be NULL */
+ GNUNET_assert (NULL == key_details.priv);
+
+ struct GNUNET_CRYPTO_BlindSignPrivateKey *priv;
+ struct GNUNET_CRYPTO_BlindSignPublicKey *pub;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_TIME_Timestamp valid_before = GNUNET_TIME_absolute_to_timestamp(
+ GNUNET_TIME_absolute_add (now,
+ key_details.token_family.duration));
+
+ GNUNET_CRYPTO_blind_sign_keys_create (&priv,
+ &pub,
+ // TODO: Make cipher and key length configurable
+ GNUNET_CRYPTO_BSA_RSA,
+ 4096);
+
+ struct TALER_TokenFamilyPublicKey token_pub = {
+ .public_key = *pub,
+ };
+ struct TALER_TokenFamilyPrivateKey token_priv = {
+ .private_key = *priv,
+ };
+
+ qs = TMH_db->insert_token_family_key (TMH_db->cls,
+ slug,
+ &token_pub,
+ &token_priv,
+ GNUNET_TIME_absolute_to_timestamp (now),
+ valid_before);
+
+ authority.token_expiration = valid_before;
+ authority.pub = &token_pub;
+ // GNUNET_CRYPTO_blind_sign_priv_decref (&token_priv.private_key);
+ } else {
+ authority.token_expiration = key_details.valid_before;
+ authority.pub = key_details.pub;
+ }
+
+ authority.label = slug;
+ authority.description = key_details.token_family.description;
+ authority.description_i18n = key_details.token_family.description_i18n;
+
+ GNUNET_free (key_details.token_family.slug);
+ GNUNET_free (key_details.token_family.name);
+ if (NULL != key_details.priv) {
+ GNUNET_CRYPTO_blind_sign_priv_decref (&key_details.priv->private_key);
+ }
+
+ switch (key_details.token_family.kind) {
+ case TALER_MERCHANTDB_TFK_Subscription:
+ authority.kind = TALER_MCTK_SUBSCRIPTION;
+ authority.details.subscription.start_date = key_details.valid_after;
+ authority.details.subscription.end_date = key_details.valid_before;
+ authority.critical = true;
+ // TODO: Set trusted domains
+ break;
+ case TALER_MERCHANTDB_TFK_Discount:
+ authority.kind = TALER_MCTK_DISCOUNT;
+ authority.critical = false;
+ // TODO: Set expected domains
+ break;
+ }
+
+ GNUNET_array_append (oc->parse_choices.authorities,
+ oc->parse_choices.authorities_len,
+ authority);
+
+ return MHD_YES;
+}
+
+/**
+ * Serialize order into @a oc->serialize_order.contract,
+ * ready to be stored in the database. Upon success, continue
+ * processing with check_contract().
+ *
+ * @param[in,out] oc order context
+ */
+static void
+serialize_order (struct OrderContext *oc)
{
const struct TALER_MERCHANTDB_InstanceSettings *settings =
- &hc->instance->settings;
- const char *order_id = NULL;
- const char *fulfillment_url = NULL;
+ &oc->hc->instance->settings;
+ json_t *merchant;
+ json_t *token_types = json_object ();
+ json_t *choices = json_array ();
+
+ merchant = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("name",
+ settings->name),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("website",
+ settings->website)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("email",
+ settings->email)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("logo",
+ settings->logo)));
+ GNUNET_assert (NULL != merchant);
+ {
+ json_t *loca;
+
+ /* Handle merchant address */
+ loca = settings->address;
+ if (NULL != loca)
+ {
+ loca = json_deep_copy (loca);
+ GNUNET_assert (NULL != loca);
+ GNUNET_assert (0 ==
+ json_object_set_new (merchant,
+ "address",
+ loca));
+ }
+ }
+ {
+ json_t *juri;
+
+ /* Handle merchant jurisdiction */
+ juri = settings->jurisdiction;
+ if (NULL != juri)
+ {
+ juri = json_deep_copy (juri);
+ GNUNET_assert (NULL != juri);
+ GNUNET_assert (0 ==
+ json_object_set_new (merchant,
+ "jurisdiction",
+ juri));
+ }
+ }
+
+ for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++)
+ {
+ struct TALER_MerchantContractTokenAuthority *authority = &oc->parse_choices.authorities[i];
+
+ // TODO: Finish spec to clearly define how token families are stored in
+ // ContractTerms.
+ // Here are some thoughts:
+ // - Multiple keys of the same token family can be referenced in
+ // one contract. E.g. exchanging old subscription for new.
+ // - TokenAuthority should be renamed to TokenFamily for consistency.
+ // - TokenFamilySlug can be used instead of TokenAuthorityLabel, but
+ // every token-based in- or output needs to have a valid_after date,
+ // so it's clear with key is referenced.
+ json_t *jauthority = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("description",
+ authority->description),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("description_i18n",
+ authority->description_i18n)),
+ GNUNET_JSON_pack_data_auto ("h_pub",
+ &authority->pub->public_key.pub_key_hash),
+ GNUNET_JSON_pack_data_auto ("pub",
+ &authority->pub->public_key.pub_key_hash),
+ GNUNET_JSON_pack_timestamp ("token_expiration",
+ authority->token_expiration)
+ );
+
+ GNUNET_assert (0 ==
+ json_object_set_new (token_types,
+ authority->label,
+ jauthority));
+ }
+
+ for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
+ {
+ struct TALER_MerchantContractChoice *choice = &oc->parse_choices.choices[i];
+
+ json_t *inputs = json_array ();
+ json_t *outputs = json_array ();
+
+ for (unsigned int j = 0; j<choice->inputs_len; j++)
+ {
+ struct TALER_MerchantContractInput *input = &choice->inputs[j];
+
+ json_t *jinput = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_int64 ("type",
+ input->type)
+ );
+
+ if (TALER_MCIT_TOKEN == input->type)
+ {
+ GNUNET_assert(0 ==
+ json_object_set_new(jinput,
+ "number",
+ json_integer (
+ input->details.token.count)));
+ GNUNET_assert(0 ==
+ json_object_set_new(jinput,
+ "token_family_slug",
+ json_string (
+ input->details.token.token_family_slug)));
+ }
+
+ GNUNET_assert (0 == json_array_append_new (inputs, jinput));
+ }
+
+ for (unsigned int j = 0; j<choice->outputs_len; j++)
+ {
+ struct TALER_MerchantContractOutput *output = &choice->outputs[j];
+
+ json_t *joutput = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_int64 ("type",
+ output->type)
+ );
+
+ if (TALER_MCOT_TOKEN == output->type)
+ {
+ GNUNET_assert(0 ==
+ json_object_set_new(joutput,
+ "number",
+ json_integer (
+ output->details.token.count)));
+
+ GNUNET_assert(0 ==
+ json_object_set_new(joutput,
+ "token_family_slug",
+ json_string (
+ output->details.token.token_family_slug)));
+ }
+
+ GNUNET_assert (0 == json_array_append (outputs, joutput));
+ }
+
+ json_t *jchoice = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_incref ("inputs",
+ inputs),
+ GNUNET_JSON_pack_array_incref ("outputs",
+ outputs)
+ );
+
+ GNUNET_assert (0 == json_array_append (choices, jchoice));
+ }
+
+ oc->serialize_order.contract = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_int64 ("version",
+ oc->parse_order.version),
+ GNUNET_JSON_pack_string ("summary",
+ oc->parse_order.summary),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("summary_i18n",
+ oc->parse_order.summary_i18n)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("public_reorder_url",
+ oc->parse_order.public_reorder_url)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("fulfillment_message",
+ oc->parse_order.fulfillment_message)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("fulfillment_message_i18n",
+ oc->parse_order.fulfillment_message_i18n)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("fulfillment_url",
+ oc->parse_order.fulfillment_url)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_uint64 ("minimum_age",
+ oc->parse_order.minimum_age)),
+ GNUNET_JSON_pack_array_incref ("products",
+ oc->merge_inventory.products),
+ GNUNET_JSON_pack_data_auto ("h_wire",
+ &oc->add_payment_details.wm->h_wire),
+ GNUNET_JSON_pack_string ("wire_method",
+ oc->add_payment_details.wm->wire_method),
+ GNUNET_JSON_pack_string ("order_id",
+ oc->parse_order.order_id),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ oc->parse_order.timestamp),
+ GNUNET_JSON_pack_timestamp ("pay_deadline",
+ oc->parse_order.pay_deadline),
+ GNUNET_JSON_pack_timestamp ("wire_transfer_deadline",
+ oc->parse_order.wire_deadline),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_timestamp ("delivery_date",
+ oc->parse_order.delivery_date)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("delivery_location",
+ oc->parse_order.delivery_location)),
+ GNUNET_JSON_pack_string ("merchant_base_url",
+ oc->parse_order.merchant_base_url),
+ GNUNET_JSON_pack_object_steal ("merchant",
+ merchant),
+ GNUNET_JSON_pack_data_auto ("merchant_pub",
+ &oc->hc->instance->merchant_pub),
+ GNUNET_JSON_pack_array_incref ("exchanges",
+ oc->set_exchanges.exchanges),
+ TALER_JSON_pack_amount ("max_fee",
+ &oc->set_max_fee.max_fee),
+ TALER_JSON_pack_amount ("amount",
+ &oc->parse_order.brutto),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_incref ("choices",
+ choices)
+ ),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("token_types",
+ token_types)
+ ),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("extra",
+ (json_t *) oc->parse_order.extra))
+ );
+
+ /* Pack does not work here, because it doesn't set zero-values for timestamps */
+ GNUNET_assert (0 ==
+ json_object_set_new (oc->serialize_order.contract,
+ "refund_deadline",
+ GNUNET_JSON_from_timestamp (
+ oc->parse_order.refund_deadline)));
+
+ GNUNET_log (
+ GNUNET_ERROR_TYPE_INFO,
+ "Refund deadline for contact is %llu\n",
+ (unsigned long long) oc->parse_order.refund_deadline.abs_time.abs_value_us);
+ GNUNET_log (
+ GNUNET_ERROR_TYPE_INFO,
+ "Wallet timestamp for contact is %llu\n",
+ (unsigned long long) oc->parse_order.timestamp.abs_time.abs_value_us);
+
+ /* Pack does not work here, because it sets zero-values for relative times */
+ /* auto_refund should only be set if it is not 0 */
+ if (! GNUNET_TIME_relative_is_zero (oc->parse_order.auto_refund))
+ {
+ GNUNET_assert (0 ==
+ json_object_set_new (oc->serialize_order.contract,
+ "auto_refund",
+ GNUNET_JSON_from_time_rel (
+ oc->parse_order.auto_refund)));
+ }
+
+ oc->phase++;
+}
+
+/**
+ * Set max_fee in @a oc based on STEFAN value if
+ * not yet present. Upon success, continue
+ * processing with serialize_order().
+ *
+ * @param[in,out] oc order context
+ */
+static void
+set_max_fee (struct OrderContext *oc)
+{
+ const struct TALER_MERCHANTDB_InstanceSettings *settings =
+ &oc->hc->instance->settings;
+
+ if (GNUNET_OK !=
+ TALER_amount_is_valid (&oc->parse_order.max_fee))
+ {
+ struct TALER_Amount stefan;
+
+ if ( (settings->use_stefan) &&
+ (GNUNET_OK ==
+ TALER_amount_is_valid (&oc->set_exchanges.max_stefan_fee)) )
+ stefan = oc->set_exchanges.max_stefan_fee;
+ else
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (oc->parse_order.brutto.currency,
+ &stefan));
+ oc->set_max_fee.max_fee = stefan;
+ }
+ else
+ {
+ oc->set_max_fee.max_fee = oc->parse_order.max_fee;
+ }
+ oc->phase++;
+}
+
+/**
+ * Set list of acceptable exchanges in @a oc. Upon success, continue
+ * processing with set_max_fee().
+ *
+ * @param[in,out] oc order context
+ * @return true to suspend execution
+ */
+static bool
+set_exchanges (struct OrderContext *oc)
+{
+ /* Note: re-building 'oc->exchanges' every time here might be a tad
+ expensive; could likely consider caching the result if it starts to
+ matter. */
+ if (NULL == oc->set_exchanges.exchanges)
+ {
+ oc->set_exchanges.exchanges = json_array ();
+ GNUNET_assert (NULL != oc->set_exchanges.exchanges);
+ TMH_exchange_get_trusted (&get_exchange_keys,
+ oc);
+ }
+ else if (! oc->set_exchanges.exchange_good)
+ {
+ if (! oc->set_exchanges.forced_reload)
+ {
+ oc->set_exchanges.forced_reload = true;
+ GNUNET_assert (0 ==
+ json_array_clear (oc->set_exchanges.exchanges));
+ TMH_exchange_get_trusted (&get_exchange_keys,
+ oc);
+ }
+ }
+ if (NULL != oc->set_exchanges.pending_reload_head)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Still trying to (re)load %skeys\n",
+ oc->set_exchanges.pending_reload_head->url);
+ MHD_suspend_connection (oc->connection);
+ oc->suspended = GNUNET_YES;
+ GNUNET_CONTAINER_DLL_insert (oc_head,
+ oc_tail,
+ oc);
+ return true; /* reloads pending */
+ }
+ if (0 == json_array_size (oc->set_exchanges.exchanges))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Cannot create order: lacking trusted exchanges\n");
+ reply_with_error (
+ oc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_EXCHANGES_FOR_WIRE_METHOD,
+ oc->add_payment_details.wm->wire_method);
+ return false;
+ }
+ if (! oc->set_exchanges.exchange_good)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Creating order, but possibly without usable trusted exchanges\n");
+ }
+ oc->phase++;
+ return false;
+}
+
+
+/**
+ * Parse the order field of the request. Upon success, continue
+ * processing with parse_choices().
+ *
+ * @param[in,out] oc order context
+ */
+static void
+parse_order (struct OrderContext *oc)
+{
+ const struct TALER_MERCHANTDB_InstanceSettings *settings =
+ &oc->hc->instance->settings;
const char *merchant_base_url = NULL;
- json_t *jmerchant = NULL;
- json_t *delivery_location = NULL;
- struct TALER_Amount max_wire_fee = { 0 };
- struct TALER_Amount max_fee = { 0 };
- uint32_t wire_fee_amortization = 0;
- struct GNUNET_TIME_Timestamp timestamp
- = GNUNET_TIME_UNIT_ZERO_TS;
- struct GNUNET_TIME_Timestamp delivery_date
- = GNUNET_TIME_UNIT_ZERO_TS;
- struct GNUNET_TIME_Timestamp refund_deadline
- = GNUNET_TIME_UNIT_FOREVER_TS;
- struct GNUNET_TIME_Timestamp pay_deadline
- = GNUNET_TIME_UNIT_ZERO_TS;
- struct GNUNET_TIME_Timestamp wire_deadline
- = GNUNET_TIME_UNIT_FOREVER_TS;
+ const char *version = NULL;
+ const json_t *jmerchant = NULL;
/* auto_refund only needs to be type-checked,
* mostly because in GNUnet relative times can't
* be negative. */
- struct GNUNET_TIME_Relative auto_refund;
+ bool no_fee;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("merchant_base_url",
- &merchant_base_url),
+ GNUNET_JSON_spec_string ("version",
+ &version),
+ NULL),
+ TALER_JSON_spec_amount_any ("amount",
+ &oc->parse_order.brutto),
+ GNUNET_JSON_spec_string ("summary",
+ &oc->parse_order.summary),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("products",
+ &oc->parse_order.products),
NULL),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("merchant",
- &jmerchant),
+ GNUNET_JSON_spec_json ("summary_i18n",
+ &oc->parse_order.summary_i18n),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("order_id",
- &order_id),
+ &oc->parse_order.order_id),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("fulfillment_message",
+ &oc->parse_order.fulfillment_message),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_json ("fulfillment_message_i18n",
+ &oc->parse_order.fulfillment_message_i18n),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("fulfillment_url",
- &fulfillment_url),
+ &oc->parse_order.fulfillment_url),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("public_reorder_url",
+ &oc->parse_order.public_reorder_url),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("choices",
+ &oc->parse_order.choices),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_web_url ("merchant_base_url",
+ &merchant_base_url),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("merchant",
+ &jmerchant),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_timestamp ("timestamp",
- &timestamp),
+ &oc->parse_order.timestamp),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_timestamp ("refund_deadline",
- &refund_deadline),
+ &oc->parse_order.refund_deadline),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_timestamp ("pay_deadline",
- &pay_deadline),
+ &oc->parse_order.pay_deadline),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
- &wire_deadline),
+ &oc->parse_order.wire_deadline),
NULL),
GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_amount ("max_fee",
- TMH_currency,
- &max_fee),
- NULL),
+ TALER_JSON_spec_amount_any ("max_fee",
+ &oc->parse_order.max_fee),
+ &no_fee),
GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_amount ("max_wire_fee",
- TMH_currency,
- &max_wire_fee),
+ GNUNET_JSON_spec_json ("delivery_location",
+ &oc->parse_order.delivery_location),
NULL),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_uint32 ("wire_fee_amortization",
- &wire_fee_amortization),
+ GNUNET_JSON_spec_timestamp ("delivery_date",
+ &oc->parse_order.delivery_date),
NULL),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("delivery_date",
- &delivery_date),
+ GNUNET_JSON_spec_uint32 ("minimum_age",
+ &oc->parse_order.minimum_age),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_relative_time ("auto_refund",
- &auto_refund),
+ &oc->parse_order.auto_refund),
NULL),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("delivery_location",
- &delivery_location),
+ GNUNET_JSON_spec_object_const ("extra",
+ &oc->parse_order.extra),
NULL),
GNUNET_JSON_spec_end ()
};
enum GNUNET_GenericReturnValue ret;
- ret = TALER_MHD_parse_json_data (connection,
- order,
+ oc->parse_order.refund_deadline = GNUNET_TIME_UNIT_FOREVER_TS;
+ oc->parse_order.wire_deadline = GNUNET_TIME_UNIT_FOREVER_TS;
+ ret = TALER_MHD_parse_json_data (oc->connection,
+ oc->parse_request.order,
spec);
if (GNUNET_OK != ret)
{
GNUNET_break_op (0);
- return (GNUNET_NO == ret)
- ? MHD_YES
- : MHD_NO;
+ finalize_order2 (oc,
+ ret);
+ return;
+ }
+ if (NULL == version || 0 == strcmp("0", version))
+ {
+ oc->parse_order.version = TALER_MCV_V0;
+
+ if (NULL != oc->parse_order.choices)
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_UNEXPECTED_REQUEST_ERROR,
+ "choices array must be null for v0 contracts");
+ return;
+ }
+ }
+ else if (0 == strcmp("1", version))
+ {
+ oc->parse_order.version = TALER_MCV_V1;
+
+ if (! json_is_array(oc->parse_order.choices))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "order.choices is not a valid array");
+ return;
+ }
+ }
+ else
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_VERSION_MALFORMED,
+ "invalid version specified in order, supported are null, '0' or '1'");
+ return;
+ }
+ if (! TMH_test_exchange_configured_for_currency (
+ oc->parse_order.brutto.currency))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_CURRENCY_MISMATCH,
+ "no trusted exchange for this currency");
+ return;
+ }
+ if ( (! no_fee) &&
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (&oc->parse_order.brutto,
+ &oc->parse_order.max_fee)) )
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_CURRENCY_MISMATCH,
+ "different currencies used for 'max_fee' and 'amount' currency");
+ return;
}
/* Add order_id if it doesn't exist. */
- if (NULL == order_id)
+ if (NULL == oc->parse_order.order_id)
{
char buf[256];
time_t timer;
@@ -812,18 +2011,18 @@ patch_order (struct MHD_Connection *connection,
size_t off;
uint64_t rand;
char *last;
- json_t *jbuf;
time (&timer);
tm_info = localtime (&timer);
if (NULL == tm_info)
{
GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (
- connection,
+ reply_with_error (
+ oc,
MHD_HTTP_INTERNAL_SERVER_ERROR,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_LOCALTIME,
NULL);
+ return;
}
off = strftime (buf,
sizeof (buf) - 1,
@@ -840,25 +2039,21 @@ patch_order (struct MHD_Connection *connection,
sizeof (buf) - off);
GNUNET_assert (NULL != last);
*last = '\0';
- jbuf = json_string (buf);
- GNUNET_assert (NULL != jbuf);
+
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Assigning order ID `%s' server-side\n",
buf);
- GNUNET_break (0 ==
- json_object_set_new (order,
- "order_id",
- jbuf));
- order_id = json_string_value (jbuf);
- GNUNET_assert (NULL != order_id);
+
+ oc->parse_order.order_id = GNUNET_strdup (buf);
+ GNUNET_assert (NULL != oc->parse_order.order_id);
}
/* Patch fulfillment URL with order_id (implements #6467). */
- if (NULL != fulfillment_url)
+ if (NULL != oc->parse_order.fulfillment_url)
{
const char *pos;
- pos = strstr (fulfillment_url,
+ pos = strstr (oc->parse_order.fulfillment_url,
"${ORDER_ID}");
if (NULL != pos)
{
@@ -870,27 +2065,25 @@ patch_order (struct MHD_Connection *connection,
"${ORDER_ID}"))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "fulfillment_url");
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "fulfillment_url");
+ return;
}
GNUNET_asprintf (&nurl,
"%.*s%s%s",
/* first output URL until ${ORDER_ID} */
- (int) (pos - fulfillment_url),
- fulfillment_url,
+ (int) (pos - oc->parse_order.fulfillment_url),
+ oc->parse_order.fulfillment_url,
/* replace ${ORDER_ID} with the right order_id */
- order_id,
+ oc->parse_order.order_id,
/* append rest of original URL */
pos + strlen ("${ORDER_ID}"));
- /* replace in JSON of the order */
- GNUNET_break (0 ==
- json_object_set_new (order,
- "fulfillment_url",
- json_string (nurl)));
+
+ oc->parse_order.fulfillment_url = GNUNET_strdup (nurl);
+
GNUNET_free (nurl);
}
}
@@ -901,437 +2094,507 @@ patch_order (struct MHD_Connection *connection,
struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
/* Add timestamp if it doesn't exist (or is zero) */
- if (GNUNET_TIME_absolute_is_zero (timestamp.abs_time))
+ if (GNUNET_TIME_absolute_is_zero (oc->parse_order.timestamp.abs_time))
{
- GNUNET_assert (0 ==
- json_object_set_new (order,
- "timestamp",
- GNUNET_JSON_from_timestamp (now)));
+ oc->parse_order.timestamp = now;
}
/* If no refund_deadline given, set one based on refund_delay. */
- if (GNUNET_TIME_absolute_is_never (refund_deadline.abs_time))
+ if (GNUNET_TIME_absolute_is_never (
+ oc->parse_order.refund_deadline.abs_time))
{
- if (GNUNET_TIME_relative_is_zero (refund_delay))
+ if (GNUNET_TIME_relative_is_zero (oc->parse_request.refund_delay))
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Refund delay is zero, no refunds are possible for this order\n");
- refund_deadline = now; /* if delay was 0, ensure that refund_deadline == timestamp */
+ oc->parse_order.refund_deadline = GNUNET_TIME_UNIT_ZERO_TS;
}
else
{
- refund_deadline = GNUNET_TIME_relative_to_timestamp (refund_delay);
+ oc->parse_order.refund_deadline = GNUNET_TIME_relative_to_timestamp (
+ oc->parse_request.refund_delay);
}
-
- GNUNET_assert (0 ==
- json_object_set_new (order,
- "refund_deadline",
- GNUNET_JSON_from_timestamp (
- refund_deadline)));
}
- if ( (! GNUNET_TIME_absolute_is_zero (delivery_date.abs_time)) &&
- (GNUNET_TIME_timestamp_cmp (delivery_date,
- <,
- now)) )
+
+ if ( (! GNUNET_TIME_absolute_is_zero (
+ oc->parse_order.delivery_date.abs_time)) &&
+ (GNUNET_TIME_absolute_is_past (
+ oc->parse_order.delivery_date.abs_time)) )
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (
- connection,
+ reply_with_error (
+ oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_DELIVERY_DATE_IN_PAST,
NULL);
+ return;
}
}
- if (GNUNET_TIME_absolute_is_zero (pay_deadline.abs_time))
+ if (GNUNET_TIME_absolute_is_zero (oc->parse_order.pay_deadline.abs_time))
{
- struct GNUNET_TIME_Timestamp t;
+ oc->parse_order.pay_deadline = GNUNET_TIME_relative_to_timestamp (
+ settings->default_pay_delay);
+ }
+ else if (GNUNET_TIME_absolute_is_past (oc->parse_order.pay_deadline.abs_time))
+ {
+ GNUNET_break_op (0);
+ reply_with_error (
+ oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_PAY_DEADLINE_IN_PAST,
+ NULL);
+ return;
+ }
- t = GNUNET_TIME_relative_to_timestamp (settings->default_pay_delay);
- GNUNET_assert (0 ==
- json_object_set_new (order,
- "pay_deadline",
- GNUNET_JSON_from_timestamp (t)));
+ if ( (! GNUNET_TIME_absolute_is_zero (
+ oc->parse_order.refund_deadline.abs_time)) &&
+ (GNUNET_TIME_absolute_is_past (
+ oc->parse_order.refund_deadline.abs_time)) )
+ {
+ GNUNET_break_op (0);
+ reply_with_error (
+ oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_REFUND_DEADLINE_IN_PAST,
+ NULL);
+ return;
}
- if (GNUNET_TIME_absolute_is_never (wire_deadline.abs_time))
+ if (GNUNET_TIME_absolute_is_never (oc->parse_order.wire_deadline.abs_time))
{
struct GNUNET_TIME_Timestamp t;
t = GNUNET_TIME_relative_to_timestamp (
GNUNET_TIME_relative_max (settings->default_wire_transfer_delay,
- refund_delay));
- wire_deadline = GNUNET_TIME_timestamp_max (refund_deadline,
- t);
- if (GNUNET_TIME_absolute_is_never (wire_deadline.abs_time))
+ oc->parse_request.refund_delay));
+ oc->parse_order.wire_deadline = GNUNET_TIME_timestamp_max (
+ oc->parse_order.refund_deadline,
+ t);
+ if (GNUNET_TIME_absolute_is_never (oc->parse_order.wire_deadline.abs_time))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (
- connection,
+ reply_with_error (
+ oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_WIRE_DEADLINE_IS_NEVER,
"order:wire_transfer_deadline");
-
+ return;
}
- GNUNET_assert (0 ==
- json_object_set_new (order,
- "wire_transfer_deadline",
- GNUNET_JSON_from_timestamp (
- wire_deadline)));
}
- if (GNUNET_TIME_timestamp_cmp (wire_deadline,
+ if (GNUNET_TIME_timestamp_cmp (oc->parse_order.wire_deadline,
<,
- refund_deadline))
+ oc->parse_order.refund_deadline))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (
- connection,
+ reply_with_error (
+ oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_REFUND_AFTER_WIRE_DEADLINE,
"order:wire_transfer_deadline;order:refund_deadline");
+ return;
}
- /* Note: total amount currency match checked
- later in execute_order() */
- if (GNUNET_OK !=
- TALER_amount_is_valid (&max_wire_fee))
- {
- GNUNET_assert (0 ==
- json_object_set_new (
- order,
- "max_wire_fee",
- TALER_JSON_from_amount (&settings->default_max_wire_fee)));
- }
-
- if (GNUNET_OK !=
- TALER_amount_is_valid (&max_fee))
+ if (NULL != merchant_base_url)
{
- GNUNET_assert (0 ==
- json_object_set_new (
- order,
- "max_fee",
- TALER_JSON_from_amount (
- &settings->default_max_deposit_fee)));
- }
- if (0 == wire_fee_amortization)
- {
- GNUNET_assert (0 ==
- json_object_set_new (
- order,
- "wire_fee_amortization",
- json_integer (
- (json_int_t) settings->default_wire_fee_amortization)));
+ if (('\0' == *merchant_base_url) ||
+ ('/' != merchant_base_url[strlen (merchant_base_url) - 1]))
+ {
+ GNUNET_break_op (0);
+ reply_with_error (
+ oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_PROPOSAL_PARSE_ERROR,
+ "merchant_base_url is not valid");
+ return;
+ }
+ oc->parse_order.merchant_base_url
+ = GNUNET_strdup (merchant_base_url);
}
- if (NULL == merchant_base_url)
+ else
{
char *url;
- url = make_merchant_base_url (connection,
+ url = make_merchant_base_url (oc->connection,
settings->id);
- GNUNET_assert (0 ==
- json_object_set_new (order,
- "merchant_base_url",
- json_string (url)));
- GNUNET_free (url);
+ if (NULL == url)
+ {
+ GNUNET_break_op (0);
+ reply_with_error (
+ oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MISSING,
+ "order:merchant_base_url");
+ return;
+ }
+ oc->parse_order.merchant_base_url = url;
}
- else if (('\0' == *merchant_base_url) ||
- ('/' != merchant_base_url[strlen (merchant_base_url) - 1]))
+
+ if ( (NULL != oc->parse_order.products) &&
+ (! TMH_products_array_valid (oc->parse_order.products)) )
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (
- connection,
+ reply_with_error (
+ oc,
MHD_HTTP_BAD_REQUEST,
- TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_PROPOSAL_PARSE_ERROR,
- "merchant_base_url is not valid");
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "order.products");
+ return;
}
- /* Fill in merchant information if necessary */
+ /* Merchant information must not already be present */
if (NULL != jmerchant)
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (
- connection,
+ reply_with_error (
+ oc,
MHD_HTTP_BAD_REQUEST,
TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_PROPOSAL_PARSE_ERROR,
"'merchant' field already set, but must be provided by backend");
+ return;
}
+ if ( (NULL != oc->parse_order.delivery_location) &&
+ (! TMH_location_object_valid (oc->parse_order.delivery_location)) )
{
- json_t *jm;
+ GNUNET_break_op (0);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "delivery_location");
+ return;
+ }
- jm = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("name",
- settings->name),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("website",
- settings->website)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("email",
- settings->email)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("logo",
- settings->logo)));
- GNUNET_assert (NULL != jm);
- {
- json_t *loca;
+ oc->phase++;
+}
- /* Handle merchant address */
- loca = settings->address;
- if (NULL != loca)
- {
- loca = json_deep_copy (loca);
- GNUNET_assert (0 ==
- json_object_set_new (jm,
- "address",
- loca));
- }
+/**
+ * Parse contract choices. Upon success, continue
+ * processing with merge_inventory().
+ *
+ * @param[in,out] oc order context
+ */
+static void
+parse_choices (struct OrderContext *oc)
+{
+ if (NULL == oc->parse_order.choices)
+ {
+ oc->phase++;
+ return;
+ }
+
+ GNUNET_array_grow (oc->parse_choices.choices,
+ oc->parse_choices.choices_len,
+ json_array_size (oc->parse_order.choices));
+ for (unsigned int i = 0; i<oc->parse_choices.choices_len; i++)
+ {
+ const char *error_name;
+ unsigned int error_line;
+ const json_t *jinputs;
+ const json_t *joutputs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("inputs",
+ &jinputs),
+ GNUNET_JSON_spec_array_const ("outputs",
+ &joutputs),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = GNUNET_JSON_parse (json_array_get (oc->parse_order.choices, i),
+ spec,
+ &error_name,
+ &error_line);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Choice parsing failed: %s:%u\n",
+ error_name,
+ error_line);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "choice");
+ return;
}
+
+ if (! json_is_array (jinputs) ||
+ ! json_is_array (joutputs))
{
- json_t *locj;
+ GNUNET_break_op (0);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "inputs or outputs");
+ return;
+ }
- /* Handle merchant jurisdiction */
- locj = settings->jurisdiction;
- if (NULL != locj)
+ {
+ // TODO: Maybe move to a separate function
+ const json_t *jinput;
+ size_t idx;
+ json_array_foreach ((json_t *) jinputs, idx, jinput)
{
- locj = json_deep_copy (locj);
- GNUNET_assert (0 ==
- json_object_set_new (jm,
- "jurisdiction",
- locj));
+ // TODO: Assuming this struct would have more fields, would i use GNUNET_new then?
+ // Or should i use GNUNET_grow first and then get the element using the index?
+ // Assuming you add a field in the future, it's easier to that way, so you don't
+ // free it.
+ struct TALER_MerchantContractInput input = {.details.token.count = 1};
+ const char *kind;
+ const char *ierror_name;
+ unsigned int ierror_line;
+
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("kind",
+ &kind),
+ GNUNET_JSON_spec_string ("token_family_slug",
+ &input.details.token.token_family_slug),
+ GNUNET_JSON_spec_timestamp ("valid_after",
+ &input.details.token.valid_after),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("count",
+ &input.details.token.count),
+ NULL),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jinput,
+ ispec,
+ &ierror_name,
+ &ierror_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid input #%u for field %s\n",
+ (unsigned int) idx,
+ ierror_name);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ ierror_name);
+ return;
+ }
+
+ input.type = TMH_string_to_contract_input_type (kind);
+
+ if (TALER_MCIT_INVALID == input.type)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Field 'kind' invalid in input #%u\n",
+ (unsigned int) idx);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "kind");
+ return;
+ }
+
+ if (0 == input.details.token.count)
+ {
+ /* Ignore inputs with 'number' field set to 0 */
+ continue;
+ }
+
+ bool found = false;
+
+ for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++)
+ {
+ if (0 == strcmp (oc->parse_choices.authorities[i].label,
+ input.details.token.token_family_slug))
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (! found)
+ {
+ MHD_RESULT res;
+ res = set_token_authority (oc,
+ input.details.token.token_family_slug,
+ input.details.token.valid_after);
+
+ if (MHD_NO == res)
+ {
+ return;
+ }
+ }
+
+ GNUNET_array_append (oc->parse_choices.choices[i].inputs,
+ oc->parse_choices.choices[i].inputs_len,
+ input);
}
}
- GNUNET_assert (0 ==
- json_object_set_new (order,
- "merchant",
- jm));
- }
- /* add fields to the contract that the backend should provide */
- GNUNET_assert (0 ==
- json_object_set (order,
- "exchanges",
- TMH_trusted_exchanges));
- GNUNET_assert (0 ==
- json_object_set (order,
- "auditors",
- j_auditors));
- GNUNET_assert (0 ==
- json_object_set_new (order,
- "merchant_pub",
- GNUNET_JSON_from_data_auto (
- &hc->instance->merchant_pub)));
- if (GNUNET_OK !=
- TALER_JSON_contract_seed_forgettable (order))
- {
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_JSON_INVALID,
- "could not compute hash of order due to bogus forgettable fields");
- }
+ {
+ const json_t *joutput;
+ size_t idx;
+ json_array_foreach ((json_t *) joutputs, idx, joutput)
+ {
+ struct TALER_MerchantContractOutput output = { .details.token.count = 1 };
+ const char *kind;
+ const char *ierror_name;
+ unsigned int ierror_line;
+
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("kind",
+ &kind),
+ GNUNET_JSON_spec_string ("token_family_slug",
+ &output.details.token.token_family_slug),
+ GNUNET_JSON_spec_timestamp ("valid_after",
+ &output.details.token.valid_after),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("count",
+ &output.details.token.count),
+ NULL),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (joutput,
+ ispec,
+ &ierror_name,
+ &ierror_line))
+ {
+ GNUNET_JSON_parse_free (spec);
+ GNUNET_JSON_parse_free (ispec);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid output #%u for field %s\n",
+ (unsigned int) idx,
+ ierror_name);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ ierror_name);
+ return;
+ }
- if ( (NULL != delivery_location) &&
- (! TMH_location_object_valid (delivery_location)) )
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "delivery_location");
- }
+ output.type = TMH_string_to_contract_output_type (kind);
- /* sanity check result */
- {
- struct TALER_PrivateContractHashP h_control;
+ if (TALER_MCOT_INVALID == output.type)
+ {
+ GNUNET_JSON_parse_free (spec);
+ GNUNET_JSON_parse_free (ispec);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Field 'kind' invalid in output #%u\n",
+ (unsigned int) idx);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "kind");
+ return;
+ }
- switch (TALER_JSON_contract_hash (order,
- &h_control))
- {
- case GNUNET_SYSERR:
- GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
- "could not compute hash of patched order");
- case GNUNET_NO:
- GNUNET_JSON_parse_free (spec);
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_FAILED_COMPUTE_JSON_HASH,
- "order contained unallowed values");
- case GNUNET_OK:
- break;
+ if (0 == output.details.token.count)
+ {
+ /* Ignore outputs with 'number' field set to 0 */
+ continue;
+ }
+
+ bool found = false;
+
+ for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++)
+ {
+ if (0 == strcmp (oc->parse_choices.authorities[i].label,
+ output.details.token.token_family_slug))
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (! found)
+ {
+ MHD_RESULT res;
+ res = set_token_authority (oc,
+ output.details.token.token_family_slug,
+ output.details.token.valid_after);
+
+ if (MHD_NO == res)
+ {
+ return;
+ }
+ }
+
+ GNUNET_array_append (oc->parse_choices.choices[i].outputs,
+ oc->parse_choices.choices[i].outputs_len,
+ output);
+ }
}
}
- {
- MHD_RESULT mres;
- mres = execute_order (connection,
- hc,
- h_post_data,
- order,
- claim_token,
- inventory_products_length,
- inventory_products,
- uuids_length,
- uuids);
- GNUNET_JSON_parse_free (spec);
- return mres;
- }
+ oc->phase++;
}
-
/**
* Process the @a payment_target and add the details of how the
* order could be paid to @a order. On success, continue
- * processing with patch_order().
+ * processing with set_exchanges().
*
- * @param connection connection to write the result or error to
- * @param hc handler context for the request
- * @param h_post_data hash of the client's POST request, for idempotency checks
- * @param[in,out] order order to process (can be modified)
- * @param claim_token token to use for access control
- * @param refund_delay refund delay
- * @param payment_target desired wire method, NULL for no preference
- * @param inventory_products_length length of the @a inventory_products array
- * @param inventory_products array of products to add to @a order from our inventory
- * @param uuids_length length of the @a uuids array
- * @param uuids array of UUIDs used to reserve products from @a inventory_products
- * @return MHD result code
+ * @param[in,out] oc order context
*/
-static MHD_RESULT
-add_payment_details (struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc,
- const struct TALER_MerchantPostDataHashP *h_post_data,
- json_t *order,
- const struct TALER_ClaimTokenP *claim_token,
- struct GNUNET_TIME_Relative refund_delay,
- const char *payment_target,
- unsigned int inventory_products_length,
- const struct InventoryProduct inventory_products[],
- unsigned int uuids_length,
- const struct GNUNET_Uuid uuids[])
+static void
+add_payment_details (struct OrderContext *oc)
{
struct TMH_WireMethod *wm;
- wm = hc->instance->wm_head;
+ wm = oc->hc->instance->wm_head;
/* Locate wire method that has a matching payment target */
while ( (NULL != wm) &&
( (! wm->active) ||
- ( (NULL != payment_target) &&
- (0 != strcasecmp (payment_target,
+ ( (NULL != oc->parse_request.payment_target) &&
+ (0 != strcasecmp (oc->parse_request.payment_target,
wm->wire_method) ) ) ) )
wm = wm->next;
if (NULL == wm)
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"No wire method available for instance '%s'\n",
- hc->instance->settings.id);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_INSTANCE_CONFIGURATION_LACKS_WIRE,
- payment_target);
+ oc->hc->instance->settings.id);
+ reply_with_error (oc,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_INSTANCE_CONFIGURATION_LACKS_WIRE,
+ oc->parse_request.payment_target);
+ return;
}
- GNUNET_assert (0 ==
- json_object_set_new (order,
- "h_wire",
- GNUNET_JSON_from_data_auto (
- &wm->h_wire)));
- GNUNET_assert (0 ==
- json_object_set_new (order,
- "wire_method",
- json_string (wm->wire_method)));
- return patch_order (connection,
- hc,
- h_post_data,
- order,
- claim_token,
- refund_delay,
- inventory_products_length,
- inventory_products,
- uuids_length,
- uuids);
+ oc->add_payment_details.wm = wm;
+ oc->phase++;
}
/**
- * Merge the inventory products into @a order, querying the
+ * Merge the inventory products into products, querying the
* database about the details of those products. Upon success,
* continue processing by calling add_payment_details().
*
- * @param connection connection to write the result or error to
- * @param hc handler context for the request
- * @param h_post_data hash of the client's POST request, for idempotency checks
- * @param[in,out] order order to process (can be modified)
- * @param claim_token token to use for access control
- * @param refund_delay time window where it is possible to ask a refund
- * @param payment_target RFC8905 payment target type to find a matching merchant account
- * @param inventory_products_length length of the @a inventory_products array
- * @param inventory_products array of products to add to @a order from our inventory
- * @param uuids_length length of the @a uuids array
- * @param uuids array of UUIDs used to reserve products from @a inventory_products
- * @return MHD result code
+ * @param[in,out] oc order context to process
*/
-static MHD_RESULT
-merge_inventory (struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc,
- const struct TALER_MerchantPostDataHashP *h_post_data,
- json_t *order,
- const struct TALER_ClaimTokenP *claim_token,
- struct GNUNET_TIME_Relative refund_delay,
- const char *payment_target,
- unsigned int inventory_products_length,
- const struct InventoryProduct inventory_products[],
- unsigned int uuids_length,
- const struct GNUNET_Uuid uuids[])
+static void
+merge_inventory (struct OrderContext *oc)
{
/**
- * inventory_products => instructions to add products to contract terms
- * order.products => contains products that are not from the backend-managed inventory.
+ * parse_request.inventory_products => instructions to add products to contract terms
+ * parse_order.products => contains products that are not from the backend-managed inventory.
*/
- GNUNET_assert (NULL != order);
- {
- json_t *jprod = json_object_get (order,
- "products");
- if (NULL == jprod)
- {
- GNUNET_assert (0 ==
- json_object_set_new (order,
- "products",
- json_array ()));
- }
- else if (! TMH_products_array_valid (jprod))
- {
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "order.products");
- }
- }
-
+ if (NULL != oc->parse_order.products)
+ oc->merge_inventory.products
+ = json_deep_copy (oc->parse_order.products);
+ else
+ oc->merge_inventory.products
+ = json_array ();
/* Populate products from inventory product array and database */
{
- json_t *np = json_array ();
-
- for (unsigned int i = 0; i<inventory_products_length; i++)
+ GNUNET_assert (NULL != oc->merge_inventory.products);
+ for (unsigned int i = 0; i<oc->parse_request.inventory_products_length; i++)
{
+ const struct InventoryProduct *ip
+ = &oc->parse_request.inventory_products[i];
struct TALER_MERCHANTDB_ProductDetails pd;
enum GNUNET_DB_QueryStatus qs;
qs = TMH_db->lookup_product (TMH_db->cls,
- hc->instance->settings.id,
- inventory_products[i].product_id,
+ oc->hc->instance->settings.id,
+ ip->product_id,
&pd);
if (qs <= 0)
{
@@ -1353,7 +2616,7 @@ merge_inventory (struct MHD_Connection *connection,
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Product %s from order unknown\n",
- inventory_products[i].product_id);
+ ip->product_id);
http_status = MHD_HTTP_NOT_FOUND;
ec = TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN;
break;
@@ -1361,12 +2624,16 @@ merge_inventory (struct MHD_Connection *connection,
/* case listed to make compilers happy */
GNUNET_assert (0);
}
- json_decref (np);
- return TALER_MHD_reply_with_error (connection,
- http_status,
- ec,
- inventory_products[i].product_id);
+ json_decref (oc->merge_inventory.products);
+ reply_with_error (oc,
+ http_status,
+ ec,
+ ip->product_id);
+ return;
}
+ oc->parse_order.minimum_age
+ = GNUNET_MAX (oc->parse_order.minimum_age,
+ pd.minimum_age);
{
json_t *p;
@@ -1383,12 +2650,12 @@ merge_inventory (struct MHD_Connection *connection,
pd.taxes),
GNUNET_JSON_pack_string ("image",
pd.image),
- GNUNET_JSON_pack_uint64 ("quantity",
- inventory_products[i].
- quantity));
+ GNUNET_JSON_pack_uint64 (
+ "quantity",
+ ip->quantity));
GNUNET_assert (NULL != p);
GNUNET_assert (0 ==
- json_array_append_new (np,
+ json_array_append_new (oc->merge_inventory.products,
p));
}
GNUNET_free (pd.description);
@@ -1396,160 +2663,181 @@ merge_inventory (struct MHD_Connection *connection,
GNUNET_free (pd.image);
json_decref (pd.address);
}
- /* merge into existing products list */
- {
- json_t *xp;
-
- xp = json_object_get (order,
- "products");
- GNUNET_assert (NULL != xp);
- json_array_extend (xp, np);
- json_decref (np);
- }
}
- return add_payment_details (connection,
- hc,
- h_post_data,
- order,
- claim_token,
- refund_delay,
- payment_target,
- inventory_products_length,
- inventory_products,
- uuids_length,
- uuids);
+ /* check if final product list is well-formed */
+ if (! TMH_products_array_valid (oc->merge_inventory.products))
+ {
+ GNUNET_break_op (0);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "order:products");
+ return;
+ }
+ oc->phase++;
}
/**
- * Generate an order. We add the fields 'exchanges', 'merchant_pub', and
- * 'H_wire' to the order gotten from the frontend, as well as possibly other
- * fields if the frontend did not provide them. Returns the order_id.
+ * Parse the client request. Upon success,
+ * continue processing by calling parse_order().
*
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param[in,out] hc context with further information about the request
- * @return MHD result code
+ * @param[in,out] oc order context to process
*/
-MHD_RESULT
-TMH_private_post_orders (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
+static void
+parse_request (struct OrderContext *oc)
{
- json_t *order;
- struct GNUNET_TIME_Relative refund_delay = GNUNET_TIME_UNIT_ZERO;
- const char *payment_target = NULL;
- json_t *ip = NULL;
- unsigned int ips_len = 0;
- struct InventoryProduct *ips = NULL;
- unsigned int uuids_len = 0;
- json_t *uuid;
- struct GNUNET_Uuid *uuids = NULL;
- struct TALER_ClaimTokenP claim_token;
+ const json_t *ip = NULL;
+ const json_t *uuid = NULL;
+ const char *otp_id = NULL;
bool create_token = true; /* default */
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_json ("order",
- &order),
+ &oc->parse_request.order),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_relative_time ("refund_delay",
- &refund_delay),
+ &oc->parse_request.refund_delay),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("payment_target",
- &payment_target),
+ &oc->parse_request.payment_target),
NULL),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("inventory_products",
- &ip),
+ GNUNET_JSON_spec_array_const ("inventory_products",
+ &ip),
NULL),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("lock_uuids",
- &uuid),
+ GNUNET_JSON_spec_string ("session_id",
+ &oc->parse_request.session_id),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("lock_uuids",
+ &uuid),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_bool ("create_token",
&create_token),
NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("otp_id",
+ &otp_id),
+ NULL),
GNUNET_JSON_spec_end ()
};
enum GNUNET_GenericReturnValue ret;
- struct TALER_MerchantPostDataHashP h_post_data;
- (void) rh;
- ret = TALER_MHD_parse_json_data (connection,
- hc->request_body,
+ ret = TALER_MHD_parse_json_data (oc->connection,
+ oc->hc->request_body,
spec);
if (GNUNET_OK != ret)
- return (GNUNET_NO == ret)
- ? MHD_YES
- : MHD_NO;
-
+ {
+ GNUNET_break_op (0);
+ finalize_order2 (oc,
+ ret);
+ return;
+ }
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Refund delay is %s\n",
- GNUNET_TIME_relative2s (refund_delay,
+ GNUNET_TIME_relative2s (oc->parse_request.refund_delay,
false));
-
TMH_db->expire_locks (TMH_db->cls);
- if (create_token)
+ if (NULL != otp_id)
{
- GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
- &claim_token,
- sizeof (claim_token));
+ struct TALER_MERCHANTDB_OtpDeviceDetails td;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TMH_db->select_otp (TMH_db->cls,
+ oc->hc->instance->settings.id,
+ otp_id,
+ &td);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ reply_with_error (oc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_otp");
+ return;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ reply_with_error (oc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ "select_otp");
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ reply_with_error (oc,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_OTP_DEVICE_UNKNOWN,
+ otp_id);
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ oc->parse_request.pos_key = td.otp_key;
+ oc->parse_request.pos_algorithm = td.otp_algorithm;
}
- else
+ if (create_token)
{
- /* we use all-zeros for 'no token' */
- memset (&claim_token,
- 0,
- sizeof (claim_token));
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &oc->parse_request.claim_token,
+ sizeof (oc->parse_request.claim_token));
}
-
/* Compute h_post_data (for idempotency check) */
{
char *req_body_enc;
/* Dump normalized JSON to string. */
- if (NULL == (req_body_enc = json_dumps (hc->request_body,
- JSON_ENCODE_ANY
- | JSON_COMPACT
- | JSON_SORT_KEYS)))
+ if (NULL == (req_body_enc
+ = json_dumps (oc->hc->request_body,
+ JSON_ENCODE_ANY
+ | JSON_COMPACT
+ | JSON_SORT_KEYS)))
{
GNUNET_break (0);
GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_ALLOCATION_FAILURE,
- "request body normalization for hashing");
+ reply_with_error (oc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_ALLOCATION_FAILURE,
+ "request body normalization for hashing");
+ return;
}
GNUNET_CRYPTO_hash (req_body_enc,
strlen (req_body_enc),
- &h_post_data.hash);
+ &oc->parse_request.h_post_data.hash);
GNUNET_free (req_body_enc);
}
/* parse the inventory_products (optionally given) */
if (NULL != ip)
{
- if (! json_is_array (ip))
+ unsigned int ipl = (unsigned int) json_array_size (ip);
+
+ if ( (json_array_size (ip) != (size_t) ipl) ||
+ (ipl > MAX_PRODUCTS) )
{
+ GNUNET_break (0);
GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "inventory_products");
+ reply_with_error (oc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_ALLOCATION_FAILURE,
+ "inventory products too long");
+ return;
}
- GNUNET_array_grow (ips,
- ips_len,
- json_array_size (ip));
- for (unsigned int i = 0; i<ips_len; i++)
+ GNUNET_array_grow (oc->parse_request.inventory_products,
+ oc->parse_request.inventory_products_length,
+ (unsigned int) json_array_size (ip));
+ for (unsigned int i = 0; i<oc->parse_request.inventory_products_length; i++)
{
+ struct InventoryProduct *ipr = &oc->parse_request.inventory_products[i];
const char *error_name;
unsigned int error_line;
struct GNUNET_JSON_Specification ispec[] = {
GNUNET_JSON_spec_string ("product_id",
- &ips[i].product_id),
+ &ipr->product_id),
GNUNET_JSON_spec_uint32 ("quantity",
- &ips[i].quantity),
+ &ipr->quantity),
GNUNET_JSON_spec_end ()
};
@@ -1561,19 +2849,16 @@ TMH_private_post_orders (const struct TMH_RequestHandler *rh,
if (GNUNET_OK != ret)
{
GNUNET_break_op (0);
- GNUNET_array_grow (ips,
- ips_len,
- 0);
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Product parsing failed at #%u: %s:%u\n",
i,
error_name,
error_line);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "inventory_products");
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "inventory_products");
+ return;
}
}
}
@@ -1581,21 +2866,10 @@ TMH_private_post_orders (const struct TMH_RequestHandler *rh,
/* parse the lock_uuids (optionally given) */
if (NULL != uuid)
{
- if (! json_is_array (uuid))
- {
- GNUNET_array_grow (ips,
- ips_len,
- 0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "lock_uuids");
- }
- GNUNET_array_grow (uuids,
- uuids_len,
+ GNUNET_array_grow (oc->parse_request.uuids,
+ oc->parse_request.uuids_length,
json_array_size (uuid));
- for (unsigned int i = 0; i<uuids_len; i++)
+ for (unsigned int i = 0; i<oc->parse_request.uuids_length; i++)
{
json_t *ui = json_array_get (uuid,
i);
@@ -1603,49 +2877,89 @@ TMH_private_post_orders (const struct TMH_RequestHandler *rh,
if (! json_is_string (ui))
{
GNUNET_break_op (0);
- GNUNET_array_grow (ips,
- ips_len,
- 0);
- GNUNET_array_grow (uuids,
- uuids_len,
- 0);
- GNUNET_JSON_parse_free (spec);
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"UUID parsing failed at #%u\n",
i);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "lock_uuids");
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "lock_uuids");
+ return;
}
TMH_uuid_from_string (json_string_value (ui),
- &uuids[i]);
+ &oc->parse_request.uuids[i]);
}
}
+ oc->phase++;
+}
- /* Finally, start by completing the order */
- {
- MHD_RESULT res;
- res = merge_inventory (connection,
- hc,
- &h_post_data,
- order,
- &claim_token,
- refund_delay,
- payment_target,
- ips_len,
- ips,
- uuids_len,
- uuids);
- GNUNET_array_grow (ips,
- ips_len,
- 0);
- GNUNET_array_grow (uuids,
- uuids_len,
- 0);
- GNUNET_JSON_parse_free (spec);
- return res;
+MHD_RESULT
+TMH_private_post_orders (
+ const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct OrderContext *oc = hc->ctx;
+
+ if (NULL == oc)
+ {
+ oc = GNUNET_new (struct OrderContext);
+ hc->ctx = oc;
+ hc->cc = &clean_order;
+ oc->connection = connection;
+ oc->hc = hc;
+ }
+ while (1)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Processing order in phase %d\n",
+ oc->phase);
+ switch (oc->phase)
+ {
+ case ORDER_PHASE_PARSE_REQUEST:
+ parse_request (oc);
+ break;
+ case ORDER_PHASE_PARSE_ORDER:
+ parse_order (oc);
+ break;
+ case ORDER_PHASE_PARSE_CHOICES:
+ parse_choices (oc);
+ break;
+ case ORDER_PHASE_MERGE_INVENTORY:
+ merge_inventory (oc);
+ break;
+ case ORDER_PHASE_ADD_PAYMENT_DETAILS:
+ add_payment_details (oc);
+ break;
+ case ORDER_PHASE_SET_EXCHANGES:
+ if (set_exchanges (oc))
+ return MHD_YES;
+ break;
+ case ORDER_PHASE_SET_MAX_FEE:
+ set_max_fee (oc);
+ break;
+ case ORDER_PHASE_SERIALIZE_ORDER:
+ serialize_order (oc);
+ break;
+ case ORDER_PHASE_CHECK_CONTRACT:
+ check_contract (oc);
+ break;
+ case ORDER_PHASE_SALT_FORGETTABLE:
+ salt_forgettable (oc);
+ break;
+ case ORDER_PHASE_EXECUTE_ORDER:
+ execute_order (oc);
+ break;
+ case ORDER_PHASE_FINISHED_MHD_YES:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Finished processing order (1)\n");
+ return MHD_YES;
+ case ORDER_PHASE_FINISHED_MHD_NO:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Finished processing order (0)\n");
+ return MHD_NO;
+ }
}
}