summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/Makefile.am6
-rw-r--r--src/backend/taler-merchant-httpd.c8
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-instances-ID.c369
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-instances-ID.h43
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-products-ID.c254
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-products-ID.h43
-rw-r--r--src/backend/taler-merchant-httpd_private-post-products.c262
-rw-r--r--src/backend/taler-merchant-httpd_private-post-products.h43
-rw-r--r--src/include/taler_merchantdb_plugin.h35
9 files changed, 1060 insertions, 3 deletions
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
index 31245456..646841db 100644
--- a/src/backend/Makefile.am
+++ b/src/backend/Makefile.am
@@ -35,8 +35,12 @@ taler_merchant_httpd_SOURCES = \
taler-merchant-httpd_private-get-products-ID.h \
taler-merchant-httpd_private-patch-instances-ID.c \
taler-merchant-httpd_private-patch-instances-ID.h \
+ taler-merchant-httpd_private-patch-products-ID.c \
+ taler-merchant-httpd_private-patch-products-ID.h \
taler-merchant-httpd_private-post-instances.c \
- taler-merchant-httpd_private-post-instances.h
+ taler-merchant-httpd_private-post-instances.h \
+ taler-merchant-httpd_private-post-products.c \
+ taler-merchant-httpd_private-post-products.h
DEAD = \
taler-merchant-httpd_check-payment.c taler-merchant-httpd_check-payment.h \
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c
index cc722487..58b68451 100644
--- a/src/backend/taler-merchant-httpd.c
+++ b/src/backend/taler-merchant-httpd.c
@@ -35,6 +35,7 @@
#include "taler-merchant-httpd_private-get-products.h"
#include "taler-merchant-httpd_private-get-products-ID.h"
#include "taler-merchant-httpd_private-patch-instances-ID.h"
+#include "taler-merchant-httpd_private-patch-products-ID.h"
#include "taler-merchant-httpd_private-post-instances.h"
/**
@@ -782,6 +783,13 @@ url_handler (void *cls,
.have_id_segment = true,
.handler = &TMH_private_delete_instances_ID
},
+ /* PATCH /products/$ID/: */
+ {
+ .url_prefix = "/",
+ .method = MHD_HTTP_METHOD_PATCH,
+ .have_id_segment = true,
+ .handler = &TMH_private_patch_products_ID
+ },
{
NULL
}
diff --git a/src/backend/taler-merchant-httpd_private-patch-instances-ID.c b/src/backend/taler-merchant-httpd_private-patch-instances-ID.c
new file mode 100644
index 00000000..9e151493
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-patch-instances-ID.c
@@ -0,0 +1,369 @@
+/*
+ This file is part of TALER
+ (C) 2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file backend/taler-merchant-httpd_private-patch-instances.c
+ * @brief implementing PATCH /instances/$ID request handling
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-patch-instances-ID.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * How often do we retry the simple INSERT database transaction?
+ */
+#define MAX_RETRIES 3
+
+
+/**
+ * Free memory used by @a wm
+ *
+ * @param wm wire method to free
+ */
+static void
+free_wm (struct TMH_WireMethod *wm)
+{
+ json_decref (wm->j_wire);
+ GNUNET_free (wm->wire_method);
+ GNUNET_free (wm);
+}
+
+
+/**
+ * PATCH configuration of an existing instance, given its configuration.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_patch_instances_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ struct TALER_MERCHANTDB_InstanceSettings is;
+ json_t *payto_uris;
+ const char *name;
+ struct TMH_WireMethod *wm_head = NULL;
+ struct TMH_WireMethod *wm_tail = NULL;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_json ("payto_uris",
+ &payto_uris),
+ GNUNET_JSON_spec_string ("name",
+ &name),
+ GNUNET_JSON_spec_json ("address",
+ &is.address),
+ GNUNET_JSON_spec_json ("jurisdiction",
+ &is.jurisdiction),
+ TALER_JSON_spec_amount ("default_max_deposit_fee",
+ &is.default_max_deposit_fee),
+ TALER_JSON_spec_amount ("default_max_wire_fee",
+ &is.default_max_wire_fee),
+ GNUNET_JSON_spec_uint32 ("default_wire_fee_amortization",
+ &is.default_wire_fee_amortization),
+ GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay",
+ &is.default_wire_transfer_delay),
+ GNUNET_JSON_spec_relative_time ("default_pay_delay",
+ &is.default_pay_delay),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_assert (NULL != mi);
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ hc->request_body,
+ spec);
+ /* json is malformed */
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ return MHD_YES;
+ }
+ /* other internal errors might have occurred */
+ if (GNUNET_SYSERR == res)
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_INTERNAL_INVARIANT_FAILURE,
+ "Impossible to parse the order");
+ }
+ if (! json_is_array (payto_uris))
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_PATCH_INSTANCES_BAD_PAYTO_URIS,
+ "Invalid bank account information");
+ for (unsigned int i = 0; i<MAX_RETRIES; i++)
+ {
+ /* Cleanup after earlier loops */
+ {
+ struct TMH_WireMethod *wm;
+
+ while (NULL != (wm = wm_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (wm_head,
+ wm_tail,
+ wm);
+ free_wm (wm);
+ }
+ }
+ if (GNUNET_OK !=
+ TMH_db->start (TMH_db->cls,
+ "PATCH /instances"))
+ {
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PATCH_INSTANCES_DB_START_ERROR,
+ "failed to start database transaction");
+ }
+ /* Check for equality of settings */
+ if (! ( (0 == strcmp (mi->settings.name,
+ name)) &&
+ (1 == json_equal (mi->settings.address,
+ is.address)) &&
+ (1 == json_equal (mi->settings.jurisdiction,
+ is.jurisdiction)) &&
+ (0 == TALER_amount_cmp_currency (
+ &mi->settings.default_max_deposit_fee,
+ &is.default_max_deposit_fee)) &&
+ (0 == TALER_amount_cmp (&mi->settings.default_max_deposit_fee,
+ &is.default_max_deposit_fee)) &&
+ (0 == TALER_amount_cmp_currency (&mi->settings.default_max_wire_fee,
+ &is.default_max_wire_fee)) &&
+ (0 == TALER_amount_cmp (&mi->settings.default_max_wire_fee,
+ &is.default_max_wire_fee)) &&
+ (mi->settings.default_wire_fee_amortization ==
+ is.default_wire_fee_amortization) &&
+ (mi->settings.default_wire_transfer_delay.rel_value_us ==
+ is.default_wire_transfer_delay.rel_value_us) &&
+ (mi->settings.default_pay_delay.rel_value_us ==
+ is.default_pay_delay.rel_value_us) ) )
+ {
+ qs = TMH_db->update_instance (TMH_db->cls,
+ &mi->settings);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ goto retry;
+ else
+ goto giveup;
+ }
+ }
+
+ /* Check for changes in accounts */
+ {
+ unsigned int len = json_array_size (payto_uris);
+ bool matches[GNUNET_NZL (len)];
+ bool matched;
+
+ for (struct TMH_WireMethod *wm = mi->wm_head;
+ NULL != wm;
+ wm = wm->next)
+ {
+ const char *uri = json_string_value (json_object_get (wm->j_wire,
+ "payto_uri"));
+ GNUNET_assert (NULL != uri);
+ matched = false;
+ for (unsigned int i = 0; i<len; i++)
+ {
+ const char *str = json_string_value (json_array_get (payto_uris,
+ i));
+ if (NULL == str)
+ {
+ GNUNET_break_op (0);
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_JSON_parse_free (spec);
+ GNUNET_assert (NULL == wm_head);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_POST_INSTANCES_BAD_PAYTO_URIS,
+ "Invalid bank account information");
+ }
+ if ( (strcasecmp (uri,
+ str)) )
+ {
+ if (matches[i])
+ {
+ GNUNET_break (0);
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_JSON_parse_free (spec);
+ GNUNET_assert (NULL == wm_head);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_POST_INSTANCES_BAD_PAYTO_URIS,
+ "Invalid bank account information");
+ }
+ matches[i] = true;
+ matched = true;
+ break;
+ }
+ }
+ /* delete unmatched (= removed) accounts */
+ if (! matched)
+ {
+ /* Account was REMOVED */
+ wm->deleting = true;
+ qs = TMH_db->inactivate_account (TMH_db->cls,
+ &wm->h_wire);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ goto retry;
+ else
+ goto giveup;
+ }
+ }
+ }
+ /* Find _new_ accounts */
+ for (unsigned int i = 0; i<len; i++)
+ {
+ struct TALER_MERCHANTDB_AccountDetails ad;
+ struct TMH_WireMethod *wm;
+
+ if (matches[i])
+ continue; /* account existed */
+ ad.payto_uri = json_string_value (json_array_get (payto_uris,
+ i));
+ GNUNET_assert (NULL != ad.payto_uri);
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ &ad.salt,
+ sizeof (ad.salt));
+ wm = GNUNET_new (struct TMH_WireMethod);
+ wm->j_wire = json_pack ("{s:O, s:s}",
+ "payto_uri", ad.payto_uri,
+ "salt", GNUNET_JSON_from_data_auto (&ad.salt));
+ GNUNET_assert (NULL != wm->j_wire);
+ wm->wire_method
+ = TALER_payto_get_method (ad.payto_uri);
+ GNUNET_assert (NULL != wm->wire_method);
+ /* This also tests for things like the IBAN being malformed */
+ if (GNUNET_OK !=
+ TALER_JSON_merchant_wire_signature_hash (wm->j_wire,
+ &wm->h_wire))
+ {
+ free_wm (wm);
+ while (NULL != (wm = wm_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (wm_head,
+ wm_tail,
+ wm);
+ free_wm (wm);
+ }
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_POST_INSTANCES_BAD_PAYTO_URIS,
+ "Invalid bank account information");
+ }
+ wm->active = true;
+ GNUNET_CONTAINER_DLL_insert (wm_head,
+ wm_tail,
+ wm);
+ ad.h_wire = wm->h_wire;
+ ad.active = true;
+ qs = TMH_db->insert_account (TMH_db->cls,
+ mi->settings.id,
+ &ad);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ goto retry;
+ else
+ goto giveup;
+ }
+ }
+ }
+
+ qs = TMH_db->commit (TMH_db->cls);
+retry:
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ continue;
+ break;
+ } /* for(... MAX_RETRIES) */
+giveup:
+ if (0 > qs)
+ {
+ struct TMH_WireMethod *wm;
+
+ while (NULL != (wm = wm_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (wm_head,
+ wm_tail,
+ wm);
+ free_wm (wm);
+ }
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PATCH_INSTANCES_DB_COMMIT_ERROR,
+ "failed to add instance to database");
+ }
+ /* Deactivate existing wire methods that were removed above */
+ for (struct TMH_WireMethod *wm = mi->wm_head;
+ NULL != wm;
+ wm = wm->next)
+ {
+ /* We did not flip the 'active' bits earlier because the
+ DB transaction could still fail. Now it is time to update our
+ runtime state. */
+ if (wm->deleting)
+ wm->active = false;
+ }
+
+ /* Update our 'settings' */
+ GNUNET_free (mi->settings.name);
+ is.id = mi->settings.id;
+ mi->settings = is;
+ mi->settings.name = GNUNET_strdup (name);
+
+ /* Add 'new' wire methods to our list */
+ {
+ struct TMH_WireMethod *wm;
+
+ while (NULL != (wm = wm_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (wm_head,
+ wm_tail,
+ wm);
+ GNUNET_CONTAINER_DLL_insert (mi->wm_head,
+ mi->wm_tail,
+ wm);
+ }
+ }
+
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-merchant-httpd_private-patch-instances-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-patch-instances-ID.h b/src/backend/taler-merchant-httpd_private-patch-instances-ID.h
new file mode 100644
index 00000000..dde86a9b
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-patch-instances-ID.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ (C) 2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file backend/taler-merchant-httpd_private-patch-instances-ID.h
+ * @brief implementing POST /instances request handling
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_PATCH_INSTANCES_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_PATCH_INSTANCES_ID_H
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * PATCH configuration of an existing instance, given its configuration.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_patch_instances_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-patch-products-ID.c b/src/backend/taler-merchant-httpd_private-patch-products-ID.c
new file mode 100644
index 00000000..8177207c
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-patch-products-ID.c
@@ -0,0 +1,254 @@
+/*
+ This file is part of TALER
+ (C) 2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file backend/taler-merchant-httpd_private-patch-products.c
+ * @brief implementing PATCH /products/$ID request handling
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-patch-products-ID.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * How often do we retry the simple INSERT database transaction?
+ */
+#define MAX_RETRIES 3
+
+
+/**
+ * Determine the cause of the PATCH failure in more detail and report.
+ *
+ * @param connection connection to report on
+ * @param instance_id instance we are processing
+ * @param product_id ID of the product to patch
+ * @param pd product details we failed to set
+ */
+static MHD_RESULT
+determine_cause (struct MHD_Connection *connection,
+ const char *instance_id,
+ const char *product_id,
+ const struct TALER_MERCHANTDB_ProductDetails *pd)
+{
+ struct TALER_MERCHANTDB_ProductDetails pdx;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TMH_db->lookup_product (TMH_db->cls,
+ instance_id,
+ product_id,
+ &pdx);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PRODUCTS_PATCH_DB_COMMIT_HARD_ERROR,
+ "Failed to get existing product");
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_INTERNAL_INVARIANT_FAILURE,
+ "Serialization error for single-statment request");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_PRODUCTS_PATCH_UNKNOWN_PRODUCT,
+ "The specified product is unknown");
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break; /* do below */
+ }
+
+ {
+ enum TALER_ErrorCode ec;
+ const char *hint;
+
+ ec = TALER_EC_INTERNAL_INVARIANT_FAILURE;
+ hint = "transaction failed for causes unknown";
+ if (pdx.total_lost > pd->total_lost)
+ {
+ ec = TALER_EC_PRODUCTS_PATCH_TOTAL_LOST_REDUCED;
+ hint = "total lost cannot be lowered";
+ }
+ if (pdx.total_sold > pd->total_sold)
+ {
+ ec = TALER_EC_PRODUCTS_PATCH_TOTAL_SOLD_REDUCED;
+ hint = "total sold cannot be lowered";
+ }
+ if (pdx.total_stocked > pd->total_stocked)
+ {
+ ec = TALER_EC_PRODUCTS_PATCH_TOTAL_STOCKED_REDUCED;
+ hint = "total stocked cannot be lowered";
+ }
+ GNUNET_free (pdx.description);
+ json_decref (pdx.description_i18n);
+ GNUNET_free (pdx.unit);
+ json_decref (pdx.taxes);
+ json_decref (pdx.image);
+ json_decref (pdx.location);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ ec,
+ hint);
+ }
+}
+
+
+/**
+ * PATCH configuration of an existing instance, given its configuration.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_patch_products_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ const char *product_id = hc->infix;
+ struct TALER_MERCHANTDB_ProductDetails pd;
+ int64_t total_stocked;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("description",
+ (const char **) &pd.description),
+ GNUNET_JSON_spec_json ("description_i18n",
+ &pd.description_i18n),
+ GNUNET_JSON_spec_string ("unit",
+ (const char **) &pd.unit),
+ TALER_JSON_spec_amount ("price",
+ &pd.price),
+ GNUNET_JSON_spec_json ("image",
+ &pd.image),
+ GNUNET_JSON_spec_json ("taxes",
+ &pd.taxes),
+ GNUNET_JSON_spec_json ("location",
+ &pd.taxes),
+ GNUNET_JSON_spec_int64 ("total_stocked",
+ &total_stocked),
+ GNUNET_JSON_spec_absolute_time ("next_restock",
+ &pd.next_restock),
+ GNUNET_JSON_spec_end ()
+ };
+
+ GNUNET_assert (NULL != mi);
+ GNUNET_assert (NULL != product_id);
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ hc->request_body,
+ spec);
+ /* json is malformed */
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ return MHD_YES;
+ }
+ /* other internal errors might have occurred */
+ if (GNUNET_SYSERR == res)
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_INTERNAL_INVARIANT_FAILURE,
+ "Impossible to parse the order");
+ }
+ if (-1 == total_stocked)
+ pd.total_stocked = UINT64_MAX;
+ else
+ pd.total_stocked = (uint64_t) total_stocked;
+ if (NULL != json_object_get (hc->request_body,
+ "next_restock"))
+ {
+ enum GNUNET_GenericReturnValue res;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_absolute_time ("next_restock",
+ &pd.next_restock),
+ GNUNET_JSON_spec_end ()
+ };
+
+ res = TALER_MHD_parse_json_data (connection,
+ hc->request_body,
+ spec);
+ /* json is malformed */
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return MHD_YES;
+ }
+ /* other internal errors might have occurred */
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_INTERNAL_INVARIANT_FAILURE,
+ "Impossible to parse the restock time");
+ }
+ }
+ else
+ {
+ pd.next_restock.abs_value_us = 0;
+ }
+
+ qs = TMH_db->update_product (TMH_db->cls,
+ mi->settings.id,
+ product_id,
+ &pd);
+ {
+ MHD_RESULT ret;
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PRODUCTS_PATCH_DB_COMMIT_HARD_ERROR,
+ "Failed to commit change");
+ break;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_INTERNAL_INVARIANT_FAILURE,
+ "Serialization error for single-statment request");
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ ret = determine_cause (connection,
+ mi->settings.id,
+ product_id,
+ &pd);
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ ret = TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ break;
+ }
+ GNUNET_JSON_parse_free (spec);
+ return ret;
+ }
+}
+
+
+/* end of taler-merchant-httpd_private-patch-products-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-patch-products-ID.h b/src/backend/taler-merchant-httpd_private-patch-products-ID.h
new file mode 100644
index 00000000..dde86a9b
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-patch-products-ID.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ (C) 2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file backend/taler-merchant-httpd_private-patch-instances-ID.h
+ * @brief implementing POST /instances request handling
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_PATCH_INSTANCES_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_PATCH_INSTANCES_ID_H
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * PATCH configuration of an existing instance, given its configuration.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_patch_instances_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-post-products.c b/src/backend/taler-merchant-httpd_private-post-products.c
new file mode 100644
index 00000000..498e51c9
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-products.c
@@ -0,0 +1,262 @@
+/*
+ This file is part of TALER
+ (C) 2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file backend/taler-merchant-httpd_private-post-products.c
+ * @brief implementing POST /products request handling
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-post-products.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * How often do we retry the simple INSERT database transaction?
+ */
+#define MAX_RETRIES 3
+
+
+/**
+ * Check if the two products are identical.
+ *
+ * @param p1 product to compare
+ * @param p2 other product to compare
+ * @return true if they are 'equal', false if not or of payto_uris is not an array
+ */
+static bool
+products_equal (const struct TALER_MERCHANTDB_ProductDetails *p1,
+ const struct TALER_MERCHANTDB_ProductDetails *p2)
+{
+ return ( (0 == strcmp (p1->description,
+ p2->description)) &&
+ (1 == json_equal (p1->description_i18n,
+ p2->description_i18n)) &&
+ (0 == strcmp (p1->unit,
+ p2->unit)) &&
+ (0 == TALER_amount_cmp_currency (&p1->price,
+ &p2->price)) &&
+ (0 == TALER_amount_cmp (&p1->price,
+ &p2->price)) &&
+ (1 == json_equal (p1->taxes,
+ p2->taxes)) &&
+ (p1->total_stocked == p2->total_stocked) &&
+ (p1->total_sold == p2->total_sold) &&
+ (p1->total_lost == p2->total_lost) &&
+ (1 == json_equal (p1->image,
+ p2->image)) &&
+ (1 == json_equal (p1->location,
+ p2->location)) &&
+ (p1->next_restock.abs_value_us ==
+ p2->next_restock.abs_value_us) );
+}
+
+
+/**
+ * Generate an instance, given its configuration.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_post_products (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ struct TALER_MERCHANTDB_ProductDetails pd;
+ const char *product_id;
+ int64_t total_stocked;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("product_id",
+ &product_id),
+ GNUNET_JSON_spec_string ("description",
+ (const char **) &pd.description),
+ GNUNET_JSON_spec_json ("description_i18n",
+ &pd.description_i18n),
+ GNUNET_JSON_spec_string ("unit",
+ (const char **) &pd.unit),
+ TALER_JSON_spec_amount ("price",
+ &pd.price),
+ GNUNET_JSON_spec_json ("image",
+ &pd.image),
+ GNUNET_JSON_spec_json ("taxes",
+ &pd.taxes),
+ GNUNET_JSON_spec_json ("location",
+ &pd.taxes),
+ GNUNET_JSON_spec_int64 ("total_stocked",
+ &total_stocked),
+ GNUNET_JSON_spec_absolute_time ("next_restock",
+ &pd.next_restock),
+ GNUNET_JSON_spec_end ()
+ };
+
+ GNUNET_assert (NULL != mi);
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ hc->request_body,
+ spec);
+ /* json is malformed */
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ return MHD_YES;
+ }
+ /* other internal errors might have occurred */
+ if (GNUNET_SYSERR == res)
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_INTERNAL_INVARIANT_FAILURE,
+ "Impossible to parse the order");
+ }
+
+ if (-1 == total_stocked)
+ pd.total_stocked = UINT64_MAX;
+ else
+ pd.total_stocked = (uint64_t) total_stocked;
+ if (NULL != json_object_get (hc->request_body,
+ "next_restock"))
+ {
+ enum GNUNET_GenericReturnValue res;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_absolute_time ("next_restock",
+ &pd.next_restock),
+ GNUNET_JSON_spec_end ()
+ };
+
+ res = TALER_MHD_parse_json_data (connection,
+ hc->request_body,
+ spec);
+ /* json is malformed */
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return MHD_YES;
+ }
+ /* other internal errors might have occurred */
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_INTERNAL_INVARIANT_FAILURE,
+ "Impossible to parse the restock time");
+ }
+ }
+ else
+ {
+ pd.next_restock.abs_value_us = 0;
+ }
+
+ for (unsigned int i = 0; i<MAX_RETRIES; i++)
+ {
+ /* Test if an product of this id is known */
+ struct TALER_MERCHANTDB_ProductDetails epd;
+
+ if (GNUNET_OK !=
+ TMH_db->start (TMH_db->cls,
+ "/post products"))
+ {
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_PRODUCTS_POST_DB_START_ERROR,
+ "Failed to start transaction");
+ }
+ qs = TMH_db->lookup_product (TMH_db->cls,
+ mi->settings.id,
+ product_id,
+ &epd);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ /* Clean up and fail hard */
+ break;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ /* restart transaction */
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* Good, we can proceed! */
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* idempotency check: is epd == pd? */
+ if (products_equal (&pd,
+ &epd))
+ {
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ }
+ else
+ {
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_PRODUCTS_POST_CONFLICT_PRODUCT_EXISTS,
+ "different product exists under this product ID");
+ }
+ }
+
+ qs = TMH_db->insert_product (TMH_db->cls,
+ mi->settings.id,
+ product_id,
+ &pd);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ goto retry;
+ qs = TMH_db->commit (TMH_db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
+ break;
+retry:
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ continue;
+ }
+ }
+ GNUNET_JSON_parse_free (spec);
+ if (qs < 0)
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ ?
+ TALER_EC_PRODUCTS_POST_DB_COMMIT_SOFT_ERROR
+ :
+ TALER_EC_PRODUCTS_POST_DB_COMMIT_HARD_ERROR,
+ "Failed to commit transaction");
+ return TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-merchant-httpd_private-post-products.c */
diff --git a/src/backend/taler-merchant-httpd_private-post-products.h b/src/backend/taler-merchant-httpd_private-post-products.h
new file mode 100644
index 00000000..0874bc2d
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-products.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ (C) 2020 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as
+ published by the Free Software Foundation; either version 3,
+ or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public
+ License along with TALER; see the file COPYING. If not,
+ see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file backend/taler-merchant-httpd_private-post-products.h
+ * @brief implementing POST /products request handling
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_PRODUCTS_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_POST_PRODUCTS_H
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Generate an instance, given its configuration.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] hc context with further information about the request
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_private_post_products (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h
index c20cdbb7..28913f59 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -496,8 +496,8 @@ struct TALER_MERCHANTDB_Plugin
* @return database result code
*/
enum GNUNET_DB_QueryStatus
- (*patch_instance)(void *cls,
- const struct TALER_MERCHANTDB_InstanceSettings *is);
+ (*update_instance)(void *cls,
+ const struct TALER_MERCHANTDB_InstanceSettings *is);
/**
* Set an instance's account in our database to "inactive".
@@ -555,6 +555,37 @@ struct TALER_MERCHANTDB_Plugin
const char *instance_id,
const char *product_id);
+ /**
+ * Insert details about a particular product.
+ *
+ * @param cls closure
+ * @param instance_id instance to insert product for
+ * @param product_id product identifier of product to insert
+ * @param pd the product details to insert
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_product)(void *cls,
+ const char *instance_id,
+ const char *product_id,
+ const struct TALER_MERCHANTDB_ProductDetails *pd);
+
+ /**
+ * Update details about a particular product.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup products for
+ * @param product_id product to lookup
+ * @param[out] pd set to the product details on success, can be NULL
+ * (in that case we only want to check if the product exists)
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*update_product)(void *cls,
+ const char *instance_id,
+ const char *product_id,
+ struct TALER_MERCHANTDB_ProductDetails *pd);
+
/* ****************** OLD API ******************** */