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.c261
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,
- &timestamp,
- &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,
&timestamp,
&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))
{