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.c1377
1 files changed, 672 insertions, 705 deletions
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c
index e75049d0..718909d3 100644
--- a/src/backend/taler-merchant-httpd_private-post-orders.c
+++ b/src/backend/taler-merchant-httpd_private-post-orders.c
@@ -28,7 +28,6 @@
#include <taler/taler_signatures.h>
#include <taler/taler_json_lib.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_helper.h"
#include "taler-merchant-httpd_private-get-orders.h"
@@ -53,109 +52,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 +62,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 };
-
- 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);
- }
+ struct GNUNET_Buffer buf;
+
+ 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,37 +94,218 @@ struct InventoryProduct
/**
+ * Information we keep per order we are processing.
+ */
+struct OrderContext
+{
+
+ /**
+ * Connection of the request.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Handler context for the request.
+ */
+ struct TMH_HandlerContext *hc;
+
+ /**
+ * Hash of the POST request data, used to detect
+ * idempotent requests.
+ */
+ struct TALER_MerchantPostDataHashP h_post_data;
+
+ /**
+ * Payment deadline.
+ */
+ struct GNUNET_TIME_Timestamp pay_deadline;
+
+ /**
+ * Set to how long refunds will be allowed.
+ */
+ struct GNUNET_TIME_Relative refund_delay;
+
+ /**
+ * Order we are building (modified as we process
+ * the request).
+ */
+ json_t *order;
+
+ /**
+ * RFC8905 payment target type to find a matching merchant account
+ */
+ const char *payment_target;
+
+ /**
+ * Wire method (and our bank account) we have selected
+ * to be included for this order.
+ */
+ const struct TMH_WireMethod *wm;
+
+ /**
+ * Claim token for the request.
+ */
+ struct TALER_ClaimTokenP claim_token;
+
+ /**
+ * Length of the @e inventory_products array.
+ */
+ unsigned int inventory_products_length;
+
+ /**
+ * Array of inventory products in the @e order.
+ */
+ 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;
+
+ /**
+ * which product (by offset) is out of stock, UINT_MAX if all were in-stock
+ */
+ unsigned int out_of_stock_index;
+
+ /**
+ * Shared key to use with @e pos_algorithm.
+ */
+ char *pos_key;
+
+ /**
+ * Our order ID. Pointer into @e order.
+ */
+ const char *order_id;
+
+ /**
+ * Selected algorithm (by template) when we are to
+ * generate an OTP code for payment confirmation.
+ */
+ enum TALER_MerchantConfirmationAlgorithm pos_algorithm;
+
+ /**
+ * Current phase of setting up the order.
+ */
+ enum
+ {
+ ORDER_PHASE_INIT,
+ ORDER_PHASE_MERGE_INVENTORY,
+ ORDER_PHASE_ADD_PAYMENT_DETAILS,
+ ORDER_PHASE_PATCH_ORDER,
+ ORDER_PHASE_SET_EXCHANGES,
+ 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;
+
+};
+
+
+/**
+ * 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;
+
+ GNUNET_array_grow (oc->inventory_products,
+ oc->inventory_products_length,
+ 0);
+ GNUNET_array_grow (oc->uuids,
+ oc->uuids_length,
+ 0);
+ json_decref (oc->order);
+ GNUNET_free (oc->pos_key);
+ 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
- * @param pos_key shared secret with the POS
- * @param pos_algorithm algorithm for payment confirmation generation
- * @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,
- const char *pos_key,
- enum TALER_MerchantConfirmationAlgorithm pos_algorithm)
+execute_transaction (struct OrderContext *oc)
{
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_TIME_Timestamp timestamp;
@@ -276,14 +320,14 @@ execute_transaction (struct TMH_HandlerContext *hc,
}
/* 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. */
- pos_key,
- pos_algorithm);
+ oc->hc->instance->settings.id,
+ oc->order_id,
+ &oc->h_post_data,
+ oc->pay_deadline,
+ &oc->claim_token,
+ oc->order, /* called 'contract terms' at database. */
+ oc->pos_key,
+ oc->pos_algorithm);
if (qs <= 0)
{
/* qs == 0: probably instance does not exist (anymore) */
@@ -291,10 +335,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->uuids_length; i++)
{
qs = TMH_db->unlock_inventory (TMH_db->cls,
- &uuids[i]);
+ &oc->uuids[i]);
if (qs < 0)
{
TMH_db->rollback (TMH_db->cls);
@@ -307,13 +351,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->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->order_id,
+ oc->inventory_products[i].product_id,
+ oc->inventory_products[i].quantity);
if (qs < 0)
{
TMH_db->rollback (TMH_db->cls);
@@ -323,17 +368,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->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->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->order_id,
&timestamp,
&order_serial);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
@@ -341,7 +386,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);
@@ -359,36 +404,14 @@ 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
- * @param pos_key encoded key for verification payment
- * @param pos_algorithm algorithm to compute the payment verification
- * @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[],
- const char *pos_key,
- enum TALER_MerchantConfirmationAlgorithm pos_algorithm)
+static void
+execute_order (struct OrderContext *oc)
{
const struct TALER_MERCHANTDB_InstanceSettings *settings =
- &hc->instance->settings;
+ &oc->hc->instance->settings;
struct TALER_Amount total;
- const char *order_id;
const char *summary;
const char *fulfillment_msg = NULL;
json_t *products;
@@ -398,13 +421,12 @@ execute_order (struct MHD_Connection *connection,
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),
+ &oc->order_id),
GNUNET_JSON_spec_string ("summary",
&summary),
/**
@@ -433,57 +455,62 @@ execute_order (struct MHD_Connection *connection,
&refund_deadline),
NULL),
GNUNET_JSON_spec_timestamp ("pay_deadline",
- &pay_deadline),
+ &oc->pay_deadline),
GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
&wire_transfer_deadline),
GNUNET_JSON_spec_end ()
};
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,
+ res = TALER_MHD_parse_json_data (oc->connection,
+ oc->order,
spec);
if (GNUNET_OK != res)
{
GNUNET_break_op (0);
- return (GNUNET_NO == res)
- ? MHD_YES
- : MHD_NO;
+ finalize_order2 (oc,
+ res);
}
}
/* check product list in contract is well-formed */
- if (GNUNET_OK != check_products (products))
+ if (! TMH_products_array_valid (products))
{
GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "order:products");
+ GNUNET_break_op (0);
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "order:products");
+ return;
}
+ // FIXME: we can do this better now...
if ( (NULL != fulfillment_i18n) &&
(! TALER_JSON_check_i18n (fulfillment_i18n)) )
{
+ 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,
- "order:fulfillment_message_i18n");
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "order:fulfillment_message_i18n");
+ return;
}
if ( (NULL != summary_i18n) &&
(! TALER_JSON_check_i18n (summary_i18n)) )
{
+ 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,
- "order:summary_i18n");
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "order:summary_i18n");
+ return;
}
/* Test if we already have an order with this id */
@@ -494,8 +521,8 @@ execute_order (struct MHD_Connection *connection,
TMH_db->preflight (TMH_db->cls);
qs = TMH_db->lookup_order (TMH_db->cls,
- hc->instance->settings.id,
- order_id,
+ oc->hc->instance->settings.id,
+ oc->order_id,
&token,
&orig_post,
&contract_terms);
@@ -505,10 +532,11 @@ execute_order (struct MHD_Connection *connection,
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");
+ reply_with_error (oc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_order");
+ return;
}
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
{
@@ -518,13 +546,15 @@ execute_order (struct MHD_Connection *connection,
/* 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))
+ &oc->h_post_data))
{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order creation idempotent\n");
ret = TALER_MHD_REPLY_JSON_PACK (
- connection,
+ oc->connection,
MHD_HTTP_OK,
GNUNET_JSON_pack_string ("order_id",
- order_id),
+ oc->order_id),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_data_varsize (
"token",
@@ -532,40 +562,29 @@ execute_order (struct MHD_Connection *connection,
? NULL
: &token,
sizeof (token))));
+ GNUNET_JSON_parse_free (spec);
+ finalize_order (oc,
+ ret);
+ return;
}
- 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;
+ /* This request is not idempotent */
+ GNUNET_break_op (0);
+ reply_with_error (
+ oc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS,
+ oc->order_id);
+ return;
}
}
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Executing database transaction to create order '%s' for instance '%s'\n",
- order_id,
+ oc->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,
- pos_key,
- pos_algorithm);
+ qs = execute_transaction (oc);
if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
break;
}
@@ -576,57 +595,64 @@ execute_order (struct MHD_Connection *connection,
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->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 out-of-stock */
- if (out_of_stock_index < UINT_MAX)
+ if (oc->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;
- memset (&pd, 0, sizeof (pd));
+ 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,
+ oc->inventory_products[oc->out_of_stock_index].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),
+ oc->inventory_products[oc->out_of_stock_index].product_id),
GNUNET_JSON_pack_uint64 (
"requested_quantity",
- inventory_products[out_of_stock_index].quantity),
+ oc->inventory_products[oc->out_of_stock_index].quantity),
GNUNET_JSON_pack_uint64 (
"available_quantity",
pd.total_stock - pd.total_sold - pd.total_lost),
@@ -635,57 +661,145 @@ 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",
+ oc->inventory_products[oc->out_of_stock_index].
+ product_id),
+ GNUNET_JSON_pack_uint64 (
+ "requested_quantity",
+ oc->inventory_products[oc->out_of_stock_index].
+ 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->order_id),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_data_varsize (
"token",
- GNUNET_is_zero (claim_token)
+ GNUNET_is_zero (&oc->claim_token)
? NULL
- : claim_token,
- sizeof (*claim_token))));
+ : &oc->claim_token,
+ sizeof (oc->claim_token))));
GNUNET_JSON_parse_free (spec);
- return ret;
+ finalize_order (oc,
+ ret);
+ }
+}
+
+
+/**
+ * Check that the contract is now well-formed.
+ *
+ * @param[in,out] oc order context
+ */
+static void
+check_contract (struct OrderContext *oc)
+{
+ struct TALER_PrivateContractHashP h_control;
+
+ switch (TALER_JSON_contract_hash (oc->order,
+ &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 patched 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:
+ oc->phase++;
+ return;
}
+ GNUNET_assert (0);
+}
+
+
+/**
+ * Set list of acceptable exchanges in @a oc.
+ *
+ * @param[in,out] oc order context
+ */
+static void
+set_exchanges (struct OrderContext *oc)
+{
+ json_t *exchanges;
+
+ exchanges = TMH_exchange_get_acceptable (oc->wm);
+ if (0 == json_array_size (exchanges))
+ {
+ json_decref (exchanges);
+ /* FIXME: maybe there are exchanges for which we simply
+ did not yet get /wire replies. We should *try* to
+ run find_exchange() or something to give those
+ exchanges a chance to respond => need to suspend! */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Cannot create order: lacking trusted exchanges for wire method `%s'\n",
+ oc->wm->wire_method);
+ reply_with_error (
+ oc,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_NO_EXCHANGES_FOR_WIRE_METHOD,
+ oc->wm->wire_method);
+ return;
+ }
+ GNUNET_assert (0 ==
+ json_object_set (oc->order,
+ "exchanges",
+ exchanges));
+ oc->phase++;
}
@@ -693,41 +807,18 @@ execute_order (struct MHD_Connection *connection,
* Add missing fields to the order. 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
- * @param pos_key encoded key for verification payment
- * @param pos_algorithm algorithm to compute the payment verification
- * @return MHD result code
+ * @param[in,out] oc order context
*/
-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[],
- const char *pos_key,
- enum TALER_MerchantConfirmationAlgorithm pos_algorithm)
+static void
+patch_order (struct OrderContext *oc)
{
const struct TALER_MERCHANTDB_InstanceSettings *settings =
- &hc->instance->settings;
+ &oc->hc->instance->settings;
const char *order_id = NULL;
const char *fulfillment_url = NULL;
const char *merchant_base_url = NULL;
- json_t *jmerchant = NULL;
- json_t *delivery_location = NULL;
+ const json_t *jmerchant = NULL;
+ const json_t *delivery_location = NULL;
struct TALER_Amount max_wire_fee = { 0 };
struct TALER_Amount max_fee = { 0 };
uint32_t wire_fee_amortization = 0;
@@ -737,8 +828,6 @@ patch_order (struct MHD_Connection *connection,
= 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;
/* auto_refund only needs to be type-checked,
@@ -751,8 +840,8 @@ patch_order (struct MHD_Connection *connection,
&merchant_base_url),
NULL),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("merchant",
- &jmerchant),
+ GNUNET_JSON_spec_object_const ("merchant",
+ &jmerchant),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("order_id",
@@ -772,7 +861,7 @@ patch_order (struct MHD_Connection *connection,
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_timestamp ("pay_deadline",
- &pay_deadline),
+ &oc->pay_deadline),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
@@ -801,22 +890,22 @@ patch_order (struct MHD_Connection *connection,
&auto_refund),
NULL),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_json ("delivery_location",
- &delivery_location),
+ GNUNET_JSON_spec_object_const ("delivery_location",
+ &delivery_location),
NULL),
GNUNET_JSON_spec_end ()
};
enum GNUNET_GenericReturnValue ret;
- ret = TALER_MHD_parse_json_data (connection,
- order,
+ ret = TALER_MHD_parse_json_data (oc->connection,
+ oc->order,
spec);
if (GNUNET_OK != ret)
{
GNUNET_break_op (0);
- return (GNUNET_NO == ret)
- ? MHD_YES
- : MHD_NO;
+ finalize_order2 (oc,
+ ret);
+ return;
}
/* Add order_id if it doesn't exist. */
@@ -835,8 +924,8 @@ patch_order (struct MHD_Connection *connection,
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);
@@ -862,7 +951,7 @@ patch_order (struct MHD_Connection *connection,
"Assigning order ID `%s' server-side\n",
buf);
GNUNET_break (0 ==
- json_object_set_new (order,
+ json_object_set_new (oc->order,
"order_id",
jbuf));
order_id = json_string_value (jbuf);
@@ -886,11 +975,11 @@ 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,
@@ -904,7 +993,7 @@ patch_order (struct MHD_Connection *connection,
pos + strlen ("${ORDER_ID}"));
/* replace in JSON of the order */
GNUNET_break (0 ==
- json_object_set_new (order,
+ json_object_set_new (oc->order,
"fulfillment_url",
json_string (nurl)));
GNUNET_free (nurl);
@@ -920,7 +1009,7 @@ patch_order (struct MHD_Connection *connection,
if (GNUNET_TIME_absolute_is_zero (timestamp.abs_time))
{
GNUNET_assert (0 ==
- json_object_set_new (order,
+ json_object_set_new (oc->order,
"timestamp",
GNUNET_JSON_from_timestamp (now)));
}
@@ -928,7 +1017,7 @@ patch_order (struct MHD_Connection *connection,
/* If no refund_deadline given, set one based on refund_delay. */
if (GNUNET_TIME_absolute_is_never (refund_deadline.abs_time))
{
- if (GNUNET_TIME_relative_is_zero (refund_delay))
+ if (GNUNET_TIME_relative_is_zero (oc->refund_delay))
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Refund delay is zero, no refunds are possible for this order\n");
@@ -936,10 +1025,10 @@ patch_order (struct MHD_Connection *connection,
}
else
{
- refund_deadline = GNUNET_TIME_relative_to_timestamp (refund_delay);
+ refund_deadline = GNUNET_TIME_relative_to_timestamp (oc->refund_delay);
}
GNUNET_assert (0 ==
- json_object_set_new (order,
+ json_object_set_new (oc->order,
"refund_deadline",
GNUNET_JSON_from_timestamp (
refund_deadline)));
@@ -948,45 +1037,45 @@ patch_order (struct MHD_Connection *connection,
(GNUNET_TIME_absolute_is_past (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->pay_deadline.abs_time))
{
- struct GNUNET_TIME_Timestamp t;
-
- t = GNUNET_TIME_relative_to_timestamp (settings->default_pay_delay);
+ oc->pay_deadline = GNUNET_TIME_relative_to_timestamp (
+ settings->default_pay_delay);
GNUNET_assert (0 ==
- json_object_set_new (order,
+ json_object_set_new (oc->order,
"pay_deadline",
- GNUNET_JSON_from_timestamp (t)));
+ GNUNET_JSON_from_timestamp (
+ oc->pay_deadline)));
}
- else if (GNUNET_TIME_absolute_is_past (pay_deadline.abs_time))
+ else if (GNUNET_TIME_absolute_is_past (oc->pay_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_PAY_DEADLINE_IN_PAST,
NULL);
+ return;
}
if ( (! GNUNET_TIME_absolute_is_zero (refund_deadline.abs_time)) &&
(GNUNET_TIME_absolute_is_past (refund_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_REFUND_DEADLINE_IN_PAST,
NULL);
+ return;
}
if (GNUNET_TIME_absolute_is_never (wire_deadline.abs_time))
@@ -995,22 +1084,21 @@ patch_order (struct MHD_Connection *connection,
t = GNUNET_TIME_relative_to_timestamp (
GNUNET_TIME_relative_max (settings->default_wire_transfer_delay,
- refund_delay));
+ oc->refund_delay));
wire_deadline = GNUNET_TIME_timestamp_max (refund_deadline,
t);
if (GNUNET_TIME_absolute_is_never (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,
+ json_object_set_new (oc->order,
"wire_transfer_deadline",
GNUNET_JSON_from_timestamp (
wire_deadline)));
@@ -1020,12 +1108,12 @@ patch_order (struct MHD_Connection *connection,
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
@@ -1035,7 +1123,7 @@ patch_order (struct MHD_Connection *connection,
{
GNUNET_assert (0 ==
json_object_set_new (
- order,
+ oc->order,
"max_wire_fee",
TALER_JSON_from_amount (&settings->default_max_wire_fee)));
}
@@ -1045,7 +1133,7 @@ patch_order (struct MHD_Connection *connection,
{
GNUNET_assert (0 ==
json_object_set_new (
- order,
+ oc->order,
"max_fee",
TALER_JSON_from_amount (
&settings->default_max_deposit_fee)));
@@ -1054,7 +1142,7 @@ patch_order (struct MHD_Connection *connection,
{
GNUNET_assert (0 ==
json_object_set_new (
- order,
+ oc->order,
"wire_fee_amortization",
json_integer (
(json_int_t) settings->default_wire_fee_amortization)));
@@ -1063,10 +1151,20 @@ patch_order (struct MHD_Connection *connection,
{
char *url;
- url = make_merchant_base_url (connection,
+ url = make_merchant_base_url (oc->connection,
settings->id);
+ 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;
+ }
GNUNET_assert (0 ==
- json_object_set_new (order,
+ json_object_set_new (oc->order,
"merchant_base_url",
json_string (url)));
GNUNET_free (url);
@@ -1075,24 +1173,24 @@ patch_order (struct MHD_Connection *connection,
('/' != merchant_base_url[strlen (merchant_base_url) - 1]))
{
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");
+ 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;
}
{
@@ -1140,91 +1238,39 @@ patch_order (struct MHD_Connection *connection,
}
}
GNUNET_assert (0 ==
- json_object_set_new (order,
+ json_object_set_new (oc->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,
+ json_object_set_new (oc->order,
"merchant_pub",
GNUNET_JSON_from_data_auto (
- &hc->instance->merchant_pub)));
+ &oc->hc->instance->merchant_pub)));
+
if (GNUNET_OK !=
- TALER_JSON_contract_seed_forgettable (order))
+ TALER_JSON_contract_seed_forgettable (oc->order))
{
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (
- connection,
+ 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;
}
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");
- }
-
- /* sanity check result */
- {
- struct TALER_PrivateContractHashP h_control;
-
- 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;
- }
- }
- {
- MHD_RESULT mres;
-
- mres = execute_order (connection,
- hc,
- h_post_data,
- order,
- claim_token,
- inventory_products_length,
- inventory_products,
- uuids_length,
- uuids,
- pos_key,
- pos_algorithm);
- GNUNET_JSON_parse_free (spec);
- return mres;
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "delivery_location");
+ return;
}
+ oc->phase++;
}
@@ -1233,77 +1279,44 @@ patch_order (struct MHD_Connection *connection,
* order could be paid to @a order. On success, continue
* processing with patch_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 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
- * @param pos_key encoded key for verification payment
- * @param pos_algorithm algorithm to compute the payment verification
+ * @param[in,out] oc order context
* @return MHD result code
*/
-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[],
- const char *pos_key,
- enum TALER_MerchantConfirmationAlgorithm pos_algorithm)
+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->payment_target) &&
+ (0 != strcasecmp (oc->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->payment_target);
+ return;
}
+ oc->wm = wm;
GNUNET_assert (0 ==
- json_object_set_new (order,
+ json_object_set_new (oc->order,
"h_wire",
GNUNET_JSON_from_data_auto (
&wm->h_wire)));
GNUNET_assert (0 ==
- json_object_set_new (order,
+ json_object_set_new (oc->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,
- pos_key,
- pos_algorithm);
+ oc->phase++;
}
@@ -1312,57 +1325,32 @@ add_payment_details (struct MHD_Connection *connection,
* 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
- * @param pos_key encoded key for verification payment
- * @param pos_algorithm algorithm to compute the payment verification
- * @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[],
- const char *pos_key,
- enum TALER_MerchantConfirmationAlgorithm pos_algorithm)
+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.
*/
- GNUNET_assert (NULL != order);
{
- json_t *jprod = json_object_get (order,
+ json_t *jprod = json_object_get (oc->order,
"products");
if (NULL == jprod)
{
GNUNET_assert (0 ==
- json_object_set_new (order,
+ json_object_set_new (oc->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");
+ reply_with_error (oc,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "order.products");
+ return;
}
}
@@ -1370,14 +1358,15 @@ merge_inventory (struct MHD_Connection *connection,
{
json_t *np = json_array ();
- for (unsigned int i = 0; i<inventory_products_length; i++)
+ GNUNET_assert (NULL != np);
+ for (unsigned int i = 0; i<oc->inventory_products_length; 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,
+ oc->inventory_products[i].product_id,
&pd);
if (qs <= 0)
{
@@ -1399,7 +1388,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);
+ oc->inventory_products[i].product_id);
http_status = MHD_HTTP_NOT_FOUND;
ec = TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN;
break;
@@ -1408,10 +1397,11 @@ merge_inventory (struct MHD_Connection *connection,
GNUNET_assert (0);
}
json_decref (np);
- return TALER_MHD_reply_with_error (connection,
- http_status,
- ec,
- inventory_products[i].product_id);
+ reply_with_error (oc,
+ http_status,
+ ec,
+ oc->inventory_products[i].product_id);
+ return;
}
{
json_t *p;
@@ -1429,9 +1419,9 @@ 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",
+ oc->inventory_products[i].quantity));
GNUNET_assert (NULL != p);
GNUNET_assert (0 ==
json_array_append_new (np,
@@ -1446,66 +1436,41 @@ merge_inventory (struct MHD_Connection *connection,
{
json_t *xp;
- xp = json_object_get (order,
+ xp = json_object_get (oc->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,
- pos_key,
- pos_algorithm);
+ oc->phase++;
}
-MHD_RESULT
-TMH_private_post_orders_with_pos_secrets (
- const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc,
- const char *pos_key,
- enum TALER_MerchantConfirmationAlgorithm pos_algorithm)
+static void
+parse_order_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;
bool create_token = true; /* default */
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_json ("order",
- &order),
+ &oc->order),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_relative_time ("refund_delay",
- &refund_delay),
+ &oc->refund_delay),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("payment_target",
- &payment_target),
+ &oc->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_array_const ("lock_uuids",
+ &uuid),
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_bool ("create_token",
@@ -1514,83 +1479,69 @@ TMH_private_post_orders_with_pos_secrets (
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->refund_delay,
false));
-
TMH_db->expire_locks (TMH_db->cls);
if (create_token)
{
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
- &claim_token,
- sizeof (claim_token));
- }
- else
- {
- /* we use all-zeros for 'no token' */
- memset (&claim_token,
- 0,
- sizeof (claim_token));
+ &oc->claim_token,
+ sizeof (oc->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->h_post_data.hash);
GNUNET_free (req_body_enc);
}
/* parse the inventory_products (optionally given) */
if (NULL != ip)
{
- if (! json_is_array (ip))
- {
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "inventory_products");
- }
- GNUNET_array_grow (ips,
- ips_len,
+ GNUNET_array_grow (oc->inventory_products,
+ oc->inventory_products_length,
json_array_size (ip));
- for (unsigned int i = 0; i<ips_len; i++)
+ for (unsigned int i = 0; i<oc->inventory_products_length; i++)
{
+ struct InventoryProduct *ipr = &oc->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 ()
};
@@ -1602,19 +1553,16 @@ TMH_private_post_orders_with_pos_secrets (
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;
}
}
}
@@ -1622,21 +1570,10 @@ TMH_private_post_orders_with_pos_secrets (
/* 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->uuids,
+ oc->uuids_length,
json_array_size (uuid));
- for (unsigned int i = 0; i<uuids_len; i++)
+ for (unsigned int i = 0; i<oc->uuids_length; i++)
{
json_t *ui = json_array_get (uuid,
i);
@@ -1644,51 +1581,81 @@ TMH_private_post_orders_with_pos_secrets (
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->uuids[i]);
}
}
+ oc->phase++;
+}
- /* Finally, start by completing the order */
+
+MHD_RESULT
+TMH_private_post_orders_with_pos_secrets (
+ const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc,
+ const char *pos_key,
+ enum TALER_MerchantConfirmationAlgorithm pos_algorithm)
+{
+ struct OrderContext *oc = hc->ctx;
+
+ if (NULL == oc)
{
- MHD_RESULT res;
-
- res = merge_inventory (connection,
- hc,
- &h_post_data,
- order,
- &claim_token,
- refund_delay,
- payment_target,
- ips_len,
- ips,
- uuids_len,
- uuids,
- pos_key,
- pos_algorithm);
- GNUNET_array_grow (ips,
- ips_len,
- 0);
- GNUNET_array_grow (uuids,
- uuids_len,
- 0);
- GNUNET_JSON_parse_free (spec);
- return res;
+ oc = GNUNET_new (struct OrderContext);
+ hc->ctx = oc;
+ hc->cc = &clean_order;
+ oc->connection = connection;
+ oc->hc = hc;
+ if (NULL != pos_key)
+ oc->pos_key = GNUNET_strdup (pos_key);
+ oc->pos_algorithm = pos_algorithm;
+ }
+ while (1)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Processing order in phase %d\n",
+ oc->phase);
+ switch (oc->phase)
+ {
+ case ORDER_PHASE_INIT:
+ parse_order_request (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_PATCH_ORDER:
+ patch_order (oc);
+ break;
+ case ORDER_PHASE_SET_EXCHANGES:
+ set_exchanges (oc);
+ break;
+ case ORDER_PHASE_CHECK_CONTRACT:
+ check_contract (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;
+ }
}
}