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 | 261 |
1 files changed, 115 insertions, 146 deletions
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c index 172cb05c..55fd42b3 100644 --- a/src/backend/taler-merchant-httpd_private-post-orders.c +++ b/src/backend/taler-merchant-httpd_private-post-orders.c @@ -58,8 +58,8 @@ * @param products JSON array to check * @return #GNUNET_OK if all is fine */ -static int -check_products (json_t *products) +static enum GNUNET_GenericReturnValue +check_products (const json_t *products) { size_t index; json_t *value; @@ -73,9 +73,12 @@ check_products (json_t *products) const char *description; const char *error_name; unsigned int error_line; - int res; + enum GNUNET_GenericReturnValue res; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("description", &description), + // FIXME: parse and format-validate all + // optional fields of a product and check validity + GNUNET_JSON_spec_string ("description", + &description), GNUNET_JSON_spec_end () }; @@ -122,7 +125,7 @@ make_merchant_base_url (struct MHD_Connection *connection, GNUNET_buffer_write_str (&buf, "http://"); host = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, - "Host"); + MHD_HTTP_HEADER_HOST); forwarded_host = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, "X-Forwarded-Host"); @@ -178,9 +181,6 @@ struct InventoryProduct }; -#define PRODUCT_OOS_OFFSET -3 - - /** * Execute the database transaction to setup the order. * @@ -194,6 +194,7 @@ struct InventoryProduct * @param inventory_products array of products to add to @a order from our inventory * @param uuids_length length of the @a uuids array * @param uuids array of UUIDs used to reserve products from @a inventory_products + * @param[out] out_of_stock_index which product (by offset) is out of stock, UINT_MAX if all were in-stock * @return transaction status, 0 if @a uuids were insufficient to reserve required inventory */ static enum GNUNET_DB_QueryStatus @@ -201,12 +202,13 @@ execute_transaction (struct TMH_HandlerContext *hc, const char *order_id, const struct GNUNET_HashCode *h_post_data, struct GNUNET_TIME_Absolute pay_deadline, - json_t *order, + 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[]) + const struct GNUNET_Uuid uuids[], + unsigned int *out_of_stock_index) { enum GNUNET_DB_QueryStatus qs; struct GNUNET_TIME_Absolute timestamp; @@ -219,17 +221,6 @@ execute_transaction (struct TMH_HandlerContext *hc, GNUNET_break (0); return GNUNET_DB_STATUS_HARD_ERROR; } - qs = TMH_db->lookup_order_summary (TMH_db->cls, - hc->instance->settings.id, - order_id, - ×tamp, - &order_serial); - if (0 != qs) - { - /* order already exists. */ - TMH_db->rollback (TMH_db->cls); - return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; - } /* Setup order */ qs = TMH_db->insert_order (TMH_db->cls, hc->instance->settings.id, @@ -238,11 +229,11 @@ execute_transaction (struct TMH_HandlerContext *hc, pay_deadline, claim_token, order); - /* qs == 0: probably instance does not exist. */ if (qs <= 0) { + /* qs == 0: probably instance does not exist (anymore) */ TMH_db->rollback (TMH_db->cls); - return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; + return qs; } /* Migrate locks from UUIDs to new order: first release old locks */ for (unsigned int i = 0; i<uuids_length; i++) @@ -268,15 +259,21 @@ execute_transaction (struct TMH_HandlerContext *hc, order_id, inventory_products[i].product_id, inventory_products[i].quantity); - if (qs <= 0) + if (qs < 0) { - /* qs == 0: lock acquisition failed due to insufficient stocks */ TMH_db->rollback (TMH_db->cls); - if (0 == qs) - qs = PRODUCT_OOS_OFFSET - i; /* indicate which product is causing the issue */ return qs; } + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + /* 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 */ + return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; + } } + *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, @@ -284,7 +281,7 @@ execute_transaction (struct TMH_HandlerContext *hc, order_id, ×tamp, &order_serial); - if (1 != qs) + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) { TMH_db->rollback (TMH_db->cls); return qs; @@ -292,24 +289,9 @@ execute_transaction (struct TMH_HandlerContext *hc, /* finally, commit transaction (note: if it fails, we ALSO re-acquire the UUID locks, which is exactly what we want) */ qs = TMH_db->commit (TMH_db->cls); - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) - { - /* Notify clients that have been waiting for the payment to succeed */ - TMH_long_poll_resume (order_id, - hc->instance, - NULL, - false); - TMH_notify_order_change (hc->instance, - order_id, - false, /* paid */ - false, /* refunded */ - false, /* wired */ - timestamp, - order_serial); - - return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; /* 1 == success! */ - } - return qs; + if (0 > qs) + return qs; + return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; /* 1 == success! */ } @@ -376,6 +358,7 @@ execute_order (struct MHD_Connection *connection, GNUNET_JSON_spec_end () }; enum GNUNET_DB_QueryStatus qs; + unsigned int out_of_stock_index; /* extract fields we need to sign separately */ { @@ -405,23 +388,7 @@ execute_order (struct MHD_Connection *connection, TMH_currency); } - if (wire_transfer_deadline.abs_value_us < - refund_deadline.abs_value_us) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "invariant failed: wire_transfer_deadline %llu >= refund_deadline %llu\n", - (unsigned long long) wire_transfer_deadline.abs_value_us, - (unsigned long long) refund_deadline.abs_value_us); - GNUNET_JSON_parse_free (spec); - return TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "order:wire_transfer_deadline;order:refund_deadline"); - } - - - /* check contract is well-formed */ + /* check product list in contract is well-formed */ if (GNUNET_OK != check_products (products)) { GNUNET_JSON_parse_free (spec); @@ -447,20 +414,24 @@ execute_order (struct MHD_Connection *connection, /* If yes, check for idempotency */ if (0 > qs) { + GNUNET_break (0); TMH_db->rollback (TMH_db->cls); GNUNET_JSON_parse_free (spec); - return qs; + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "lookup_order"); } if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) { + MHD_RESULT ret; + json_decref (contract_terms); /* Comparing the contract terms is sufficient because all the other params get added to it at some point. */ if (0 == GNUNET_memcmp (&orig_post, h_post_data)) { - MHD_RESULT ret; - ret = TALER_MHD_reply_json_pack ( connection, MHD_HTTP_OK, @@ -471,22 +442,18 @@ execute_order (struct MHD_Connection *connection, (GNUNET_YES == GNUNET_is_zero (&token)) ? NULL : GNUNET_JSON_from_data_auto (&token)); - GNUNET_JSON_parse_free (spec); - return ret; } else { /* This request is not idempotent */ - MHD_RESULT ret; - ret = TALER_MHD_reply_with_error ( connection, MHD_HTTP_CONFLICT, TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS, order_id); - GNUNET_JSON_parse_free (spec); - return ret; } + GNUNET_JSON_parse_free (spec); + return ret; } } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, @@ -505,23 +472,23 @@ execute_order (struct MHD_Connection *connection, inventory_products_length, inventory_products, uuids_length, - uuids); + uuids, + &out_of_stock_index); if (GNUNET_DB_STATUS_SOFT_ERROR != qs) break; } if (0 >= qs) { + GNUNET_JSON_parse_free (spec); /* Special report if retries insufficient */ if (GNUNET_DB_STATUS_SOFT_ERROR == qs) { GNUNET_break (0); - GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_SOFT_FAILURE, NULL); } - if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) { /* should be: contract (!) with same order ID @@ -532,83 +499,83 @@ execute_order (struct MHD_Connection *connection, TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_ALREADY_EXISTS, order_id); } - - /* If we have a product that has insufficient quantities, - generate the details for the response. */ - if (PRODUCT_OOS_OFFSET >= qs) - { - unsigned int i = -qs + PRODUCT_OOS_OFFSET; - struct TALER_MERCHANTDB_ProductDetails pd; - MHD_RESULT ret; - - memset (&pd, 0, sizeof (pd)); - qs = TMH_db->lookup_product (TMH_db->cls, - hc->instance->settings.id, - inventory_products[i].product_id, - &pd); - GNUNET_JSON_parse_free (spec); - switch (qs) - { - case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: - ret = TALER_MHD_reply_json_pack (connection, - MHD_HTTP_GONE, - "{s:s,s:I,s:I,s:o?}", - "product_id", - inventory_products[i].product_id, - "requested_quantity", - inventory_products[i].quantity, - "available_quantity", - pd.total_stock - pd.total_sold - - pd.total_lost, - "restock_expected", - (pd.next_restock.abs_value_us == 0) ? - NULL : - GNUNET_JSON_from_time_abs ( - pd.next_restock)); - TALER_MERCHANTDB_product_details_free (&pd); - break; - case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: - ret = TALER_MHD_reply_json_pack (connection, - MHD_HTTP_GONE, - "{s:s,s:I,s:I}", - "product_id", - inventory_products[i].product_id, - "requested_quantity", - inventory_products[i].quantity, - "available_quantity", - (json_int_t) 0); - break; - case GNUNET_DB_STATUS_SOFT_ERROR: - GNUNET_break (0); - ret = TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_SOFT_FAILURE, - NULL); - break; - case GNUNET_DB_STATUS_HARD_ERROR: - ret = TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_GENERIC_DB_FETCH_FAILED, - NULL); - break; - default: - GNUNET_break (0); - return MHD_NO; - } - return ret; - } - /* Other hard transaction error (disk full, etc.) */ - GNUNET_JSON_parse_free (spec); + GNUNET_break (0); return TALER_MHD_reply_with_error ( connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_COMMIT_FAILED, NULL); } - /* DB transaction succeeded, generate positive response */ + + /* DB transaction succeeded, check for out-of-stock */ + if (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)); + qs = TMH_db->lookup_product ( + TMH_db->cls, + hc->instance->settings.id, + inventory_products[out_of_stock_index].product_id, + &pd); + GNUNET_JSON_parse_free (spec); + switch (qs) + { + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + ret = TALER_MHD_reply_json_pack (connection, + MHD_HTTP_GONE, + "{s:s,s:I,s:I,s:o?}", + "product_id", + inventory_products[out_of_stock_index]. + product_id, + "requested_quantity", + inventory_products[out_of_stock_index]. + quantity, + "available_quantity", + pd.total_stock - pd.total_sold + - pd.total_lost, + "restock_expected", + (0 == pd.next_restock.abs_value_us) + ? NULL + : GNUNET_JSON_from_time_abs ( + pd.next_restock)); + TALER_MERCHANTDB_product_details_free (&pd); + return ret; + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + return TALER_MHD_reply_json_pack (connection, + MHD_HTTP_GONE, + "{s:s,s:I,s:I}", + "product_id", + inventory_products[out_of_stock_index]. + product_id, + "requested_quantity", + inventory_products[out_of_stock_index]. + quantity, + "available_quantity", + (json_int_t) 0); + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_SOFT_FAILURE, + NULL); + case GNUNET_DB_STATUS_HARD_ERROR: + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + NULL); + } + GNUNET_break (0); + return MHD_NO; + } + + /* Everything in-stock, generate positive response */ { MHD_RESULT ret; @@ -912,10 +879,12 @@ patch_order (struct MHD_Connection *connection, connection, MHD_HTTP_BAD_REQUEST, TALER_EC_MERCHANT_PRIVATE_POST_ORDERS_REFUND_AFTER_WIRE_DEADLINE, - NULL); + "order:wire_transfer_deadline;order:refund_deadline"); } + /* Note: total amount currency match checked + later in execute_order() */ if (GNUNET_OK != TALER_amount_is_valid (&max_wire_fee)) { |