gnunet-svn
[Top][All Lists]
Advanced

[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.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]