[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[taler-merchant] 57/277: implement logic to complete POSTed /orders usin
From: |
gnunet |
Subject: |
[taler-merchant] 57/277: implement logic to complete POSTed /orders using inventory data |
Date: |
Sun, 05 Jul 2020 20:49:30 +0200 |
This is an automated email from the git hooks/post-receive script.
grothoff pushed a commit to branch master
in repository merchant.
commit f799df31e066a23a0df8f4d062470526710741dd
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Sun Apr 26 14:01:59 2020 +0200
implement logic to complete POSTed /orders using inventory data
---
.../taler-merchant-httpd_private-post-orders.c | 180 +++++++++++++++++++--
src/backenddb/merchant-0001.sql | 6 +-
src/backenddb/plugin_merchantdb_postgres.c | 104 +++++++++++-
src/include/taler_merchantdb_plugin.h | 37 ++++-
4 files changed, 309 insertions(+), 18 deletions(-)
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c
b/src/backend/taler-merchant-httpd_private-post-orders.c
index e871f9f..af75999 100644
--- a/src/backend/taler-merchant-httpd_private-post-orders.c
+++ b/src/backend/taler-merchant-httpd_private-post-orders.c
@@ -74,9 +74,6 @@ check_products (json_t *products)
int res;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("description", &description),
- /* FIXME: there are other fields in the product specification
- that are currently not labeled as optional. Maybe check
- those as well, or make them truly optional. */
GNUNET_JSON_spec_end ()
};
@@ -89,7 +86,7 @@ check_products (json_t *products)
{
GNUNET_break (0);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Product description parsing failed at #%u: %s:%u\n",
+ "Product parsing failed at #%u: %s:%u\n",
(unsigned int) index,
error_name,
error_line);
@@ -188,7 +185,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
- * @return transaction status
+ * @return transaction status, 0 if @a uuids were insufficient to reserve
required inventory
*/
static enum GNUNET_DB_QueryStatus
execute_transaction (struct TMH_HandlerContext *hc,
@@ -209,7 +206,7 @@ execute_transaction (struct TMH_HandlerContext *hc,
GNUNET_break (0);
return GNUNET_DB_STATUS_HARD_ERROR;
}
- // FIXME: migrate locks from UUIDs to ORDER here!
+ /* Setup order */
qs = TMH_db->insert_order (TMH_db->cls,
hc->instance->settings.id,
order_id,
@@ -220,7 +217,44 @@ execute_transaction (struct TMH_HandlerContext *hc,
TMH_db->rollback (TMH_db->cls);
return qs;
}
- return TMH_db->commit (TMH_db->cls);
+ GNUNET_assert (qs > 0);
+ /* Migrate locks from UUIDs to new order: first release old locks */
+ for (unsigned int i = 0; i<uuids_length; i++)
+ {
+ qs = TMH_db->unlock_inventory (TMH_db->cls,
+ &uuids[i]);
+ if (qs < 0)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ return qs;
+ }
+ /* qs == 0 is OK here, that just means we did not HAVE any lock under this
+ UUID */
+ }
+ /* Migrate locks from UUIDs to new order: acquire new locks
+ (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++)
+ {
+ qs = TMH_db->insert_order_lock (TMH_db->cls,
+ hc->instance->settings.id,
+ order_id,
+ inventory_products[i].product_id,
+ inventory_products[i].quantity);
+ if (qs <= 0)
+ {
+ /* qs == 0: lock acquisition failed due to insufficient stocks */
+ TMH_db->rollback (TMH_db->cls);
+ return qs;
+ }
+ }
+ /* 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)
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; /* 1 == success! */
+ return qs;
}
@@ -790,13 +824,137 @@ merge_inventory (struct MHD_Connection *connection,
if (NULL == json_object_get (order,
"products"))
{
- json_object_set_new (order,
- "products",
- json_array ());
+ GNUNET_assert (0 ==
+ json_object_set_new (order,
+ "products",
+ json_array ()));
}
+ {
+ bool have_total = false;
+ bool want_total;
+ struct TALER_Amount total;
+ json_t *np = json_array ();
+
+ want_total = (NULL == json_object_get (order,
+ "total"));
+
+ for (unsigned int i = 0; i<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,
+ &pd);
+ if (qs <= 0)
+ {
+ enum TALER_ErrorCode ec;
+ unsigned int http_status;
- // FIXME: merge inventory products into order here!
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ ec = TALER_EC_ORDERS_LOOKUP_PRODUCT_DB_HARD_FAILURE;
+ break;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ ec = TALER_EC_ORDERS_LOOKUP_PRODUCT_DB_SOFT_FAILURE;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ http_status = MHD_HTTP_NOT_FOUND;
+ ec = TALER_EC_ORDERS_LOOKUP_PRODUCT_NOT_FOUND;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* case listed to make compilers happy */
+ GNUNET_assert (0);
+ }
+ json_decref (np);
+ return TALER_MHD_reply_with_error (connection,
+ http_status,
+ ec,
+ inventory_products[i].product_id);
+ }
+ {
+ json_t *p;
+
+ p = json_pack ("{s:s, s:o, s:s, s:o, s:o, s:o}",
+ "description",
+ pd.description,
+ "description_i18n",
+ pd.description_i18n,
+ "unit",
+ pd.unit,
+ "price",
+ TALER_JSON_from_amount (&pd.price),
+ "taxes",
+ pd.taxes,
+ "image",
+ pd.image);
+ GNUNET_assert (NULL != p);
+ GNUNET_assert (0 ==
+ json_array_append_new (np,
+ p));
+ if (have_total)
+ {
+ if (0 <
+ TALER_amount_add (&total,
+ &total,
+ &pd.price))
+ {
+ GNUNET_break (0);
+ json_decref (np);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+
TALER_EC_ORDERS_TOTAL_SUM_FAILED,
+ "failed to add up product
prices");
+ }
+ }
+ else
+ {
+ have_total = true;
+ total = pd.price;
+ }
+
+ }
+ GNUNET_free (pd.description);
+ GNUNET_free (pd.unit);
+ json_decref (pd.address);
+ }
+ if ( (have_total) &&
+ (want_total) )
+ {
+ GNUNET_assert (0 ==
+ json_object_set_new (order,
+ "total",
+ TALER_JSON_from_amount (&total)));
+ }
+ if ( (! have_total) &&
+ (want_total) )
+ {
+ GNUNET_break_op (0);
+ json_decref (np);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_ORDERS_TOTAL_MISSING,
+ "total missing in order, and we could not calculate it");
+ }
+
+ /* merge into existing products list */
+ {
+ json_t *xp;
+
+ xp = json_object_get (order,
+ "products");
+ GNUNET_assert (NULL != xp);
+ json_array_extend (xp, np);
+ json_decref (np);
+ }
+ }
return add_payment_details (connection,
hc,
order,
diff --git a/src/backenddb/merchant-0001.sql b/src/backenddb/merchant-0001.sql
index 7c58643..b92cd6f 100644
--- a/src/backenddb/merchant-0001.sql
+++ b/src/backenddb/merchant-0001.sql
@@ -167,12 +167,12 @@ CREATE TABLE IF NOT EXISTS merchant_inventory_locks
,total_locked BIGINT NOT NULL
,expiration TIMESTAMP NOT NULL
);
-CREATE INDEX IF NOT EXISTS merchant_inventory_locks_by_product_and_lock
- ON merchant_inventory_locks
- (product_serial, lock_uuid);
CREATE INDEX IF NOT EXISTS merchant_inventory_locks_by_expiration
ON merchant_inventory_locks
(expiration);
+CREATE INDEX IF NOT EXISTS merchant_inventory_locks_by_uuid
+ ON merchant_inventory_locks
+ (lock_uuid);
COMMENT ON TABLE merchant_inventory_locks
IS 'locks on inventory helt by shopping carts; note that locks MAY not be
honored if merchants increase total_lost for inventory';
COMMENT ON COLUMN merchant_inventory_locks.total_locked
diff --git a/src/backenddb/plugin_merchantdb_postgres.c
b/src/backenddb/plugin_merchantdb_postgres.c
index 4ad8463..280b9f5 100644
--- a/src/backenddb/plugin_merchantdb_postgres.c
+++ b/src/backenddb/plugin_merchantdb_postgres.c
@@ -1151,6 +1151,68 @@ postgres_insert_order (void *cls,
}
+/**
+ * Release an inventory lock by UUID. Releases ALL stocks locked under
+ * the given UUID.
+ *
+ * @param cls closure
+ * @param uuid the UUID to release locks for
+ * @return transaction status,
+ * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS means there are no locks under @a
uuid
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT indicates success
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_unlock_inventory (void *cls,
+ const struct GNUNET_Uuid *uuid)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (uuid),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "unlock_inventory",
+ params);
+}
+
+
+/**
+ * Lock inventory stock to a particular order.
+ *
+ * @param cls closure
+ * @param instance_id identifies the instance responsible for the order
+ * @param order_id alphanumeric string that uniquely identifies the order
+ * @param product_id uniquely identifies the product to be locked
+ * @param quantity how many units should be locked to the @a order_id
+ * @return transaction status,
+ * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS means there are insufficient stocks
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT indicates success
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_insert_order_lock (void *cls,
+ const char *instance_id,
+ const char *order_id,
+ const char *product_id,
+ uint32_t quantity)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (order_id),
+ GNUNET_PQ_query_param_string (product_id),
+ GNUNET_PQ_query_param_uint32 (&quantity),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_order_lock",
+ params);
+}
+
+
/* ********************* OLD API ************************** */
/**
@@ -4163,7 +4225,7 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
" FROM merchant_inventory"
" JOIN ps USING (product_serial)"
" WHERE "
- " total_stock - total_sold - total_lost > "
+ " total_stock - total_sold - total_lost - $4
>= "
" (SELECT SUM(total_locked)"
" FROM merchant_inventory_locks"
" WHERE product_serial=ps.product_serial) +
"
@@ -4204,6 +4266,41 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
" FROM merchant_instances"
" WHERE merchant_id=$1",
4),
+ GNUNET_PQ_make_prepare ("unlock_inventory",
+ "DELETE"
+ " FROM merchant_inventory_locks"
+ " WHERE lock_uuid=$1",
+ 1),
+ GNUNET_PQ_make_prepare ("insert_order_lock",
+ "WITH tmp AS"
+ " (SELECT "
+ " product_serial"
+ " ,merchant_serial"
+ " ,total_stock"
+ " ,total_sold"
+ " ,total_lost"
+ " FROM merchant_inventory"
+ " WHERE product_id=$3"
+ " AND merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1))"
+ " INSERT INTO merchant_order_locks"
+ " (product_serial"
+ " ,total_locked"
+ " ,order_serial)"
+ " SELECT tmp.product_serial, $4, order_serial"
+ " FROM merchant_orders"
+ " JOIN tmp USING(merchant_serial)"
+ " WHERE order_id=$2 AND"
+ " tmp.total_stock - tmp.total_sold -
tmp.total_lost - $4 >= "
+ " (SELECT SUM(total_locked)"
+ " FROM merchant_inventory_locks"
+ " WHERE product_serial=tmp.product_serial)
+ "
+ " (SELECT SUM(total_locked)"
+ " FROM merchant_order_locks"
+ " WHERE product_serial=tmp.product_serial)",
+ 4),
/* OLD API: */
#if 0
GNUNET_PQ_make_prepare ("insert_deposit",
@@ -4698,7 +4795,9 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
plugin->lock_product = &postgres_lock_product;
plugin->delete_order = &postgres_delete_order;
plugin->lookup_order = &postgres_lookup_order;
-
+ plugin->insert_order = &postgres_insert_order;
+ plugin->unlock_inventory = &postgres_unlock_inventory;
+ plugin->insert_order_lock = &postgres_insert_order_lock;
/* old API: */
plugin->store_deposit = &postgres_store_deposit;
plugin->store_coin_to_transfer = &postgres_store_coin_to_transfer;
@@ -4711,7 +4810,6 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
plugin->find_deposits_by_wtid = &postgres_find_deposits_by_wtid;
plugin->find_proof_by_wtid = &postgres_find_proof_by_wtid;
plugin->insert_contract_terms = &postgres_insert_contract_terms;
- plugin->insert_order = &postgres_insert_order;
plugin->find_contract_terms = &postgres_find_contract_terms;
plugin->find_contract_terms_history = &postgres_find_contract_terms_history;
plugin->find_contract_terms_by_date = &postgres_find_contract_terms_by_date;
diff --git a/src/include/taler_merchantdb_plugin.h
b/src/include/taler_merchantdb_plugin.h
index 32407bb..c74b67c 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -651,7 +651,7 @@ struct TALER_MERCHANTDB_Plugin
*
* @param cls closure
* @param instance_id identifies the instance responsible for the order
- * @param order_id alphanumeric string that uniquely identifies the proposal
+ * @param order_id alphanumeric string that uniquely identifies the order
* @param pay_deadline how long does the customer have to pay for the order
* @param contract_terms proposal data to store
* @return transaction status
@@ -664,6 +664,41 @@ struct TALER_MERCHANTDB_Plugin
const json_t *contract_terms);
+ /**
+ * Release an inventory lock by UUID. Releases ALL stocks locked under
+ * the given UUID.
+ *
+ * @param cls closure
+ * @param uuid the UUID to release locks for
+ * @return transaction status,
+ * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS means there are no locks under @a
uuid
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT indicates success
+ */
+ enum GNUNET_DB_QueryStatus
+ (*unlock_inventory)(void *cls,
+ const struct GNUNET_Uuid *uuid);
+
+
+ /**
+ * Lock inventory stock to a particular order.
+ *
+ * @param cls closure
+ * @param instance_id identifies the instance responsible for the order
+ * @param order_id alphanumeric string that uniquely identifies the order
+ * @param product_id uniquely identifies the product to be locked
+ * @param quantity how many units should be locked to the @a order_id
+ * @return transaction status,
+ * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS means there are insufficient stocks
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT indicates success
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_order_lock)(void *cls,
+ const char *instance_id,
+ const char *order_id,
+ const char *product_id,
+ uint32_t quantity);
+
+
/* ****************** OLD API ******************** */
/**
--
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.
- [taler-merchant] 41/277: add DELETE/PURGE /instances/ID command, (continued)
- [taler-merchant] 41/277: add DELETE/PURGE /instances/ID command, gnunet, 2020/07/05
- [taler-merchant] 42/277: implement purge, gnunet, 2020/07/05
- [taler-merchant] 43/277: toward stesting, gnunet, 2020/07/05
- [taler-merchant] 50/277: misc fixes, gnunet, 2020/07/05
- [taler-merchant] 49/277: fix fmt string, gnunet, 2020/07/05
- [taler-merchant] 68/277: sql-ing, gnunet, 2020/07/05
- [taler-merchant] 53/277: implemente DELETE /orders/ID, gnunet, 2020/07/05
- [taler-merchant] 38/277: add PATCH /instances/ID command, gnunet, 2020/07/05
- [taler-merchant] 46/277: improve API, gnunet, 2020/07/05
- [taler-merchant] 54/277: first hack at POST /orders, gnunet, 2020/07/05
- [taler-merchant] 57/277: implement logic to complete POSTed /orders using inventory data,
gnunet <=
- [taler-merchant] 62/277: mark as obsolete, gnunet, 2020/07/05
- [taler-merchant] 60/277: GET /orders logic, gnunet, 2020/07/05
- [taler-merchant] 48/277: fix tests, gnunet, 2020/07/05
- [taler-merchant] 56/277: implement POST /orders client with all optional arguments, gnunet, 2020/07/05
- [taler-merchant] 55/277: fix fTBFS, gnunet, 2020/07/05
- [taler-merchant] 61/277: rename fest to match new structure, gnunet, 2020/07/05
- [taler-merchant] 69/277: update pay logic, gnunet, 2020/07/05
- [taler-merchant] 74/277: DB API for /abort, gnunet, 2020/07/05
- [taler-merchant] 47/277: typo, gnunet, 2020/07/05
- [taler-merchant] 40/277: add PATCH /instances/ID command, gnunet, 2020/07/05