diff options
Diffstat (limited to 'src/backend/taler-merchant-httpd_private-post-orders.c')
-rw-r--r-- | src/backend/taler-merchant-httpd_private-post-orders.c | 1377 |
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, ×tamp, &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; + } } } |