summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog3
-rw-r--r--configure.ac2
m---------contrib/wallet-core0
-rw-r--r--debian/changelog25
-rw-r--r--debian/control10
-rw-r--r--debian/taler-merchant.taler-merchant-depositcheck.service2
-rw-r--r--debian/taler-merchant.taler-merchant-exchange.service4
-rw-r--r--debian/taler-merchant.taler-merchant-webhook.service2
-rw-r--r--debian/taler-merchant.taler-merchant-wirewatch.service2
-rw-r--r--doc/doxygen/taler.doxy2
m---------doc/prebuilt0
-rw-r--r--src/backend/Makefile.am12
-rw-r--r--src/backend/taler-merchant-httpd.c54
-rw-r--r--src/backend/taler-merchant-httpd_config.c2
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-categories-ID.c92
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-categories-ID.h42
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-otp-devices-ID.c2
-rw-r--r--src/backend/taler-merchant-httpd_private-delete-products-ID.c40
-rw-r--r--src/backend/taler-merchant-httpd_private-get-categories-ID.c119
-rw-r--r--src/backend/taler-merchant-httpd_private-get-categories-ID.h42
-rw-r--r--src/backend/taler-merchant-httpd_private-get-categories.c93
-rw-r--r--src/backend/taler-merchant-httpd_private-get-categories.h42
-rw-r--r--src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c2
-rw-r--r--src/backend/taler-merchant-httpd_private-get-instances-ID.c2
-rw-r--r--src/backend/taler-merchant-httpd_private-get-instances.c2
-rw-r--r--src/backend/taler-merchant-httpd_private-get-pos.c148
-rw-r--r--src/backend/taler-merchant-httpd_private-get-pos.h41
-rw-r--r--src/backend/taler-merchant-httpd_private-get-products-ID.c41
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-categories-ID.c120
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-categories-ID.h45
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-instances-ID.c20
-rw-r--r--src/backend/taler-merchant-httpd_private-patch-products-ID.c306
-rw-r--r--src/backend/taler-merchant-httpd_private-post-categories.c170
-rw-r--r--src/backend/taler-merchant-httpd_private-post-categories.h45
-rw-r--r--src/backend/taler-merchant-httpd_private-post-instances.c14
-rw-r--r--src/backend/taler-merchant-httpd_private-post-orders.c156
-rw-r--r--src/backend/taler-merchant-httpd_private-post-products-ID-lock.c72
-rw-r--r--src/backend/taler-merchant-httpd_private-post-products.c285
-rw-r--r--src/backenddb/Makefile.am10
-rw-r--r--src/backenddb/merchant-0001.sql2
-rw-r--r--src/backenddb/merchant-0006.sql58
-rw-r--r--src/backenddb/merchantdb_helper.c21
-rw-r--r--src/backenddb/pg_delete_category.c54
-rw-r--r--src/backenddb/pg_delete_category.h43
-rw-r--r--src/backenddb/pg_delete_otp.c4
-rw-r--r--src/backenddb/pg_insert_category.c65
-rw-r--r--src/backenddb/pg_insert_category.h46
-rw-r--r--src/backenddb/pg_insert_deposit_to_transfer.sql2
-rw-r--r--src/backenddb/pg_insert_instance.c8
-rw-r--r--src/backenddb/pg_insert_product.c62
-rw-r--r--src/backenddb/pg_insert_product.h14
-rw-r--r--src/backenddb/pg_insert_product.sql175
-rw-r--r--src/backenddb/pg_lookup_all_products.c193
-rw-r--r--src/backenddb/pg_lookup_all_products.h46
-rw-r--r--src/backenddb/pg_lookup_categories.c148
-rw-r--r--src/backenddb/pg_lookup_categories.h43
-rw-r--r--src/backenddb/pg_lookup_instances.c8
-rw-r--r--src/backenddb/pg_lookup_product.c59
-rw-r--r--src/backenddb/pg_lookup_product.h7
-rw-r--r--src/backenddb/pg_select_category.c37
-rw-r--r--src/backenddb/pg_select_category.h45
-rw-r--r--src/backenddb/pg_select_category_by_name.c68
-rw-r--r--src/backenddb/pg_select_category_by_name.h46
-rw-r--r--src/backenddb/pg_update_category.c59
-rw-r--r--src/backenddb/pg_update_category.h47
-rw-r--r--src/backenddb/pg_update_instance.c3
-rw-r--r--src/backenddb/pg_update_otp.c15
-rw-r--r--src/backenddb/pg_update_product.c76
-rw-r--r--src/backenddb/pg_update_product.h26
-rw-r--r--src/backenddb/pg_update_product.sql139
-rw-r--r--src/backenddb/plugin_merchantdb_postgres.c21
-rw-r--r--src/backenddb/procedures.sql.in2
-rw-r--r--src/backenddb/test_merchantdb.c251
-rw-r--r--src/include/taler_merchant_service.h65
-rw-r--r--src/include/taler_merchantdb_lib.h10
-rw-r--r--src/include/taler_merchantdb_plugin.h279
-rw-r--r--src/lib/Makefile.am2
-rw-r--r--src/lib/merchant_api_get_config.c4
-rw-r--r--src/lib/merchant_api_get_instance.c10
-rw-r--r--src/lib/merchant_api_get_instances.c7
-rw-r--r--src/lib/merchant_api_get_product.c31
-rw-r--r--src/lib/merchant_api_patch_instance.c10
-rw-r--r--src/lib/merchant_api_post_instances.c10
-rw-r--r--src/lib/merchant_api_post_products.c59
-rw-r--r--src/merchant-tools/taler-merchant-passwd.c14
-rwxr-xr-xsrc/testing/test_merchant_order_refund.sh233
-rw-r--r--src/testing/testing_api_cmd_patch_instance.c1
-rw-r--r--src/testing/testing_api_cmd_post_instances.c1
88 files changed, 3979 insertions, 721 deletions
diff --git a/ChangeLog b/ChangeLog
index 6bc01461..46004f56 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,6 @@
+Wed May 15 19:43:41 AM CEST 2024
+ Releasing taler-merchant 0.11.0. -CG
+
Fri Apr 12 10:43:41 AM CEST 2024
Releasing taler-merchant 0.10.2. -CG
diff --git a/configure.ac b/configure.ac
index 46eef3a7..b18d5a49 100644
--- a/configure.ac
+++ b/configure.ac
@@ -18,7 +18,7 @@
# This configure file is in the public domain
AC_PREREQ([2.69])
-AC_INIT([taler-merchant],[0.10.2],[taler-bug@gnunet.org])
+AC_INIT([taler-merchant],[0.11.3],[taler-bug@gnunet.org])
AC_CONFIG_SRCDIR([src/backend/taler-merchant-httpd.c])
AC_CONFIG_HEADERS([taler_merchant_config.h])
# support for non-recursive builds
diff --git a/contrib/wallet-core b/contrib/wallet-core
-Subproject 240d647da85de6b575d15c37efec04757541e3d
+Subproject f9b0af55874c1d49e3c04fd8a64ed10a7236a55
diff --git a/debian/changelog b/debian/changelog
index c04b84f3..ba0c5f38 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,28 @@
+taler-merchant (0.11.3) unstable; urgency=low
+
+ * Importing bank account info.
+
+ -- Sebastian Marchano <sebasjm@taler.net> Mon 3 Jun 2024 13:20:12 -0300
+
+taler-merchant (0.11.2) unstable; urgency=low
+
+ * Fixed bad bug in SQL procedure.
+
+ -- Christian Grothoff <grothoff@gnu.org> Wed 22 Apr 2024 19:50:12 +0200
+
+taler-merchant (0.11.1) unstable; urgency=low
+
+ * Fixed issues with restarts in service files.
+ * Bumped dependencies.
+
+ -- Christian Grothoff <grothoff@gnu.org> Tue, 21 Apr 2024 14:50:12 +0200
+
+taler-merchant (0.11.0) unstable; urgency=low
+
+ * Releasing v0.11.0.
+
+ -- Christian Grothoff <grothoff@gnu.org> Wed, 15 May 2024 19:50:12 +0200
+
taler-merchant (0.10.2) unstable; urgency=low
* Update various submodules to latest version.
diff --git a/debian/control b/debian/control
index 6fa6e6c1..670adf53 100644
--- a/debian/control
+++ b/debian/control
@@ -9,8 +9,8 @@ Build-Depends:
debhelper-compat (= 12),
gettext,
libgnunet-dev (>=0.21),
- libtalerexchange-dev (>=0.10.2),
- libpq-dev (>=14.0),
+ libtalerexchange-dev (>=0.11.0),
+ libpq-dev (>=15.0),
po-debconf,
libqrencode-dev,
zlib1g-dev,
@@ -48,14 +48,14 @@ Pre-Depends:
${misc:Pre-Depends}
Depends:
libtalermerchant (= ${binary:Version}),
- libtalerexchange (>= 0.10.2),
+ libtalerexchange (>= 0.11.0),
adduser,
lsb-base,
netbase,
${misc:Depends},
${shlibs:Depends}
Recommends:
- postgresql (>=14.0),
+ postgresql (>=15.0),
taler-terms-generator,
apache2 | nginx | httpd
Description: GNU's payment system merchant backend.
@@ -69,7 +69,7 @@ Package: libtalermerchant-dev
Section: libdevel
Architecture: any
Depends:
- libtalerexchange-dev (>= 0.10.2),
+ libtalerexchange-dev (>= 0.11.0),
libgnunet-dev (>=0.21),
${misc:Depends},
${shlibs:Depends}
diff --git a/debian/taler-merchant.taler-merchant-depositcheck.service b/debian/taler-merchant.taler-merchant-depositcheck.service
index bc5b84c0..37ac7c37 100644
--- a/debian/taler-merchant.taler-merchant-depositcheck.service
+++ b/debian/taler-merchant.taler-merchant-depositcheck.service
@@ -8,7 +8,7 @@ Type=simple
Restart=always
RestartMode=direct
RestartSec=1s
-RestartPreventExitStatus=2 3 4 5 6 9
+RestartPreventExitStatus=9
ExecStart=/usr/bin/taler-merchant-depositcheck -c /etc/taler/taler.conf -L INFO
PrivateTmp=yes
PrivateDevices=yes
diff --git a/debian/taler-merchant.taler-merchant-exchange.service b/debian/taler-merchant.taler-merchant-exchange.service
index 4d368c3b..f61dc546 100644
--- a/debian/taler-merchant.taler-merchant-exchange.service
+++ b/debian/taler-merchant.taler-merchant-exchange.service
@@ -3,12 +3,12 @@ Description=GNU Taler merchant-exchange transaction reconciliation service
After=postgres.service
[Service]
-User=taler-merchant-exchange
+User=taler-merchant-httpd
Type=simple
Restart=always
RestartMode=direct
RestartSec=1s
-RestartPreventExitStatus=2 3 4 5 6 9
+RestartPreventExitStatus=9
ExecStart=/usr/bin/taler-merchant-exchange -c /etc/taler/taler.conf -L INFO
PrivateTmp=yes
PrivateDevices=yes
diff --git a/debian/taler-merchant.taler-merchant-webhook.service b/debian/taler-merchant.taler-merchant-webhook.service
index e71bb5c8..06a6a4a4 100644
--- a/debian/taler-merchant.taler-merchant-webhook.service
+++ b/debian/taler-merchant.taler-merchant-webhook.service
@@ -8,7 +8,7 @@ Type=simple
Restart=always
RestartMode=direct
RestartSec=1s
-RestartPreventExitStatus=2 3 4 5 6 9
+RestartPreventExitStatus=9
ExecStart=/usr/bin/taler-merchant-webhook -c /etc/taler/taler.conf -L INFO
PrivateTmp=yes
PrivateDevices=yes
diff --git a/debian/taler-merchant.taler-merchant-wirewatch.service b/debian/taler-merchant.taler-merchant-wirewatch.service
index 8b61d68e..c1ccba5a 100644
--- a/debian/taler-merchant.taler-merchant-wirewatch.service
+++ b/debian/taler-merchant.taler-merchant-wirewatch.service
@@ -8,7 +8,7 @@ Type=simple
Restart=always
RestartMode=direct
RestartSec=1s
-RestartPreventExitStatus=2 3 4 5 6 9
+RestartPreventExitStatus=9
ExecStart=/usr/bin/taler-merchant-wirewatch -c /etc/taler/taler.conf -L INFO
PrivateTmp=yes
PrivateDevices=yes
diff --git a/doc/doxygen/taler.doxy b/doc/doxygen/taler.doxy
index e5185031..4394cfd4 100644
--- a/doc/doxygen/taler.doxy
+++ b/doc/doxygen/taler.doxy
@@ -5,7 +5,7 @@
#---------------------------------------------------------------------------
DOXYFILE_ENCODING = UTF-8
PROJECT_NAME = "GNU Taler: Merchant"
-PROJECT_NUMBER = 0.10.2
+PROJECT_NUMBER = 0.11.0
PROJECT_LOGO = logo.svg
OUTPUT_DIRECTORY = .
CREATE_SUBDIRS = YES
diff --git a/doc/prebuilt b/doc/prebuilt
-Subproject b8d2d2fa2ed2a771880f451725176f256583cb2
+Subproject c1a885ac1f9d9b2fdef19a976bed01dab4f7d85
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
index dbc7cde8..cce7faec 100644
--- a/src/backend/Makefile.am
+++ b/src/backend/Makefile.am
@@ -37,6 +37,8 @@ taler_merchant_httpd_SOURCES = \
taler-merchant-httpd_mhd.h \
taler-merchant-httpd_private-delete-account-ID.c \
taler-merchant-httpd_private-delete-account-ID.h \
+ taler-merchant-httpd_private-delete-categories-ID.c \
+ taler-merchant-httpd_private-delete-categories-ID.h \
taler-merchant-httpd_private-delete-instances-ID.c \
taler-merchant-httpd_private-delete-instances-ID.h \
taler-merchant-httpd_private-delete-instances-ID-token.c \
@@ -59,12 +61,18 @@ taler_merchant_httpd_SOURCES = \
taler-merchant-httpd_private-get-accounts.h \
taler-merchant-httpd_private-get-accounts-ID.c \
taler-merchant-httpd_private-get-accounts-ID.h \
+ taler-merchant-httpd_private-get-categories.c \
+ taler-merchant-httpd_private-get-categories.h \
+ taler-merchant-httpd_private-get-categories-ID.c \
+ taler-merchant-httpd_private-get-categories-ID.h \
taler-merchant-httpd_private-get-instances.c \
taler-merchant-httpd_private-get-instances.h \
taler-merchant-httpd_private-get-instances-ID.c \
taler-merchant-httpd_private-get-instances-ID.h \
taler-merchant-httpd_private-get-instances-ID-kyc.c \
taler-merchant-httpd_private-get-instances-ID-kyc.h \
+ taler-merchant-httpd_private-get-pos.c \
+ taler-merchant-httpd_private-get-pos.h \
taler-merchant-httpd_private-get-products.c \
taler-merchant-httpd_private-get-products.h \
taler-merchant-httpd_private-get-products-ID.c \
@@ -93,6 +101,8 @@ taler_merchant_httpd_SOURCES = \
taler-merchant-httpd_private-get-webhooks-ID.h \
taler-merchant-httpd_private-patch-accounts-ID.c \
taler-merchant-httpd_private-patch-accounts-ID.h \
+ taler-merchant-httpd_private-patch-categories-ID.c \
+ taler-merchant-httpd_private-patch-categories-ID.h \
taler-merchant-httpd_private-patch-instances-ID.c \
taler-merchant-httpd_private-patch-instances-ID.h \
taler-merchant-httpd_private-patch-orders-ID-forget.c \
@@ -109,6 +119,8 @@ taler_merchant_httpd_SOURCES = \
taler-merchant-httpd_private-patch-webhooks-ID.h \
taler-merchant-httpd_private-post-account.c \
taler-merchant-httpd_private-post-account.h \
+ taler-merchant-httpd_private-post-categories.c \
+ taler-merchant-httpd_private-post-categories.h \
taler-merchant-httpd_private-post-instances.c \
taler-merchant-httpd_private-post-instances.h \
taler-merchant-httpd_private-post-instances-ID-auth.c \
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c
index 7384bfc9..e9ee1cbf 100644
--- a/src/backend/taler-merchant-httpd.c
+++ b/src/backend/taler-merchant-httpd.c
@@ -34,6 +34,7 @@
#include "taler-merchant-httpd_get-templates-ID.h"
#include "taler-merchant-httpd_mhd.h"
#include "taler-merchant-httpd_private-delete-account-ID.h"
+#include "taler-merchant-httpd_private-delete-categories-ID.h"
#include "taler-merchant-httpd_private-delete-instances-ID.h"
#include "taler-merchant-httpd_private-delete-instances-ID-token.h"
#include "taler-merchant-httpd_private-delete-products-ID.h"
@@ -45,9 +46,12 @@
#include "taler-merchant-httpd_private-delete-webhooks-ID.h"
#include "taler-merchant-httpd_private-get-accounts.h"
#include "taler-merchant-httpd_private-get-accounts-ID.h"
+#include "taler-merchant-httpd_private-get-categories.h"
+#include "taler-merchant-httpd_private-get-categories-ID.h"
#include "taler-merchant-httpd_private-get-instances.h"
#include "taler-merchant-httpd_private-get-instances-ID.h"
#include "taler-merchant-httpd_private-get-instances-ID-kyc.h"
+#include "taler-merchant-httpd_private-get-pos.h"
#include "taler-merchant-httpd_private-get-products.h"
#include "taler-merchant-httpd_private-get-products-ID.h"
#include "taler-merchant-httpd_private-get-orders.h"
@@ -62,6 +66,7 @@
#include "taler-merchant-httpd_private-get-webhooks.h"
#include "taler-merchant-httpd_private-get-webhooks-ID.h"
#include "taler-merchant-httpd_private-patch-accounts-ID.h"
+#include "taler-merchant-httpd_private-patch-categories-ID.h"
#include "taler-merchant-httpd_private-patch-instances-ID.h"
#include "taler-merchant-httpd_private-patch-orders-ID-forget.h"
#include "taler-merchant-httpd_private-patch-otp-devices-ID.h"
@@ -70,6 +75,7 @@
#include "taler-merchant-httpd_private-patch-token-families-SLUG.h"
#include "taler-merchant-httpd_private-patch-webhooks-ID.h"
#include "taler-merchant-httpd_private-post-account.h"
+#include "taler-merchant-httpd_private-post-categories.h"
#include "taler-merchant-httpd_private-post-instances.h"
#include "taler-merchant-httpd_private-post-instances-ID-auth.h"
#include "taler-merchant-httpd_private-post-instances-ID-token.h"
@@ -932,6 +938,52 @@ url_handler (void *cls,
.method = MHD_HTTP_METHOD_GET,
.handler = &TMH_private_get_instances_ID_kyc,
},
+ /* GET /pos: */
+ {
+ .url_prefix = "/pos",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler = &TMH_private_get_pos
+ },
+ /* GET /categories: */
+ {
+ .url_prefix = "/categories",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler = &TMH_private_get_categories
+ },
+ /* POST /categories: */
+ {
+ .url_prefix = "/categories",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler = &TMH_private_post_categories,
+ /* allow category data of up to 8 kb, that should be plenty */
+ .max_upload = 1024 * 8
+ },
+ /* GET /categories/$ID: */
+ {
+ .url_prefix = "/categories/",
+ .method = MHD_HTTP_METHOD_GET,
+ .have_id_segment = true,
+ .allow_deleted_instance = true,
+ .handler = &TMH_private_get_categories_ID
+ },
+ /* DELETE /categories/$ID: */
+ {
+ .url_prefix = "/categories/",
+ .method = MHD_HTTP_METHOD_DELETE,
+ .have_id_segment = true,
+ .allow_deleted_instance = true,
+ .handler = &TMH_private_delete_categories_ID
+ },
+ /* PATCH /categories/$ID/: */
+ {
+ .url_prefix = "/categories/",
+ .method = MHD_HTTP_METHOD_PATCH,
+ .have_id_segment = true,
+ .allow_deleted_instance = true,
+ .handler = &TMH_private_patch_categories_ID,
+ /* allow category data of up to 8 kb, that should be plenty */
+ .max_upload = 1024 * 8
+ },
/* GET /products: */
{
.url_prefix = "/products",
@@ -949,7 +1001,7 @@ url_handler (void *cls,
in the code... */
.max_upload = 1024 * 1024 * 8
},
- /* GET /products/$ID/: */
+ /* GET /products/$ID: */
{
.url_prefix = "/products/",
.method = MHD_HTTP_METHOD_GET,
diff --git a/src/backend/taler-merchant-httpd_config.c b/src/backend/taler-merchant-httpd_config.c
index d1340249..10f0cd39 100644
--- a/src/backend/taler-merchant-httpd_config.c
+++ b/src/backend/taler-merchant-httpd_config.c
@@ -42,7 +42,7 @@
* #MERCHANT_PROTOCOL_CURRENT and #MERCHANT_PROTOCOL_AGE in
* merchant_api_config.c!
*/
-#define MERCHANT_PROTOCOL_VERSION "14:1:10"
+#define MERCHANT_PROTOCOL_VERSION "16:0:12"
/**
diff --git a/src/backend/taler-merchant-httpd_private-delete-categories-ID.c b/src/backend/taler-merchant-httpd_private-delete-categories-ID.c
new file mode 100644
index 00000000..892dbd9c
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-delete-categories-ID.c
@@ -0,0 +1,92 @@
+/*
+ This file is part of TALER
+ (C) 2024 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 taler-merchant-httpd_private-delete-categories-ID.c
+ * @brief implement DELETE /private/categories/$ID
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-delete-categories-ID.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Handle a DELETE "/categories/$ID" request.
+ *
+ * @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_delete_categories_ID (
+ const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ enum GNUNET_DB_QueryStatus qs;
+ unsigned long long cnum;
+ char dummy;
+
+ (void) rh;
+ GNUNET_assert (NULL != mi);
+ GNUNET_assert (NULL != hc->infix);
+ if (1 != sscanf (hc->infix,
+ "%llu%c",
+ &cnum,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "category_id must be a number");
+ }
+ qs = TMH_db->delete_category (TMH_db->cls,
+ mi->settings.id,
+ cnum);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "delete_category");
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "delete_category");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_CATEGORY_UNKNOWN,
+ hc->infix);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ return TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ }
+ GNUNET_assert (0);
+ return MHD_NO;
+}
+
+
+/* end of taler-merchant-httpd_private-delete-categories-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-delete-categories-ID.h b/src/backend/taler-merchant-httpd_private-delete-categories-ID.h
new file mode 100644
index 00000000..b17eed49
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-delete-categories-ID.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ (C) 2024 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 taler-merchant-httpd_private-delete-categories-ID.h
+ * @brief implement DELETE /private/categories/$ID/
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_DELETE_CATEGORIES_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_DELETE_CATEGORIES_ID_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a DELETE "/categories/$ID" request.
+ *
+ * @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_delete_categories_ID (
+ const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+/* end of taler-merchant-httpd_private-delete-categories-ID.h */
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-delete-otp-devices-ID.c b/src/backend/taler-merchant-httpd_private-delete-otp-devices-ID.c
index b147b84f..ff9ad2ef 100644
--- a/src/backend/taler-merchant-httpd_private-delete-otp-devices-ID.c
+++ b/src/backend/taler-merchant-httpd_private-delete-otp-devices-ID.c
@@ -61,7 +61,7 @@ TMH_private_delete_otp_devices_ID (const struct TMH_RequestHandler *rh,
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_TEMPLATE_UNKNOWN,
+ TALER_EC_MERCHANT_GENERIC_OTP_DEVICE_UNKNOWN,
hc->infix);
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
return TALER_MHD_reply_static (connection,
diff --git a/src/backend/taler-merchant-httpd_private-delete-products-ID.c b/src/backend/taler-merchant-httpd_private-delete-products-ID.c
index 7d314785..23b6de3c 100644
--- a/src/backend/taler-merchant-httpd_private-delete-products-ID.c
+++ b/src/backend/taler-merchant-httpd_private-delete-products-ID.c
@@ -59,22 +59,30 @@ TMH_private_delete_products_ID (const struct TMH_RequestHandler *rh,
TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
"delete_product (soft)");
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* check if deletion must have failed because of locks by
- checking if the product exists */
- qs = TMH_db->lookup_product (TMH_db->cls,
- mi->settings.id,
- hc->infix,
- NULL);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "lookup_product");
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN,
- hc->infix);
+ {
+ size_t num_categories = 0;
+ uint64_t *categories = NULL;
+
+ /* check if deletion must have failed because of locks by
+ checking if the product exists */
+ qs = TMH_db->lookup_product (TMH_db->cls,
+ mi->settings.id,
+ hc->infix,
+ NULL,
+ &num_categories,
+ &categories);
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "lookup_product");
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN,
+ hc->infix);
+ GNUNET_free (categories);
+ }
return TALER_MHD_reply_with_error (
connection,
MHD_HTTP_CONFLICT,
diff --git a/src/backend/taler-merchant-httpd_private-get-categories-ID.c b/src/backend/taler-merchant-httpd_private-get-categories-ID.c
new file mode 100644
index 00000000..02ef3495
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-categories-ID.c
@@ -0,0 +1,119 @@
+/*
+ This file is part of TALER
+ (C) 2022-2024 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 taler-merchant-httpd_private-get-categories-ID.c
+ * @brief implement GET /private/categories/$ID
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-categories-ID.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Handle a GET "/private/categories/$ID" request.
+ *
+ * @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_get_categories_ID (
+ const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ enum GNUNET_DB_QueryStatus qs;
+ unsigned long long cnum;
+ char dummy;
+ struct TALER_MERCHANTDB_CategoryDetails cd;
+
+ GNUNET_assert (NULL != mi);
+ GNUNET_assert (NULL != hc->infix);
+ if (1 != sscanf (hc->infix,
+ "%llu%c",
+ &cnum,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "category_id must be a number");
+ }
+
+ qs = TMH_db->select_category (TMH_db->cls,
+ mi->settings.id,
+ cnum,
+ &cd);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_category");
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_CATEGORY_UNKNOWN,
+ hc->infix);
+ }
+ {
+ MHD_RESULT ret;
+ json_t *products;
+
+ products = json_array ();
+ GNUNET_assert (NULL != products);
+ for (unsigned int i = 0; i<cd.num_products; i++)
+ {
+ const struct TALER_MERCHANTDB_ProductSummary *product
+ = &cd.products[i];
+ json_t *jprod;
+
+ jprod = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("product_id",
+ product->product_id),
+ GNUNET_JSON_pack_string ("description",
+ product->description),
+ GNUNET_JSON_pack_object_steal ("description_i18n",
+ product->description_i18n));
+ GNUNET_assert (0 ==
+ json_array_append_new (products,
+ jprod));
+ }
+
+ ret = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("name",
+ cd.category_name),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("name_i18n",
+ cd.category_name_i18n)),
+ GNUNET_JSON_pack_array_steal ("products",
+ products));
+ TALER_MERCHANTDB_category_details_free (&cd);
+ return ret;
+ }
+}
+
+
+/* end of taler-merchant-httpd_private-get-categories-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-get-categories-ID.h b/src/backend/taler-merchant-httpd_private-get-categories-ID.h
new file mode 100644
index 00000000..c0226659
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-categories-ID.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ (C) 2024 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 taler-merchant-httpd_private-get-categories-ID.h
+ * @brief implement GET /private/categories/$ID/
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_CATEGORIES_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_CATEGORIES_ID_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a GET "/private/categories/$ID" request.
+ *
+ * @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_get_categories_ID (
+ const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+/* end of taler-merchant-httpd_private-get-categories-ID.h */
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-categories.c b/src/backend/taler-merchant-httpd_private-get-categories.c
new file mode 100644
index 00000000..8ebccb2b
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-categories.c
@@ -0,0 +1,93 @@
+/*
+ This file is part of TALER
+ (C) 2024 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 taler-merchant-httpd_private-get-categories.c
+ * @brief implement GET /categories
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-categories.h"
+
+
+/**
+ * Add category details to our JSON array.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @param category_id ID of the category
+ * @param category_name name of the category
+ * @param category_name_i18n translations of the @a category_name
+ * @param product_count number of products in the category
+ */
+static void
+add_category (void *cls,
+ uint64_t category_id,
+ const char *category_name,
+ const json_t *category_name_i18n,
+ uint64_t product_count)
+{
+ json_t *pa = cls;
+
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ pa,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 (
+ "category_id",
+ category_id),
+ GNUNET_JSON_pack_string (
+ "name",
+ category_name),
+ GNUNET_JSON_pack_object_incref (
+ "name_i18n",
+ (json_t *) category_name_i18n),
+ GNUNET_JSON_pack_uint64 (
+ "product_count",
+ product_count))));
+}
+
+
+MHD_RESULT
+TMH_private_get_categories (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ json_t *pa;
+ enum GNUNET_DB_QueryStatus qs;
+
+ pa = json_array ();
+ GNUNET_assert (NULL != pa);
+ qs = TMH_db->lookup_categories (TMH_db->cls,
+ hc->instance->settings.id,
+ &add_category,
+ pa);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ json_decref (pa);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ }
+ return TALER_MHD_REPLY_JSON_PACK (connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("categories",
+ pa));
+}
+
+
+/* end of taler-merchant-httpd_private-get-categories.c */
diff --git a/src/backend/taler-merchant-httpd_private-get-categories.h b/src/backend/taler-merchant-httpd_private-get-categories.h
new file mode 100644
index 00000000..68eed05e
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-categories.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ (C) 2024 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 taler-merchant-httpd_private-get-categories.h
+ * @brief implement GET /private/categories
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_CATEGORIES_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_CATEGORIES_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a GET "/private/categories" request.
+ *
+ * @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_get_categories (
+ const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+/* end of taler-merchant-httpd_private-get-categories.h */
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c b/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c
index 8a338e7d..0382b742 100644
--- a/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c
+++ b/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c
@@ -684,7 +684,7 @@ kyc_with_exchange (void *cls,
keys,
ekr->exchange_kyc_serial,
&h_payto,
- ekr->kc->mi->settings.ut,
+ 1, /* FIXME: this will go away! */
GNUNET_TIME_absolute_get_remaining (kc->timeout),
&exchange_check_cb,
ekr);
diff --git a/src/backend/taler-merchant-httpd_private-get-instances-ID.c b/src/backend/taler-merchant-httpd_private-get-instances-ID.c
index adc99c39..c8cd2c12 100644
--- a/src/backend/taler-merchant-httpd_private-get-instances-ID.c
+++ b/src/backend/taler-merchant-httpd_private-get-instances-ID.c
@@ -79,7 +79,7 @@ get_instances_ID (struct TMH_MerchantInstance *mi,
mi->settings.name),
GNUNET_JSON_pack_string (
"user_type",
- TALER_KYCLOGIC_kyc_user_type2s (mi->settings.ut)),
+ "business"),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("website",
mi->settings.website)),
diff --git a/src/backend/taler-merchant-httpd_private-get-instances.c b/src/backend/taler-merchant-httpd_private-get-instances.c
index 50b5c0c2..0220d4df 100644
--- a/src/backend/taler-merchant-httpd_private-get-instances.c
+++ b/src/backend/taler-merchant-httpd_private-get-instances.c
@@ -76,7 +76,7 @@ add_instance (void *cls,
mi->settings.name),
GNUNET_JSON_pack_string (
"user_type",
- TALER_KYCLOGIC_kyc_user_type2s (mi->settings.ut)),
+ "business"),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("website",
mi->settings.website)),
diff --git a/src/backend/taler-merchant-httpd_private-get-pos.c b/src/backend/taler-merchant-httpd_private-get-pos.c
new file mode 100644
index 00000000..4c14b6ae
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-pos.c
@@ -0,0 +1,148 @@
+/*
+ This file is part of TALER
+ (C) 2019, 2020, 2021, 2024 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 taler-merchant-httpd_private-get-pos.c
+ * @brief implement GET /private/pos
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-pos.h"
+
+
+/**
+ * Closure for add_product().
+ */
+struct Context
+{
+ /**
+ * JSON array of products we are building.
+ */
+ json_t *pa;
+
+ /**
+ * JSON array of categories we are building.
+ */
+ json_t *ca;
+
+};
+
+
+/**
+ * Add product details to our JSON array.
+ *
+ * @param ctx a `struct Context` with JSON arrays to build
+ * @param product_id ID of the product
+ */
+static void
+add_product (void *cls,
+ uint64_t product_serial,
+ const char *product_id,
+ const struct TALER_MERCHANTDB_ProductDetails *pd,
+ size_t num_categories,
+ const uint64_t *categories)
+{
+ struct Context *ctx = cls;
+ json_t *pa = ctx->pa;
+ json_t *cata;
+
+ cata = json_array ();
+ GNUNET_assert (NULL != cata);
+ for (size_t i = 0; i<num_categories; i++)
+ GNUNET_assert (
+ 0 == json_array_append_new (
+ cata,
+ json_integer (categories[i])));
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ pa,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("description",
+ pd->description),
+ GNUNET_JSON_pack_object_incref ("description_i18n",
+ (json_t *) pd->description_i18n),
+ GNUNET_JSON_pack_string ("unit",
+ pd->unit),
+ TALER_JSON_pack_amount ("price",
+ &pd->price),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("image",
+ pd->image)),
+ GNUNET_JSON_pack_array_steal ("categories",
+ cata),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_incref ("taxes",
+ (json_t *) pd->taxes)),
+ (INT64_MAX == pd->total_stock)
+ ? GNUNET_JSON_pack_int64 ("total_stock",
+ pd->total_stock)
+ : GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("total_stock",
+ NULL)),
+ GNUNET_JSON_pack_uint64 ("minimum_age",
+ pd->minimum_age),
+ GNUNET_JSON_pack_uint64 ("product_serial",
+ product_serial),
+ GNUNET_JSON_pack_string ("product_id",
+ product_id))));
+}
+
+
+MHD_RESULT
+TMH_private_get_pos (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct Context ctx;
+ enum GNUNET_DB_QueryStatus qs;
+
+ ctx.pa = json_array ();
+ GNUNET_assert (NULL != ctx.pa);
+ ctx.ca = json_array ();
+ GNUNET_assert (NULL != ctx.ca);
+ GNUNET_assert (
+ 0 == json_array_append_new (
+ ctx.ca,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("id",
+ 0),
+ GNUNET_JSON_pack_string ("name",
+ "default"))));
+ qs = TMH_db->lookup_all_products (TMH_db->cls,
+ hc->instance->settings.id,
+ &add_product,
+ &ctx);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ json_decref (ctx.pa);
+ json_decref (ctx.ca);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("categories",
+ ctx.ca),
+ GNUNET_JSON_pack_array_steal ("products",
+ ctx.pa));
+}
+
+
+/* end of taler-merchant-httpd_private-get-pos.c */
diff --git a/src/backend/taler-merchant-httpd_private-get-pos.h b/src/backend/taler-merchant-httpd_private-get-pos.h
new file mode 100644
index 00000000..ce266823
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-get-pos.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ (C) 2024 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 taler-merchant-httpd_private-get-pos.h
+ * @brief implement GET /pos
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_POS_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_POS_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a GET "/pos" request.
+ *
+ * @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_get_pos (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+/* end of taler-merchant-httpd_private-get-pos.h */
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-products-ID.c b/src/backend/taler-merchant-httpd_private-get-products-ID.c
index 1406349f..0729b1df 100644
--- a/src/backend/taler-merchant-httpd_private-get-products-ID.c
+++ b/src/backend/taler-merchant-httpd_private-get-products-ID.c
@@ -32,19 +32,25 @@
* @return MHD result code
*/
MHD_RESULT
-TMH_private_get_products_ID (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
+TMH_private_get_products_ID (
+ const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
{
struct TMH_MerchantInstance *mi = hc->instance;
struct TALER_MERCHANTDB_ProductDetails pd = { 0 };
enum GNUNET_DB_QueryStatus qs;
+ size_t num_categories = 0;
+ uint64_t *categories = NULL;
+ json_t *jcategories;
GNUNET_assert (NULL != mi);
qs = TMH_db->lookup_product (TMH_db->cls,
mi->settings.id,
hc->infix,
- &pd);
+ &pd,
+ &num_categories,
+ &categories);
if (0 > qs)
{
GNUNET_break (0);
@@ -60,6 +66,16 @@ TMH_private_get_products_ID (const struct TMH_RequestHandler *rh,
TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN,
hc->infix);
}
+ jcategories = json_array ();
+ GNUNET_assert (NULL != jcategories);
+ for (size_t i = 0; i<num_categories; i++)
+ {
+ GNUNET_assert (0 ==
+ json_array_append_new (jcategories,
+ json_integer (categories[i])));
+ }
+ GNUNET_free (categories);
+
{
MHD_RESULT ret;
@@ -72,12 +88,16 @@ TMH_private_get_products_ID (const struct TMH_RequestHandler *rh,
pd.description_i18n),
GNUNET_JSON_pack_string ("unit",
pd.unit),
+ GNUNET_JSON_pack_array_steal ("categories",
+ jcategories),
TALER_JSON_pack_amount ("price",
&pd.price),
- GNUNET_JSON_pack_string ("image",
- pd.image),
- GNUNET_JSON_pack_array_steal ("taxes",
- pd.taxes),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("image",
+ pd.image)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_steal ("taxes",
+ pd.taxes)),
GNUNET_JSON_pack_int64 ("total_stock",
(INT64_MAX == pd.total_stock)
? -1LL
@@ -86,8 +106,9 @@ TMH_private_get_products_ID (const struct TMH_RequestHandler *rh,
pd.total_sold),
GNUNET_JSON_pack_uint64 ("total_lost",
pd.total_lost),
- GNUNET_JSON_pack_object_steal ("address",
- pd.address),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_steal ("address",
+ pd.address)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_timestamp ("next_restock",
(pd.next_restock))),
diff --git a/src/backend/taler-merchant-httpd_private-patch-categories-ID.c b/src/backend/taler-merchant-httpd_private-patch-categories-ID.c
new file mode 100644
index 00000000..1aa489cf
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-patch-categories-ID.c
@@ -0,0 +1,120 @@
+/*
+ This file is part of TALER
+ (C) 2024 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 taler-merchant-httpd_private-patch-categories-ID.c
+ * @brief implementing PATCH /categories/$ID request handling
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-patch-categories-ID.h"
+#include "taler-merchant-httpd_helper.h"
+#include <taler/taler_json_lib.h>
+
+
+MHD_RESULT
+TMH_private_patch_categories_ID (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ unsigned long long cnum;
+ char dummy;
+ const char *category_name;
+ const json_t *category_name_i18n;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("name",
+ &category_name),
+ GNUNET_JSON_spec_object_const ("name_i18n",
+ &category_name_i18n),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_assert (NULL != mi);
+ GNUNET_assert (NULL != hc->infix);
+ if (1 != sscanf (hc->infix,
+ "%llu%c",
+ &cnum,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "category_id must be a number");
+ }
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ hc->request_body,
+ spec);
+ if (GNUNET_OK != res)
+ return (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO;
+ }
+
+ qs = TMH_db->update_category (TMH_db->cls,
+ mi->settings.id,
+ cnum,
+ category_name,
+ category_name_i18n);
+ {
+ MHD_RESULT ret = MHD_NO;
+
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "update_category");
+ 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_INTERNAL_INVARIANT_FAILURE,
+ "unexpected serialization problem");
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_CATEGORY_UNKNOWN,
+ category_name);
+ 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-categories-ID.c */
diff --git a/src/backend/taler-merchant-httpd_private-patch-categories-ID.h b/src/backend/taler-merchant-httpd_private-patch-categories-ID.h
new file mode 100644
index 00000000..c88290a8
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-patch-categories-ID.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ (C) 2024 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 taler-merchant-httpd_private-patch-categories-ID.h
+ * @brief implementing PATCH /private/categories/$ID request handling
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_PATCH_CATEGORIES_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_PATCH_CATEGORIES_ID_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * PATCH descriptions of an existing product category.
+ *
+ * @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_categories_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-instances-ID.c b/src/backend/taler-merchant-httpd_private-patch-instances-ID.c
index 027d5869..70df009e 100644
--- a/src/backend/taler-merchant-httpd_private-patch-instances-ID.c
+++ b/src/backend/taler-merchant-httpd_private-patch-instances-ID.c
@@ -64,17 +64,12 @@ patch_instances_ID (struct TMH_MerchantInstance *mi,
{
struct TALER_MERCHANTDB_InstanceSettings is;
const char *name;
- const char *uts = "business";
struct TMH_WireMethod *wm_head = NULL;
struct TMH_WireMethod *wm_tail = NULL;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("name",
&name),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("user_type",
- &uts),
- NULL),
- GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("website",
(const char **) &is.website),
NULL),
@@ -115,19 +110,6 @@ patch_instances_ID (struct TMH_MerchantInstance *mi,
? MHD_YES
: MHD_NO;
}
- if (NULL == uts)
- uts = "business";
- if (GNUNET_OK !=
- TALER_KYCLOGIC_kyc_user_type_from_string (uts,
- &is.ut))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "user_type");
- }
if (! TMH_location_object_valid (is.address))
{
GNUNET_break_op (0);
@@ -185,7 +167,6 @@ patch_instances_ID (struct TMH_MerchantInstance *mi,
/* Check for equality of settings */
if (! ( (0 == strcmp (mi->settings.name,
name)) &&
- (mi->settings.ut == is.ut) &&
((mi->settings.email == is.email) ||
(NULL != is.email && NULL != mi->settings.email &&
0 == strcmp (mi->settings.email,
@@ -240,7 +221,6 @@ giveup:
json_decref (mi->settings.jurisdiction);
is.id = mi->settings.id;
mi->settings = is;
- // mi->settings.user_type = (enum TALER_KYCLOGIC_KycUserType) is.user_type;
mi->settings.address = json_incref (mi->settings.address);
mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction);
mi->settings.name = GNUNET_strdup (name);
diff --git a/src/backend/taler-merchant-httpd_private-patch-products-ID.c b/src/backend/taler-merchant-httpd_private-patch-products-ID.c
index 7bc327cd..6e50cced 100644
--- a/src/backend/taler-merchant-httpd_private-patch-products-ID.c
+++ b/src/backend/taler-merchant-httpd_private-patch-products-ID.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2020 Taler Systems SA
+ (C) 2020-2024 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
@@ -29,76 +29,6 @@
/**
- * 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:
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- NULL);
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- "unexpected serialization problem");
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN,
- product_id);
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break; /* do below */
- }
-
- {
- enum TALER_ErrorCode ec;
-
- ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
- if (pdx.total_lost > pd->total_lost)
- ec = TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_LOST_REDUCED;
- if (pdx.total_sold > pd->total_sold)
- ec = TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_SOLD_REDUCED;
- if (pdx.total_stock > pd->total_stock)
- ec = TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_STOCKED_REDUCED;
- TALER_MERCHANTDB_product_details_free (&pdx);
- GNUNET_break (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE != ec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_CONFLICT,
- ec,
- NULL);
- }
-}
-
-
-/**
* PATCH configuration of an existing instance, given its configuration.
*
* @param rh context of the handler
@@ -107,13 +37,15 @@ determine_cause (struct MHD_Connection *connection,
* @return MHD result code
*/
MHD_RESULT
-TMH_private_patch_products_ID (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
+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 = {0};
+ const json_t *categories = NULL;
int64_t total_stock;
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_JSON_Specification spec[] = {
@@ -135,6 +67,10 @@ TMH_private_patch_products_ID (const struct TMH_RequestHandler *rh,
GNUNET_JSON_spec_json ("taxes",
&pd.taxes),
NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("categories",
+ &categories),
+ NULL),
GNUNET_JSON_spec_int64 ("total_stock",
&total_stock),
GNUNET_JSON_spec_mark_optional (
@@ -155,6 +91,15 @@ TMH_private_patch_products_ID (const struct TMH_RequestHandler *rh,
NULL),
GNUNET_JSON_spec_end ()
};
+ MHD_RESULT ret;
+ size_t num_cats = 0;
+ uint64_t *cats = NULL;
+ bool no_instance;
+ ssize_t no_cat;
+ bool no_product;
+ bool lost_reduced;
+ bool sold_reduced;
+ bool stock_reduced;
pd.total_sold = 0; /* will be ignored anyway */
GNUNET_assert (NULL != mi);
@@ -173,11 +118,11 @@ TMH_private_patch_products_ID (const struct TMH_RequestHandler *rh,
if (total_stock < -1)
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "total_stock");
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "total_stock");
+ goto cleanup;
}
if (-1 == total_stock)
pd.total_stock = INT64_MAX;
@@ -189,23 +134,45 @@ TMH_private_patch_products_ID (const struct TMH_RequestHandler *rh,
if (! TMH_location_object_valid (pd.address))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "address");
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "address");
+ goto cleanup;
+ }
+ num_cats = json_array_size (categories);
+ cats = GNUNET_new_array (num_cats,
+ uint64_t);
+ {
+ size_t idx;
+ json_t *val;
+
+ json_array_foreach (categories, idx, val)
+ {
+ if (! json_is_integer (val))
+ {
+ GNUNET_break_op (0);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "categories");
+ goto cleanup;
+ }
+ cats[idx] = json_integer_value (val);
+ }
}
+
if (NULL == pd.description_i18n)
pd.description_i18n = json_object ();
if (! TALER_JSON_check_i18n (pd.description_i18n))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "description_i18n");
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "description_i18n");
+ goto cleanup;
}
if (NULL == pd.taxes)
@@ -214,74 +181,143 @@ TMH_private_patch_products_ID (const struct TMH_RequestHandler *rh,
if (! TMH_taxes_array_valid (pd.taxes))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "taxes");
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "taxes");
+ goto cleanup;
}
+
if (NULL == pd.image)
pd.image = "";
if (! TMH_image_data_url_valid (pd.image))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "image");
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "image");
+ goto cleanup;
}
+
if ( (pd.total_stock < pd.total_sold + pd.total_lost) ||
(pd.total_sold + pd.total_lost < pd.total_sold) /* integer overflow */)
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (
+ ret = TALER_MHD_reply_with_error (
connection,
MHD_HTTP_BAD_REQUEST,
TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_LOST_EXCEEDS_STOCKS,
NULL);
+ goto cleanup;
}
+
qs = TMH_db->update_product (TMH_db->cls,
mi->settings.id,
product_id,
- &pd);
+ &pd,
+ num_cats,
+ cats,
+ &no_instance,
+ &no_cat,
+ &no_product,
+ &lost_reduced,
+ &sold_reduced,
+ &stock_reduced);
+ switch (qs)
{
- MHD_RESULT ret = MHD_NO;
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ NULL);
+ goto cleanup;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "unexpected serialization problem");
+ goto cleanup;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "unexpected problem in stored procedure");
+ goto cleanup;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- NULL);
- 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_INTERNAL_INVARIANT_FAILURE,
- "unexpected serialization problem");
- 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;
+ if (no_instance)
+ {
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
+ mi->settings.id);
+ goto cleanup;
+ }
+ if (-1 != no_cat)
+ {
+ char cats[24];
+
+ GNUNET_snprintf (cats,
+ sizeof (cats),
+ "%llu",
+ (unsigned long long) no_cat);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_CATEGORY_UNKNOWN,
+ cats);
+ goto cleanup;
+ }
+ if (no_product)
+ {
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN,
+ product_id);
+ goto cleanup;
+ }
+ if (lost_reduced)
+ {
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_LOST_REDUCED,
+ NULL);
+ goto cleanup;
+ }
+ if (sold_reduced)
+ {
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_SOLD_REDUCED,
+ NULL);
+ goto cleanup;
+ }
+ if (stock_reduced)
+ {
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_PATCH_PRODUCTS_TOTAL_STOCKED_REDUCED,
+ NULL);
+ goto cleanup;
}
+ /* success! */
+ ret = TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+cleanup:
+ GNUNET_free (cats);
+ GNUNET_JSON_parse_free (spec);
+ return ret;
}
diff --git a/src/backend/taler-merchant-httpd_private-post-categories.c b/src/backend/taler-merchant-httpd_private-post-categories.c
new file mode 100644
index 00000000..675de765
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-categories.c
@@ -0,0 +1,170 @@
+/*
+ This file is part of TALER
+ (C) 2024 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 taler-merchant-httpd_private-post-categories.c
+ * @brief implementing POST /private/categories request handling
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-post-categories.h"
+#include "taler-merchant-httpd_helper.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * How often do we retry the simple INSERT database transaction?
+ */
+#define MAX_RETRIES 3
+
+
+MHD_RESULT
+TMH_private_post_categories (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ struct TMH_MerchantInstance *mi = hc->instance;
+ const char *category_name;
+ const json_t *category_name_i18n;
+ uint64_t category_id;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("name",
+ &category_name),
+ GNUNET_JSON_spec_object_const ("name_i18n",
+ &category_name_i18n),
+ 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);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break_op (0);
+ return (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO;
+ }
+ }
+
+ /* finally, interact with DB until no serialization error */
+ for (unsigned int i = 0; i<MAX_RETRIES; i++)
+ {
+ json_t *xcategory_name_i18n;
+
+ if (GNUNET_OK !=
+ TMH_db->start (TMH_db->cls,
+ "POST /categories"))
+ {
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ NULL);
+ }
+ qs = TMH_db->select_category_by_name (TMH_db->cls,
+ mi->settings.id,
+ category_name,
+ &xcategory_name_i18n,
+ &category_id);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ /* Clean up and fail hard */
+ GNUNET_break (0);
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ 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 etp == tp? */
+ {
+ bool eq;
+
+ eq = (1 == json_equal (xcategory_name_i18n,
+ category_name_i18n));
+ json_decref (xcategory_name_i18n);
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_JSON_parse_free (spec);
+ return eq
+ ? TALER_MHD_REPLY_JSON_PACK (connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_uint64 ("category_id",
+ category_id))
+ : TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_POST_CATEGORIES_CONFLICT_CATEGORY_EXISTS,
+ category_name);
+ }
+ } /* end switch (qs) */
+
+ qs = TMH_db->insert_category (TMH_db->cls,
+ mi->settings.id,
+ category_name,
+ category_name_i18n,
+ &category_id);
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ break;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ qs = TMH_db->commit (TMH_db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
+ break;
+ }
+retry:
+ GNUNET_assert (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ TMH_db->rollback (TMH_db->cls);
+ } /* for RETRIES loop */
+ GNUNET_JSON_parse_free (spec);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ ? TALER_EC_GENERIC_DB_SOFT_FAILURE
+ : TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ NULL);
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_uint64 ("category_id",
+ category_id));
+}
+
+
+/* end of taler-merchant-httpd_private-post-categories.c */
diff --git a/src/backend/taler-merchant-httpd_private-post-categories.h b/src/backend/taler-merchant-httpd_private-post-categories.h
new file mode 100644
index 00000000..a8431059
--- /dev/null
+++ b/src/backend/taler-merchant-httpd_private-post-categories.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ (C) 2024 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 taler-merchant-httpd_private-post-categories.h
+ * @brief implementing POST /categories request handling
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_CATEGORIES_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_POST_CATEGORIES_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Generate a product category.
+ *
+ * @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_categories (
+ const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-post-instances.c b/src/backend/taler-merchant-httpd_private-post-instances.c
index a4cf884d..f56e7098 100644
--- a/src/backend/taler-merchant-httpd_private-post-instances.c
+++ b/src/backend/taler-merchant-httpd_private-post-instances.c
@@ -103,20 +103,6 @@ TMH_private_post_instances (const struct TMH_RequestHandler *rh,
? MHD_YES
: MHD_NO;
}
- if (NULL == uts)
- uts = "business";
- if (GNUNET_OK !=
- TALER_KYCLOGIC_kyc_user_type_from_string (uts,
- &is.ut))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "user_type");
- }
-
{
enum GNUNET_GenericReturnValue ret;
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c
index eedece55..8bdae3f1 100644
--- a/src/backend/taler-merchant-httpd_private-post-orders.c
+++ b/src/backend/taler-merchant-httpd_private-post-orders.c
@@ -940,6 +940,8 @@ execute_order (struct OrderContext *oc)
struct TALER_MERCHANTDB_ProductDetails pd;
MHD_RESULT ret;
const struct InventoryProduct *ip;
+ size_t num_categories = 0;
+ uint64_t *categories = NULL;
ip = &oc->parse_request.inventory_products[
oc->execute_order.out_of_stock_index];
@@ -950,10 +952,13 @@ execute_order (struct OrderContext *oc)
TMH_db->cls,
oc->hc->instance->settings.id,
ip->product_id,
- &pd);
+ &pd,
+ &num_categories,
+ &categories);
switch (qs)
{
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ GNUNET_free (categories);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Order creation failed: product out of stock\n");
ret = TALER_MHD_REPLY_JSON_PACK (
@@ -1317,6 +1322,7 @@ get_exchange_keys (void *cls,
rx);
}
+
/**
* Fetch details about the token family with the given @a slug
* and add them to the list of token authorities. Check if the
@@ -1340,12 +1346,13 @@ set_token_authority (struct OrderContext *oc,
// TODO: make this configurable. This is the granularity of token
// expiration dates.
GNUNET_TIME_UNIT_DAYS
- );
+ );
qs = TMH_db->lookup_token_family_key (TMH_db->cls,
oc->hc->instance->settings.id,
slug,
- GNUNET_TIME_absolute_to_timestamp (min_start_date),
+ GNUNET_TIME_absolute_to_timestamp (
+ min_start_date),
start_date,
&key_details);
@@ -1390,9 +1397,10 @@ set_token_authority (struct OrderContext *oc,
struct GNUNET_CRYPTO_BlindSignPrivateKey *priv;
struct GNUNET_CRYPTO_BlindSignPublicKey *pub;
struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
- struct GNUNET_TIME_Timestamp valid_before = GNUNET_TIME_absolute_to_timestamp(
- GNUNET_TIME_absolute_add (now,
- key_details.token_family.duration));
+ struct GNUNET_TIME_Timestamp valid_before =
+ GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (now,
+ key_details.token_family.duration));
GNUNET_CRYPTO_blind_sign_keys_create (&priv,
&pub,
@@ -1411,13 +1419,16 @@ set_token_authority (struct OrderContext *oc,
slug,
&token_pub,
&token_priv,
- GNUNET_TIME_absolute_to_timestamp (now),
+ GNUNET_TIME_absolute_to_timestamp (now
+ ),
valid_before);
authority.token_expiration = valid_before;
authority.pub = &token_pub;
// GNUNET_CRYPTO_blind_sign_priv_decref (&token_priv.private_key);
- } else {
+ }
+ else
+ {
authority.token_expiration = key_details.valid_before;
authority.pub = key_details.pub;
}
@@ -1428,11 +1439,13 @@ set_token_authority (struct OrderContext *oc,
GNUNET_free (key_details.token_family.slug);
GNUNET_free (key_details.token_family.name);
- if (NULL != key_details.priv) {
+ if (NULL != key_details.priv)
+ {
GNUNET_CRYPTO_blind_sign_priv_decref (&key_details.priv->private_key);
}
- switch (key_details.token_family.kind) {
+ switch (key_details.token_family.kind)
+ {
case TALER_MERCHANTDB_TFK_Subscription:
authority.kind = TALER_MCTK_SUBSCRIPTION;
authority.details.subscription.start_date = key_details.valid_after;
@@ -1454,6 +1467,7 @@ set_token_authority (struct OrderContext *oc,
return MHD_YES;
}
+
/**
* Serialize order into @a oc->serialize_order.contract,
* ready to be stored in the database. Upon success, continue
@@ -1472,16 +1486,16 @@ serialize_order (struct OrderContext *oc)
merchant = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("name",
- settings->name),
+ settings->name),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("website",
- settings->website)),
+ settings->website)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("email",
- settings->email)),
+ settings->email)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("logo",
- settings->logo)));
+ settings->logo)));
GNUNET_assert (NULL != merchant);
{
json_t *loca;
@@ -1493,7 +1507,7 @@ serialize_order (struct OrderContext *oc)
loca = json_deep_copy (loca);
GNUNET_assert (NULL != loca);
GNUNET_assert (0 ==
- json_object_set_new (merchant,
+ json_object_set_new (merchant,
"address",
loca));
}
@@ -1508,7 +1522,7 @@ serialize_order (struct OrderContext *oc)
juri = json_deep_copy (juri);
GNUNET_assert (NULL != juri);
GNUNET_assert (0 ==
- json_object_set_new (merchant,
+ json_object_set_new (merchant,
"jurisdiction",
juri));
}
@@ -1516,7 +1530,8 @@ serialize_order (struct OrderContext *oc)
for (unsigned int i = 0; i<oc->parse_choices.authorities_len; i++)
{
- struct TALER_MerchantContractTokenAuthority *authority = &oc->parse_choices.authorities[i];
+ struct TALER_MerchantContractTokenAuthority *authority = &oc->parse_choices.
+ authorities[i];
// TODO: Finish spec to clearly define how token families are stored in
// ContractTerms.
@@ -1529,7 +1544,7 @@ serialize_order (struct OrderContext *oc)
// so it's clear with key is referenced.
json_t *jauthority = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("description",
- authority->description),
+ authority->description),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("description_i18n",
authority->description_i18n)),
@@ -1539,10 +1554,10 @@ serialize_order (struct OrderContext *oc)
&authority->pub->public_key.pub_key_hash),
GNUNET_JSON_pack_timestamp ("token_expiration",
authority->token_expiration)
- );
+ );
GNUNET_assert (0 ==
- json_object_set_new (token_types,
+ json_object_set_new (token_types,
authority->label,
jauthority));
}
@@ -1561,20 +1576,21 @@ serialize_order (struct OrderContext *oc)
json_t *jinput = GNUNET_JSON_PACK (
GNUNET_JSON_pack_int64 ("type",
input->type)
- );
+ );
if (TALER_MCIT_TOKEN == input->type)
{
- GNUNET_assert(0 ==
- json_object_set_new(jinput,
- "number",
- json_integer (
- input->details.token.count)));
- GNUNET_assert(0 ==
- json_object_set_new(jinput,
- "token_family_slug",
- json_string (
- input->details.token.token_family_slug)));
+ GNUNET_assert (0 ==
+ json_object_set_new (jinput,
+ "number",
+ json_integer (
+ input->details.token.count)));
+ GNUNET_assert (0 ==
+ json_object_set_new (jinput,
+ "token_family_slug",
+ json_string (
+ input->details.token.
+ token_family_slug)));
}
GNUNET_assert (0 == json_array_append_new (inputs, jinput));
@@ -1587,21 +1603,22 @@ serialize_order (struct OrderContext *oc)
json_t *joutput = GNUNET_JSON_PACK (
GNUNET_JSON_pack_int64 ("type",
output->type)
- );
+ );
if (TALER_MCOT_TOKEN == output->type)
{
- GNUNET_assert(0 ==
- json_object_set_new(joutput,
- "number",
- json_integer (
- output->details.token.count)));
-
- GNUNET_assert(0 ==
- json_object_set_new(joutput,
- "token_family_slug",
- json_string (
- output->details.token.token_family_slug)));
+ GNUNET_assert (0 ==
+ json_object_set_new (joutput,
+ "number",
+ json_integer (
+ output->details.token.count)));
+
+ GNUNET_assert (0 ==
+ json_object_set_new (joutput,
+ "token_family_slug",
+ json_string (
+ output->details.token.
+ token_family_slug)));
}
GNUNET_assert (0 == json_array_append (outputs, joutput));
@@ -1609,10 +1626,10 @@ serialize_order (struct OrderContext *oc)
json_t *jchoice = GNUNET_JSON_PACK (
GNUNET_JSON_pack_array_incref ("inputs",
- inputs),
+ inputs),
GNUNET_JSON_pack_array_incref ("outputs",
- outputs)
- );
+ outputs)
+ );
GNUNET_assert (0 == json_array_append (choices, jchoice));
}
@@ -1633,7 +1650,8 @@ serialize_order (struct OrderContext *oc)
oc->parse_order.fulfillment_message)),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("fulfillment_message_i18n",
- oc->parse_order.fulfillment_message_i18n)),
+ oc->parse_order.fulfillment_message_i18n))
+ ,
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_string ("fulfillment_url",
oc->parse_order.fulfillment_url)),
@@ -1674,12 +1692,12 @@ serialize_order (struct OrderContext *oc)
&oc->parse_order.brutto),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_array_incref ("choices",
- choices)
- ),
+ choices)
+ ),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("token_types",
token_types)
- ),
+ ),
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("extra",
(json_t *) oc->parse_order.extra))
@@ -1715,6 +1733,7 @@ serialize_order (struct OrderContext *oc)
oc->phase++;
}
+
/**
* Set max_fee in @a oc based on STEFAN value if
* not yet present. Upon success, continue
@@ -1750,6 +1769,7 @@ set_max_fee (struct OrderContext *oc)
oc->phase++;
}
+
/**
* Set list of acceptable exchanges in @a oc. Upon success, continue
* processing with set_max_fee().
@@ -1871,7 +1891,7 @@ parse_order (struct OrderContext *oc)
NULL),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_array_const ("choices",
- &oc->parse_order.choices),
+ &oc->parse_order.choices),
NULL),
GNUNET_JSON_spec_mark_optional (
TALER_JSON_spec_web_url ("merchant_base_url",
@@ -1937,7 +1957,7 @@ parse_order (struct OrderContext *oc)
ret);
return;
}
- if (NULL == version || 0 == strcmp("0", version))
+ if (NULL == version || 0 == strcmp ("0", version))
{
oc->parse_order.version = TALER_MCV_V0;
@@ -1952,11 +1972,11 @@ parse_order (struct OrderContext *oc)
return;
}
}
- else if (0 == strcmp("1", version))
+ else if (0 == strcmp ("1", version))
{
oc->parse_order.version = TALER_MCV_V1;
- if (! json_is_array(oc->parse_order.choices))
+ if (! json_is_array (oc->parse_order.choices))
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
@@ -1989,9 +2009,9 @@ parse_order (struct OrderContext *oc)
return;
}
if ( (! no_fee) &&
- (GNUNET_OK !=
- TALER_amount_cmp_currency (&oc->parse_order.brutto,
- &oc->parse_order.max_fee)) )
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (&oc->parse_order.brutto,
+ &oc->parse_order.max_fee)) )
{
GNUNET_break_op (0);
GNUNET_JSON_parse_free (spec);
@@ -2268,6 +2288,7 @@ parse_order (struct OrderContext *oc)
oc->phase++;
}
+
/**
* Parse contract choices. Upon success, continue
* processing with merge_inventory().
@@ -2355,7 +2376,7 @@ parse_choices (struct OrderContext *oc)
GNUNET_JSON_spec_uint32 ("count",
&input.details.token.count),
NULL),
- GNUNET_JSON_spec_end()
+ GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
@@ -2431,7 +2452,8 @@ parse_choices (struct OrderContext *oc)
size_t idx;
json_array_foreach ((json_t *) joutputs, idx, joutput)
{
- struct TALER_MerchantContractOutput output = { .details.token.count = 1 };
+ struct TALER_MerchantContractOutput output = { .details.token.count = 1}
+ ;
const char *kind;
const char *ierror_name;
unsigned int ierror_line;
@@ -2447,14 +2469,14 @@ parse_choices (struct OrderContext *oc)
GNUNET_JSON_spec_uint32 ("count",
&output.details.token.count),
NULL),
- GNUNET_JSON_spec_end()
+ GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (joutput,
- ispec,
- &ierror_name,
- &ierror_line))
+ ispec,
+ &ierror_name,
+ &ierror_line))
{
GNUNET_JSON_parse_free (spec);
GNUNET_JSON_parse_free (ispec);
@@ -2526,6 +2548,7 @@ parse_choices (struct OrderContext *oc)
oc->phase++;
}
+
/**
* Process the @a payment_target and add the details of how the
* order could be paid to @a order. On success, continue
@@ -2591,11 +2614,15 @@ merge_inventory (struct OrderContext *oc)
= &oc->parse_request.inventory_products[i];
struct TALER_MERCHANTDB_ProductDetails pd;
enum GNUNET_DB_QueryStatus qs;
+ size_t num_categories = 0;
+ uint64_t *categories = NULL;
qs = TMH_db->lookup_product (TMH_db->cls,
oc->hc->instance->settings.id,
ip->product_id,
- &pd);
+ &pd,
+ &num_categories,
+ &categories);
if (qs <= 0)
{
enum TALER_ErrorCode ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
@@ -2631,6 +2658,7 @@ merge_inventory (struct OrderContext *oc)
ip->product_id);
return;
}
+ GNUNET_free (categories);
oc->parse_order.minimum_age
= GNUNET_MAX (oc->parse_order.minimum_age,
pd.minimum_age);
diff --git a/src/backend/taler-merchant-httpd_private-post-products-ID-lock.c b/src/backend/taler-merchant-httpd_private-post-products-ID-lock.c
index 184f1d28..844b2ec8 100644
--- a/src/backend/taler-merchant-httpd_private-post-products-ID-lock.c
+++ b/src/backend/taler-merchant-httpd_private-post-products-ID-lock.c
@@ -29,9 +29,10 @@
MHD_RESULT
-TMH_private_post_products_ID_lock (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
+TMH_private_post_products_ID_lock (
+ 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;
@@ -75,35 +76,48 @@ TMH_private_post_products_ID_lock (const struct TMH_RequestHandler *rh,
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- NULL);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ NULL);
case GNUNET_DB_STATUS_SOFT_ERROR:
GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- "Serialization error for single-statment request");
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "Serialization error for single-statment request");
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- qs = TMH_db->lookup_product (TMH_db->cls,
- mi->settings.id,
- product_id,
- NULL);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "lookup_product");
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN,
- product_id);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_GONE,
- TALER_EC_MERCHANT_PRIVATE_POST_PRODUCTS_LOCK_INSUFFICIENT_STOCKS,
- product_id);
+ {
+ size_t num_categories = 0;
+ uint64_t *categories = NULL;
+
+ qs = TMH_db->lookup_product (TMH_db->cls,
+ mi->settings.id,
+ product_id,
+ NULL,
+ &num_categories,
+ &categories);
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "lookup_product");
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_PRODUCT_UNKNOWN,
+ product_id);
+ GNUNET_free (categories);
+ }
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_GONE,
+ TALER_EC_MERCHANT_PRIVATE_POST_PRODUCTS_LOCK_INSUFFICIENT_STOCKS,
+ product_id);
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
return TALER_MHD_reply_static (connection,
MHD_HTTP_NO_CONTENT,
diff --git a/src/backend/taler-merchant-httpd_private-post-products.c b/src/backend/taler-merchant-httpd_private-post-products.c
index 3cad91a9..3edc0c16 100644
--- a/src/backend/taler-merchant-httpd_private-post-products.c
+++ b/src/backend/taler-merchant-httpd_private-post-products.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- (C) 2020 Taler Systems SA
+ (C) 2020-2024 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
@@ -28,50 +28,6 @@
#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)) &&
- (GNUNET_OK ==
- 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_stock == p2->total_stock) &&
- (p1->total_sold == p2->total_sold) &&
- (p1->total_lost == p2->total_lost) &&
- (p1->minimum_age == p2->minimum_age) &&
- (0 == strcmp (p1->image,
- p2->image)) &&
- (1 == json_equal (p1->address,
- p2->address)) &&
- (GNUNET_TIME_timestamp_cmp (p1->next_restock,
- ==,
- p2->next_restock) ) );
-}
-
-
MHD_RESULT
TMH_private_post_products (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
@@ -79,9 +35,9 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh,
{
struct TMH_MerchantInstance *mi = hc->instance;
struct TALER_MERCHANTDB_ProductDetails pd = { 0 };
+ const json_t *categories = NULL;
const char *product_id;
int64_t total_stock;
- enum GNUNET_DB_QueryStatus qs;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("product_id",
&product_id),
@@ -103,6 +59,10 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh,
GNUNET_JSON_spec_json ("taxes",
&pd.taxes),
NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("categories",
+ &categories),
+ NULL),
GNUNET_JSON_spec_int64 ("total_stock",
&total_stock),
GNUNET_JSON_spec_mark_optional (
@@ -119,6 +79,13 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh,
NULL),
GNUNET_JSON_spec_end ()
};
+ size_t num_cats = 0;
+ uint64_t *cats = NULL;
+ bool conflict;
+ bool no_instance;
+ ssize_t no_cat;
+ enum GNUNET_DB_QueryStatus qs;
+ MHD_RESULT ret;
GNUNET_assert (NULL != mi);
{
@@ -138,13 +105,33 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh,
if (total_stock < -1)
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "total_stock");
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "total_stock");
+ goto cleanup;
}
+ num_cats = json_array_size (categories);
+ cats = GNUNET_new_array (num_cats,
+ uint64_t);
+ {
+ size_t idx;
+ json_t *val;
+ json_array_foreach (categories, idx, val)
+ {
+ if (! json_is_integer (val))
+ {
+ GNUNET_break_op (0);
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "categories");
+ goto cleanup;
+ }
+ cats[idx] = json_integer_value (val);
+ }
+ }
if (-1 == total_stock)
pd.total_stock = INT64_MAX;
@@ -162,31 +149,31 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh,
if (! TMH_taxes_array_valid (pd.taxes))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "taxes");
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "taxes");
+ goto cleanup;
}
if (! TMH_location_object_valid (pd.address))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "address");
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "address");
+ goto cleanup;
}
if (! TALER_JSON_check_i18n (pd.description_i18n))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "description_i18n");
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "description_i18n");
+ goto cleanup;
}
if (NULL == pd.image)
@@ -194,110 +181,88 @@ TMH_private_post_products (const struct TMH_RequestHandler *rh,
if (! TMH_image_data_url_valid (pd.image))
{
GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "image");
+ ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "image");
+ goto cleanup;
}
- /* finally, interact with DB until no serialization error */
- for (unsigned int i = 0; i<MAX_RETRIES; i++)
+ qs = TMH_db->insert_product (TMH_db->cls,
+ mi->settings.id,
+ product_id,
+ &pd,
+ num_cats,
+ cats,
+ &no_instance,
+ &conflict,
+ &no_cat);
+ switch (qs)
{
- /* 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_GENERIC_DB_START_FAILED,
- NULL);
- }
- 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 */
- GNUNET_break (0);
- TMH_db->rollback (TMH_db->cls);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- NULL);
- 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? */
- {
- bool eq;
-
- eq = products_equal (&pd,
- &epd);
- TALER_MERCHANTDB_product_details_free (&epd);
- TMH_db->rollback (TMH_db->cls);
- GNUNET_JSON_parse_free (spec);
- return eq
- ? TALER_MHD_reply_static (connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0)
- : TALER_MHD_reply_with_error (connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_MERCHANT_PRIVATE_POST_PRODUCTS_CONFLICT_PRODUCT_EXISTS,
- product_id);
- }
- } /* end switch (qs) */
-
- qs = TMH_db->insert_product (TMH_db->cls,
- mi->settings.id,
- product_id,
- &pd);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- {
- TMH_db->rollback (TMH_db->cls);
- break;
- }
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- qs = TMH_db->commit (TMH_db->cls);
- if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
- break;
- }
-retry:
- GNUNET_assert (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- TMH_db->rollback (TMH_db->cls);
- } /* for RETRIES loop */
- GNUNET_JSON_parse_free (spec);
- if (qs < 0)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ ret = TALER_MHD_reply_with_error (
connection,
MHD_HTTP_INTERNAL_SERVER_ERROR,
(GNUNET_DB_STATUS_SOFT_ERROR == qs)
? TALER_EC_GENERIC_DB_SOFT_FAILURE
: TALER_EC_GENERIC_DB_COMMIT_FAILED,
NULL);
+ goto cleanup;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ NULL);
+ goto cleanup;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
}
- return TALER_MHD_reply_static (connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
+ if (no_instance)
+ {
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
+ mi->settings.id);
+ goto cleanup;
+ }
+ if (conflict)
+ {
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_POST_PRODUCTS_CONFLICT_PRODUCT_EXISTS,
+ product_id);
+ goto cleanup;
+ }
+ if (-1 != no_cat)
+ {
+ char nocats[24];
+
+ GNUNET_break_op (0);
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_snprintf (nocats,
+ sizeof (nocats),
+ "%llu",
+ (unsigned long long) no_cat);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_CATEGORY_UNKNOWN,
+ nocats);
+ goto cleanup;
+ }
+ ret = TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+cleanup:
+ GNUNET_JSON_parse_free (spec);
+ GNUNET_free (cats);
+ return ret;
}
diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am
index defc3cf9..4cacd3b4 100644
--- a/src/backenddb/Makefile.am
+++ b/src/backenddb/Makefile.am
@@ -22,6 +22,7 @@ sql_DATA = \
merchant-0003.sql \
merchant-0004.sql \
merchant-0005.sql \
+ merchant-0006.sql \
drop.sql
BUILT_SOURCES = \
@@ -62,7 +63,7 @@ libtalermerchantdb_la_LIBADD = \
libtalermerchantdb_la_LDFLAGS = \
$(POSTGRESQL_LDFLAGS) \
- -version-info 3:0:1 \
+ -version-info 4:0:2 \
-no-undefined
libtaler_plugin_merchantdb_postgres_la_SOURCES = \
@@ -76,6 +77,12 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \
pg_insert_transfer_details.h pg_insert_transfer_details.c \
pg_store_wire_fee_by_exchange.h pg_store_wire_fee_by_exchange.c \
pg_select_open_transfers.h pg_select_open_transfers.c \
+ pg_lookup_categories.h pg_lookup_categories.c \
+ pg_select_category_by_name.h pg_select_category_by_name.c \
+ pg_select_category.h pg_select_category.c \
+ pg_update_category.h pg_update_category.c \
+ pg_insert_category.h pg_insert_category.c \
+ pg_delete_category.h pg_delete_category.c \
pg_lookup_instances.h pg_lookup_instances.c \
pg_lookup_transfers.h pg_lookup_transfers.c \
pg_update_transfer_status.h pg_update_transfer_status.c \
@@ -112,6 +119,7 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \
pg_update_template.h pg_update_template.c \
pg_lookup_templates.h pg_lookup_templates.c \
pg_lookup_template.h pg_lookup_template.c \
+ pg_lookup_all_products.h pg_lookup_all_products.c \
pg_lookup_products.h pg_lookup_products.c \
pg_lookup_product.h pg_lookup_product.c \
pg_delete_product.h pg_delete_product.c \
diff --git a/src/backenddb/merchant-0001.sql b/src/backenddb/merchant-0001.sql
index 2adb9996..ca753a04 100644
--- a/src/backenddb/merchant-0001.sql
+++ b/src/backenddb/merchant-0001.sql
@@ -194,7 +194,7 @@ CREATE TABLE IF NOT EXISTS merchant_inventory
,description TEXT NOT NULL
,description_i18n BYTEA NOT NULL
,unit TEXT NOT NULL
- ,image BYTEA NOT NULL
+ ,image BYTEA NOT NULL -- NOTE: merchant-0006 changes this to TEXT!
,taxes BYTEA NOT NULL
,price taler_amount_currency NOT NULL
,total_stock BIGINT NOT NULL
diff --git a/src/backenddb/merchant-0006.sql b/src/backenddb/merchant-0006.sql
new file mode 100644
index 00000000..00b9afff
--- /dev/null
+++ b/src/backenddb/merchant-0006.sql
@@ -0,0 +1,58 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+-- Everything in one big transaction
+BEGIN;
+
+-- Check patch versioning is in place.
+SELECT _v.register_patch('merchant-0006', NULL, NULL);
+
+SET search_path TO merchant;
+
+ALTER TABLE merchant_inventory
+ ALTER COLUMN image SET DATA TYPE TEXT;
+
+CREATE TABLE IF NOT EXISTS merchant_categories
+ (category_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
+ ,merchant_serial BIGINT NOT NULL
+ REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
+ ,category_name TEXT NOT NULL UNIQUE
+ ,category_name_i18n BYTEA NOT NULL
+ );
+
+COMMENT ON COLUMN merchant_categories.category_name
+ IS 'name of the category';
+COMMENT ON COLUMN merchant_categories.category_name_i18n
+ IS 'JSON with translations of the category name';
+
+CREATE TABLE merchant_product_categories
+ (category_serial BIGINT NOT NULL
+ REFERENCES merchant_categories (category_serial) ON DELETE CASCADE
+ ,product_serial BIGINT NOT NULL
+ REFERENCES merchant_inventory (product_serial) ON DELETE CASCADE);
+CREATE INDEX merchant_categories_by_category
+ ON merchant_product_categories (category_serial);
+CREATE INDEX merchant_categories_by_product
+ ON merchant_product_categories (product_serial);
+
+COMMENT ON COLUMN merchant_product_categories.category_serial
+ IS 'Reference to a category the product is part of';
+COMMENT ON COLUMN merchant_product_categories.product_serial
+ IS 'Reference to a product which is in the given category';
+
+
+-- Complete transaction
+COMMIT;
diff --git a/src/backenddb/merchantdb_helper.c b/src/backenddb/merchantdb_helper.c
index 5894525c..6c1d2ec6 100644
--- a/src/backenddb/merchantdb_helper.c
+++ b/src/backenddb/merchantdb_helper.c
@@ -83,4 +83,25 @@ TALER_MERCHANTDB_token_family_details_free (
}
+void
+TALER_MERCHANTDB_category_details_free (
+ struct TALER_MERCHANTDB_CategoryDetails *cd)
+{
+ GNUNET_free (cd->category_name);
+ json_decref (cd->category_name_i18n);
+ for (unsigned int i = 0; i<cd->num_products; i++)
+ {
+ struct TALER_MERCHANTDB_ProductSummary *ps
+ = &cd->products[i];
+
+ GNUNET_free (ps->product_id);
+ GNUNET_free (ps->description);
+ json_decref (ps->description_i18n);
+ }
+ GNUNET_array_grow (cd->products,
+ cd->num_products,
+ 0);
+}
+
+
/* end of merchantdb_helper.c */
diff --git a/src/backenddb/pg_delete_category.c b/src/backenddb/pg_delete_category.c
new file mode 100644
index 00000000..4a43aa37
--- /dev/null
+++ b/src/backenddb/pg_delete_category.c
@@ -0,0 +1,54 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 backenddb/pg_delete_category.c
+ * @brief Implementation of the delete_category function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_delete_category.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_category (void *cls,
+ const char *instance_id,
+ uint64_t category_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_uint64 (&category_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "delete_category",
+ "DELETE"
+ " FROM merchant_categories"
+ " WHERE merchant_serial="
+ " (SELECT merchant_serial "
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND category_serial=$2");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "delete_category",
+ params);
+}
diff --git a/src/backenddb/pg_delete_category.h b/src/backenddb/pg_delete_category.h
new file mode 100644
index 00000000..8837a6b8
--- /dev/null
+++ b/src/backenddb/pg_delete_category.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 backenddb/pg_delete_category.h
+ * @brief implementation of the delete_category function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_DELETE_CATEGORY_H
+#define PG_DELETE_CATEGORY_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Delete information about a product category.
+ *
+ * @param cls closure
+ * @param instance_id instance to delete category of
+ * @param category_id identifies the category to delete
+ * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+ * if template unknown.
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_category (void *cls,
+ const char *instance_id,
+ uint64_t category_id);
+
+
+#endif
diff --git a/src/backenddb/pg_delete_otp.c b/src/backenddb/pg_delete_otp.c
index 5f011a4b..60f20481 100644
--- a/src/backenddb/pg_delete_otp.c
+++ b/src/backenddb/pg_delete_otp.c
@@ -43,11 +43,11 @@ TMH_PG_delete_otp (void *cls,
"delete_otp",
"DELETE"
" FROM merchant_otp_devices"
- " WHERE merchant_otp_devices.merchant_serial="
+ " WHERE merchant_serial="
" (SELECT merchant_serial "
" FROM merchant_instances"
" WHERE merchant_id=$1)"
- " AND merchant_otp_devices.otp_id=$2");
+ " AND otp_id=$2");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"delete_otp",
params);
diff --git a/src/backenddb/pg_insert_category.c b/src/backenddb/pg_insert_category.c
new file mode 100644
index 00000000..f820b2c6
--- /dev/null
+++ b/src/backenddb/pg_insert_category.c
@@ -0,0 +1,65 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 backenddb/pg_insert_category.c
+ * @brief Implementation of the insert_category function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_category.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_category (void *cls,
+ const char *instance_id,
+ const char *category_name,
+ const json_t *category_name_i18n,
+ uint64_t *category_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (category_name),
+ TALER_PQ_query_param_json (category_name_i18n),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("category_serial",
+ category_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "insert_category",
+ "INSERT INTO merchant_categories"
+ "(category_name"
+ ",category_name_i18n"
+ ")"
+ " SELECT merchant_serial,"
+ " $2, $3"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1"
+ " RETURNING category_serial");
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "insert_category",
+ params,
+ rs);
+}
diff --git a/src/backenddb/pg_insert_category.h b/src/backenddb/pg_insert_category.h
new file mode 100644
index 00000000..8f4bfc99
--- /dev/null
+++ b/src/backenddb/pg_insert_category.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 backenddb/pg_insert_category.h
+ * @brief implementation of the insert_category function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_INSERT_CATEGORY_H
+#define PG_INSERT_CATEGORY_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Insert new product category.
+ *
+ * @param cls closure
+ * @param instance_id instance to insert OTP device for
+ * @param category_name name of the category
+ * @param category_name_i18n translations of the category name
+ * @param[out] category_id set to the category id on success
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_category (void *cls,
+ const char *instance_id,
+ const char *category_name,
+ const json_t *category_name_i18n,
+ uint64_t *category_id);
+
+
+#endif
diff --git a/src/backenddb/pg_insert_deposit_to_transfer.sql b/src/backenddb/pg_insert_deposit_to_transfer.sql
index b2e587f1..ae43b613 100644
--- a/src/backenddb/pg_insert_deposit_to_transfer.sql
+++ b/src/backenddb/pg_insert_deposit_to_transfer.sql
@@ -114,7 +114,7 @@ SELECT deposit_confirmation_serial
-- for the entire deposit confirmation
UPDATE merchant_deposit_confirmations
SET wire_pending=FALSE
- WHERE (deposit_confirmation_serial=decose)
+ WHERE (deposit_confirmation_serial=my_decose)
AND NOT EXISTS
(SELECT 1
FROM merchant_deposits md
diff --git a/src/backenddb/pg_insert_instance.c b/src/backenddb/pg_insert_instance.c
index 6928a094..f1217e29 100644
--- a/src/backenddb/pg_insert_instance.c
+++ b/src/backenddb/pg_insert_instance.c
@@ -35,14 +35,12 @@ TMH_PG_insert_instance (
const struct TALER_MERCHANTDB_InstanceAuthSettings *ias)
{
struct PostgresClosure *pg = cls;
- uint32_t ut32 = (uint32_t) is->ut;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_auto_from_type (merchant_pub),
GNUNET_PQ_query_param_auto_from_type (&ias->auth_hash),
GNUNET_PQ_query_param_auto_from_type (&ias->auth_salt),
GNUNET_PQ_query_param_string (is->id),
GNUNET_PQ_query_param_string (is->name),
- GNUNET_PQ_query_param_uint32 (&ut32),
TALER_PQ_query_param_json (is->address),
TALER_PQ_query_param_json (is->jurisdiction),
GNUNET_PQ_query_param_bool (is->use_stefan),
@@ -76,7 +74,6 @@ TMH_PG_insert_instance (
",auth_salt"
",merchant_id"
",merchant_name"
- ",user_type"
",address"
",jurisdiction"
",use_stefan"
@@ -84,9 +81,10 @@ TMH_PG_insert_instance (
",default_pay_delay"
",website"
",email"
- ",logo)"
+ ",logo"
+ ",user_type)"
"VALUES"
- "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)");
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, 0)");
qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
"insert_instance",
params);
diff --git a/src/backenddb/pg_insert_product.c b/src/backenddb/pg_insert_product.c
index 55db57e9..0d0a8380 100644
--- a/src/backenddb/pg_insert_product.c
+++ b/src/backenddb/pg_insert_product.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -30,7 +30,12 @@ enum GNUNET_DB_QueryStatus
TMH_PG_insert_product (void *cls,
const char *instance_id,
const char *product_id,
- const struct TALER_MERCHANTDB_ProductDetails *pd)
+ const struct TALER_MERCHANTDB_ProductDetails *pd,
+ size_t num_cats,
+ const uint64_t *cats,
+ bool *no_instance,
+ bool *conflict,
+ ssize_t *no_cat)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
@@ -42,36 +47,45 @@ TMH_PG_insert_product (void *cls,
GNUNET_PQ_query_param_string (pd->image),
TALER_PQ_query_param_json (pd->taxes),
TALER_PQ_query_param_amount_with_currency (pg->conn,
- &pd->price),
+ &pd->price),
GNUNET_PQ_query_param_uint64 (&pd->total_stock),
TALER_PQ_query_param_json (pd->address),
GNUNET_PQ_query_param_timestamp (&pd->next_restock),
GNUNET_PQ_query_param_uint32 (&pd->minimum_age),
+ GNUNET_PQ_query_param_array_uint64 (num_cats,
+ cats,
+ pg->conn),
GNUNET_PQ_query_param_end
};
+ uint64_t ncat;
+ bool cats_found = true;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("conflict",
+ conflict),
+ GNUNET_PQ_result_spec_bool ("no_instance",
+ no_instance),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint64 ("no_cat",
+ &ncat),
+ &cats_found),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
PREPARE (pg,
"insert_product",
- "INSERT INTO merchant_inventory"
- "(merchant_serial"
- ",product_id"
- ",description"
- ",description_i18n"
- ",unit"
- ",image"
- ",taxes"
- ",price"
- ",total_stock"
- ",address"
- ",next_restock"
- ",minimum_age"
- ")"
- " SELECT merchant_serial,"
- " $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12"
- " FROM merchant_instances"
- " WHERE merchant_id=$1");
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "insert_product",
- params);
+ "SELECT"
+ " out_conflict AS conflict"
+ ",out_no_cat AS no_cat"
+ ",out_no_instance AS no_instance"
+ " FROM merchant_do_insert_product"
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "insert_product",
+ params,
+ rs);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ *no_cat = (cats_found) ? -1 : (ssize_t) ncat;
+ return qs;
}
diff --git a/src/backenddb/pg_insert_product.h b/src/backenddb/pg_insert_product.h
index 169bd150..5e85228e 100644
--- a/src/backenddb/pg_insert_product.h
+++ b/src/backenddb/pg_insert_product.h
@@ -32,12 +32,24 @@
* @param instance_id instance to insert product for
* @param product_id product identifier of product to insert
* @param pd the product details to insert
+ * @param num_cats length of @a cats array
+ * @param cats array of categories the product is in
+ * @param[out] no_instance set to true if @a instance_id is unknown
+ * @param[out] conflict set to true if a conflicting
+ * product already exists in the database
+ * @param[out] no_cat set to index of non-existing category from @a cats, or -1 if all @a cats were found
* @return database result code
*/
enum GNUNET_DB_QueryStatus
TMH_PG_insert_product (void *cls,
const char *instance_id,
const char *product_id,
- const struct TALER_MERCHANTDB_ProductDetails *pd);
+ const struct TALER_MERCHANTDB_ProductDetails *pd,
+ size_t num_cats,
+ const uint64_t *cats,
+ bool *no_instance,
+ bool *conflict,
+ ssize_t *no_cat);
+
#endif
diff --git a/src/backenddb/pg_insert_product.sql b/src/backenddb/pg_insert_product.sql
new file mode 100644
index 00000000..4abf4469
--- /dev/null
+++ b/src/backenddb/pg_insert_product.sql
@@ -0,0 +1,175 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+
+CREATE OR REPLACE FUNCTION merchant_do_insert_product (
+ IN in_instance_id TEXT,
+ IN in_product_id TEXT,
+ IN in_description TEXT,
+ IN in_description_i18n BYTEA,
+ IN in_unit TEXT,
+ IN in_image TEXT,
+ IN in_taxes BYTEA,
+ IN in_price taler_amount_currency,
+ IN in_total_stock INT8,
+ IN in_address BYTEA,
+ IN in_next_restock INT8,
+ IN in_minimum_age INT4,
+ IN ina_categories INT8[],
+ OUT out_no_instance BOOL,
+ OUT out_conflict BOOL,
+ OUT out_no_cat INT8)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_merchant_id INT8;
+ my_product_serial INT8;
+ i INT8;
+ ini_cat INT8;
+BEGIN
+
+-- Which instance are we using?
+SELECT merchant_serial
+ INTO my_merchant_id
+ FROM merchant_instances
+ WHERE merchant_id=in_instance_id;
+
+IF NOT FOUND
+THEN
+ out_no_instance=TRUE;
+ out_conflict=FALSE;
+ out_no_cat=NULL;
+ RETURN;
+END IF;
+out_no_instance=FALSE;
+
+INSERT INTO merchant_inventory
+ (merchant_serial
+ ,product_id
+ ,description
+ ,description_i18n
+ ,unit
+ ,image
+ ,taxes
+ ,price
+ ,total_stock
+ ,address
+ ,next_restock
+ ,minimum_age
+) VALUES (
+ my_merchant_id
+ ,in_product_id
+ ,in_description
+ ,in_description_i18n
+ ,in_unit
+ ,in_image
+ ,in_taxes
+ ,in_price
+ ,in_total_stock
+ ,in_address
+ ,in_next_restock
+ ,in_minimum_age)
+ON CONFLICT (merchant_serial, product_id) DO NOTHING
+ RETURNING product_serial
+ INTO my_product_serial;
+
+
+IF NOT FOUND
+THEN
+ -- Check for idempotency
+ SELECT product_serial
+ INTO my_product_serial
+ FROM merchant_inventory
+ WHERE merchant_serial=my_merchant_id
+ AND product_id=in_product_id
+ AND description=in_description
+ AND description_i18n=in_description_i18n
+ AND unit=in_unit
+ AND image=in_image
+ AND taxes=in_taxes
+ AND price=in_price
+ AND total_stock=in_total_stock
+ AND address=in_address
+ AND next_restock=in_next_restock
+ AND minimum_age=in_minimum_age;
+ IF NOT FOUND
+ THEN
+ out_conflict=TRUE;
+ out_no_cat=NULL;
+ RETURN;
+ END IF;
+
+ -- Check categories match as well
+ FOR i IN 1..COALESCE(array_length(ina_categories,1),0)
+ LOOP
+ ini_cat=ina_categories[i];
+
+ PERFORM
+ FROM merchant_product_categories
+ WHERE product_serial=my_product_serial
+ AND category_serial=ini_cat;
+ IF NOT FOUND
+ THEN
+ out_conflict=TRUE;
+ out_no_cat=NULL;
+ RETURN;
+ END IF;
+ END LOOP;
+
+ -- Also check there are no additional categories
+ -- in either set.
+ SELECT COUNT(*)
+ INTO i
+ FROM merchant_product_categories
+ WHERE product_serial=my_product_serial;
+ IF i != array_length(ina_categories,1)
+ THEN
+ out_conflict=TRUE;
+ out_no_cat=NULL;
+ RETURN;
+ END IF;
+
+ -- Is idempotent!
+ out_conflict=FALSE;
+ out_no_cat=NULL;
+ RETURN;
+END IF;
+out_conflict=FALSE;
+
+
+-- Add categories
+FOR i IN 1..COALESCE(array_length(ina_categories,1),0)
+LOOP
+ ini_cat=ina_categories[i];
+
+ INSERT INTO merchant_product_categories
+ (product_serial
+ ,category_serial)
+ VALUES
+ (my_product_serial
+ ,ini_cat)
+ ON CONFLICT DO NOTHING;
+
+ IF NOT FOUND
+ THEN
+ out_no_cat=i;
+ RETURN;
+ END IF;
+END LOOP;
+
+-- Success!
+out_no_cat=NULL;
+END $$;
diff --git a/src/backenddb/pg_lookup_all_products.c b/src/backenddb/pg_lookup_all_products.c
new file mode 100644
index 00000000..31320d59
--- /dev/null
+++ b/src/backenddb/pg_lookup_all_products.c
@@ -0,0 +1,193 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 backenddb/pg_lookup_all_products.c
+ * @brief Implementation of the lookup_all_products function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_products.h"
+#include "pg_helper.h"
+
+/**
+ * Context used for TMH_PG_lookup_all_products().
+ */
+struct LookupProductsContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_ProductCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Postgres context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Did database result extraction fail?
+ */
+ bool extract_failed;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about products.
+ *
+ * @param[in,out] cls of type `struct LookupProductsContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_products_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupProductsContext *plc = cls;
+ struct PostgresClosure *pg = plc->pg;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ char *product_id;
+ uint64_t product_serial;
+ struct TALER_MERCHANTDB_ProductDetails pd;
+ size_t num_categories;
+ uint64_t *categories;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("product_id",
+ &product_id),
+ GNUNET_PQ_result_spec_uint64 ("product_serial",
+ &product_serial),
+ GNUNET_PQ_result_spec_string ("description",
+ &pd.description),
+ TALER_PQ_result_spec_json ("description_i18n",
+ &pd.description_i18n),
+ GNUNET_PQ_result_spec_string ("unit",
+ &pd.unit),
+ TALER_PQ_result_spec_amount_with_currency ("price",
+ &pd.price),
+ TALER_PQ_result_spec_json ("taxes",
+ &pd.taxes),
+ GNUNET_PQ_result_spec_uint64 ("total_stock",
+ &pd.total_stock),
+ GNUNET_PQ_result_spec_uint64 ("total_sold",
+ &pd.total_sold),
+ GNUNET_PQ_result_spec_uint64 ("total_lost",
+ &pd.total_lost),
+ GNUNET_PQ_result_spec_string ("image",
+ &pd.image),
+ TALER_PQ_result_spec_json ("address",
+ &pd.address),
+ GNUNET_PQ_result_spec_timestamp ("next_restock",
+ &pd.next_restock),
+ GNUNET_PQ_result_spec_uint32 ("minimum_age",
+ &pd.minimum_age),
+ GNUNET_PQ_result_spec_array_uint64 (pg->conn,
+ "categories",
+ &num_categories,
+ &categories),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ plc->extract_failed = true;
+ return;
+ }
+ plc->cb (plc->cb_cls,
+ product_serial,
+ product_id,
+ &pd,
+ num_categories,
+ categories);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_all_products (void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_ProductCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupProductsContext plc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .pg = pg,
+ /* Can be overwritten by the lookup_products_cb */
+ .extract_failed = false,
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_all_products",
+ "SELECT"
+ " description"
+ ",description_i18n"
+ ",unit"
+ ",price"
+ ",taxes"
+ ",total_stock"
+ ",total_sold"
+ ",total_lost"
+ ",image"
+ ",merchant_inventory.address"
+ ",next_restock"
+ ",minimum_age"
+ ",product_id"
+ ",product_serial"
+ " FROM merchant_inventory"
+ " JOIN merchant_instances"
+ " USING (merchant_serial)"
+ ",LATERAL ("
+ " SELECT ARRAY ("
+ " SELECT mpc.category_serial"
+ " FROM merchant_product_categories mpc"
+ " WHERE mpc.product_serial = mi.product_serial"
+ " ) AS category_array"
+ " ) t"
+ " WHERE merchant_instances.merchant_id=$1");
+ qs = GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "lookup_all_products",
+ params,
+ &lookup_products_cb,
+ &plc);
+ /* If there was an error inside lookup_products_cb, return a hard error. */
+ if (plc.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_all_products.h b/src/backenddb/pg_lookup_all_products.h
new file mode 100644
index 00000000..390fa60d
--- /dev/null
+++ b/src/backenddb/pg_lookup_all_products.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 backenddb/pg_lookup_all_products.h
+ * @brief implementation of the lookup_all_products function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_ALL_PRODUCTS_H
+#define PG_LOOKUP_ALL_PRODUCTS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup all of the products the given instance has configured.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup products for
+ * @param offset transfer_serial number of the transfer we want to offset from
+ * @param limit number of entries to return, negative for descending,
+ * positive for ascending
+ * @param cb function to call on all products found
+ * @param cb_cls closure for @a cb
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_all_products (void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_ProductCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_lookup_categories.c b/src/backenddb/pg_lookup_categories.c
new file mode 100644
index 00000000..9aea5477
--- /dev/null
+++ b/src/backenddb/pg_lookup_categories.c
@@ -0,0 +1,148 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 backenddb/pg_lookup_categories.c
+ * @brief Implementation of the lookup_categories function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_lookup_categories.h"
+#include "pg_helper.h"
+
+
+/**
+ * Context used for TMH_PG_lookup_categories().
+ */
+struct LookupCategoryContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_CategoriesCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Did database result extraction fail?
+ */
+ bool extract_failed;
+};
+
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about otp_device.
+ *
+ * @param[in,out] cls of type `struct LookupCategoryContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_categories_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupCategoryContext *tlc = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ uint64_t category_id;
+ char *category_name;
+ json_t *category_name_i18n;
+ uint64_t product_count;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("category_serial",
+ &category_id),
+ GNUNET_PQ_result_spec_string ("category_name",
+ &category_name),
+ TALER_PQ_result_spec_json ("category_name_i18n",
+ &category_name_i18n),
+ GNUNET_PQ_result_spec_uint64 ("product_count",
+ &product_count),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ tlc->extract_failed = true;
+ return;
+ }
+ tlc->cb (tlc->cb_cls,
+ category_id,
+ category_name,
+ category_name_i18n,
+ product_count);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_categories (void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_CategoriesCallback cb,
+ void *cb_cls)
+{
+
+ struct PostgresClosure *pg = cls;
+ struct LookupCategoryContext tlc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ /* Can be overwritten by the lookup_categories_cb */
+ .extract_failed = false,
+ };
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_categories",
+ "SELECT"
+ " mc.category_serial"
+ ",mc.category_name"
+ ",mc.category_name_i18n"
+ ",COALESCE(COUNT(mpc.product_serial),0)"
+ " AS product_count"
+ " FROM merchant_categories mc"
+ " JOIN merchant_product_categories mpc"
+ " JOIN merchant_instances mi"
+ " USING (merchant_serial)"
+ " WHERE mi.merchant_id=$1"
+ " GROUP BY mc.category_serial"
+ " ORDER BY mc.category_serial;");
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_categories",
+ params,
+ &lookup_categories_cb,
+ &tlc);
+ /* If there was an error inside lookup_categories_cb, return a hard error. */
+ if (tlc.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
diff --git a/src/backenddb/pg_lookup_categories.h b/src/backenddb/pg_lookup_categories.h
new file mode 100644
index 00000000..500295c0
--- /dev/null
+++ b/src/backenddb/pg_lookup_categories.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 backenddb/pg_lookup_categories.h
+ * @brief implementation of the lookup_categories function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_LOOKUP_CATEGORIES_H
+#define PG_LOOKUP_CATEGORIES_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Lookup all of the product categories the given instance has configured.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup OTP devices for
+ * @param cb function to call on all categories found
+ * @param cb_cls closure for @a cb
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_categories (void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_CategoriesCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_lookup_instances.c b/src/backenddb/pg_lookup_instances.c
index 323b1957..f4f820ac 100644
--- a/src/backenddb/pg_lookup_instances.c
+++ b/src/backenddb/pg_lookup_instances.c
@@ -173,7 +173,6 @@ lookup_instances_cb (void *cls,
{
bool no_auth;
bool no_salt;
- uint32_t ut32;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("merchant_serial",
&lic->instance_serial),
@@ -191,8 +190,6 @@ lookup_instances_cb (void *cls,
&lic->is.id),
GNUNET_PQ_result_spec_string ("merchant_name",
&lic->is.name),
- GNUNET_PQ_result_spec_uint32 ("user_type",
- &ut32),
TALER_PQ_result_spec_json ("address",
&lic->is.address),
TALER_PQ_result_spec_json ("jurisdiction",
@@ -200,7 +197,8 @@ lookup_instances_cb (void *cls,
GNUNET_PQ_result_spec_bool ("use_stefan",
&lic->is.use_stefan),
GNUNET_PQ_result_spec_relative_time ("default_wire_transfer_delay",
- &lic->is.default_wire_transfer_delay),
+ &lic->is.default_wire_transfer_delay)
+ ,
GNUNET_PQ_result_spec_relative_time ("default_pay_delay",
&lic->is.default_pay_delay),
GNUNET_PQ_result_spec_allow_null (
@@ -233,7 +231,6 @@ lookup_instances_cb (void *cls,
lic->qs = GNUNET_DB_STATUS_HARD_ERROR;
return;
}
- lic->is.ut = (enum TALER_KYCLOGIC_KycUserType) ut32;
call_cb (lic);
GNUNET_PQ_cleanup_result (rs);
if (0 > lic->qs)
@@ -270,7 +267,6 @@ TMH_PG_lookup_instances (void *cls,
",auth_salt"
",merchant_id"
",merchant_name"
- ",user_type"
",address"
",jurisdiction"
",use_stefan"
diff --git a/src/backenddb/pg_lookup_product.c b/src/backenddb/pg_lookup_product.c
index 65a3c0e5..2948e6ca 100644
--- a/src/backenddb/pg_lookup_product.c
+++ b/src/backenddb/pg_lookup_product.c
@@ -29,7 +29,9 @@ enum GNUNET_DB_QueryStatus
TMH_PG_lookup_product (void *cls,
const char *instance_id,
const char *product_id,
- struct TALER_MERCHANTDB_ProductDetails *pd)
+ struct TALER_MERCHANTDB_ProductDetails *pd,
+ size_t *num_categories,
+ uint64_t **categories)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
@@ -38,6 +40,35 @@ TMH_PG_lookup_product (void *cls,
GNUNET_PQ_query_param_end
};
+ PREPARE (pg,
+ "lookup_product",
+ "SELECT"
+ " mi.description"
+ ",mi.description_i18n"
+ ",mi.unit"
+ ",mi.price"
+ ",mi.taxes"
+ ",mi.total_stock"
+ ",mi.total_sold"
+ ",mi.total_lost"
+ ",mi.image"
+ ",mi.address"
+ ",mi.next_restock"
+ ",mi.minimum_age"
+ ",t.category_array AS categories"
+ " FROM merchant_inventory mi"
+ " JOIN merchant_instances inst"
+ " USING (merchant_serial)"
+ ",LATERAL ("
+ " SELECT ARRAY ("
+ " SELECT mpc.category_serial"
+ " FROM merchant_product_categories mpc"
+ " WHERE mpc.product_serial = mi.product_serial"
+ " ) AS category_array"
+ " ) t"
+ " WHERE inst.merchant_id=$1"
+ " AND mi.product_id=$2"
+ );
if (NULL == pd)
{
struct GNUNET_PQ_ResultSpec rs_null[] = {
@@ -60,7 +91,7 @@ TMH_PG_lookup_product (void *cls,
GNUNET_PQ_result_spec_string ("unit",
&pd->unit),
TALER_PQ_result_spec_amount_with_currency ("price",
- &pd->price),
+ &pd->price),
TALER_PQ_result_spec_json ("taxes",
&pd->taxes),
GNUNET_PQ_result_spec_uint64 ("total_stock",
@@ -77,30 +108,14 @@ TMH_PG_lookup_product (void *cls,
&pd->next_restock),
GNUNET_PQ_result_spec_uint32 ("minimum_age",
&pd->minimum_age),
+ GNUNET_PQ_result_spec_array_uint64 (pg->conn,
+ "categories",
+ num_categories,
+ categories),
GNUNET_PQ_result_spec_end
};
check_connection (pg);
- PREPARE (pg,
- "lookup_product",
- "SELECT"
- " description"
- ",description_i18n"
- ",unit"
- ",price"
- ",taxes"
- ",total_stock"
- ",total_sold"
- ",total_lost"
- ",image"
- ",merchant_inventory.address"
- ",next_restock"
- ",minimum_age"
- " FROM merchant_inventory"
- " JOIN merchant_instances"
- " USING (merchant_serial)"
- " WHERE merchant_instances.merchant_id=$1"
- " AND merchant_inventory.product_id=$2");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"lookup_product",
params,
diff --git a/src/backenddb/pg_lookup_product.h b/src/backenddb/pg_lookup_product.h
index a6add4cb..7346cc0d 100644
--- a/src/backenddb/pg_lookup_product.h
+++ b/src/backenddb/pg_lookup_product.h
@@ -33,12 +33,17 @@
* @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)
+ * @param[out] num_categories set to length of @a categories array
+ * @param[out] categories set to array of categories the
+ * product is in, caller must free() it.
* @return database result code
*/
enum GNUNET_DB_QueryStatus
TMH_PG_lookup_product (void *cls,
const char *instance_id,
const char *product_id,
- struct TALER_MERCHANTDB_ProductDetails *pd);
+ struct TALER_MERCHANTDB_ProductDetails *pd,
+ size_t *num_categories,
+ uint64_t **categories);
#endif
diff --git a/src/backenddb/pg_select_category.c b/src/backenddb/pg_select_category.c
new file mode 100644
index 00000000..c20d7bb7
--- /dev/null
+++ b/src/backenddb/pg_select_category.c
@@ -0,0 +1,37 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 backenddb/pg_select_category.c
+ * @brief Implementation of the select_category function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_select_category.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_category (void *cls,
+ const char *instance_id,
+ uint64_t category_id,
+ struct TALER_MERCHANTDB_CategoryDetails *cd)
+{
+ GNUNET_break (0); // FIXME
+ return GNUNET_DB_STATUS_HARD_ERROR;
+}
diff --git a/src/backenddb/pg_select_category.h b/src/backenddb/pg_select_category.h
new file mode 100644
index 00000000..9eeb14aa
--- /dev/null
+++ b/src/backenddb/pg_select_category.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 backenddb/pg_select_category.h
+ * @brief implementation of the select_category function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_CATEGORY_H
+#define PG_SELECT_CATEGORY_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Lookup details about product category.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup template for
+ * @param category_id category to update
+ * @param[out] cd set to the category details on success, can be NULL
+ * (in that case we only want to check if the category exists)
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_category (void *cls,
+ const char *instance_id,
+ uint64_t category_id,
+ struct TALER_MERCHANTDB_CategoryDetails *cd);
+
+#endif
diff --git a/src/backenddb/pg_select_category_by_name.c b/src/backenddb/pg_select_category_by_name.c
new file mode 100644
index 00000000..9c2aa784
--- /dev/null
+++ b/src/backenddb/pg_select_category_by_name.c
@@ -0,0 +1,68 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 backenddb/pg_select_category_by_name.c
+ * @brief Implementation of the select_category_by_name function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_select_category_by_name.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_category_by_name (void *cls,
+ const char *instance_id,
+ const char *category_name,
+ json_t **name_i18n,
+ uint64_t *category_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (category_name),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("category_serial",
+ category_id),
+ TALER_PQ_result_spec_json ("category_name_i18n",
+ name_i18n),
+ GNUNET_PQ_result_spec_end
+ };
+
+ PREPARE (pg,
+ "select_category_by_name",
+ "SELECT"
+ " category_serial"
+ ",category_name_i18n"
+ " FROM merchant_categories mc"
+ " JOIN merchant_instances mi"
+ " USING (merchant_serial)"
+ " WHERE mi.merchant_id=$1"
+ " AND mc.category_name=$2");
+
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "select_category_by_name",
+ params,
+ rs);
+}
diff --git a/src/backenddb/pg_select_category_by_name.h b/src/backenddb/pg_select_category_by_name.h
new file mode 100644
index 00000000..4f582828
--- /dev/null
+++ b/src/backenddb/pg_select_category_by_name.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 backenddb/pg_select_category_by_name.h
+ * @brief implementation of the select_category_by_name function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_SELECT_CATEGORY_BY_NAME_H
+#define PG_SELECT_CATEGORY_BY_NAME_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Lookup details about product category by name.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup template for
+ * @param category_name category name to look for
+ * @param[out] name_i18n category name translation
+ * @param[out] category_id category ID
+ * @return database result code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_category_by_name (void *cls,
+ const char *instance_id,
+ const char *category_name,
+ json_t **name_i18n,
+ uint64_t *category_id);
+
+#endif
diff --git a/src/backenddb/pg_update_category.c b/src/backenddb/pg_update_category.c
new file mode 100644
index 00000000..3b07a266
--- /dev/null
+++ b/src/backenddb/pg_update_category.c
@@ -0,0 +1,59 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 backenddb/pg_update_category.c
+ * @brief Implementation of the update_category function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_update_category.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_category (void *cls,
+ const char *instance_id,
+ uint64_t category_id,
+ const char *category_name,
+ const json_t *category_name_i18n)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_uint64 (&category_id),
+ GNUNET_PQ_query_param_string (category_name),
+ TALER_PQ_query_param_json (category_name_i18n),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "update_category",
+ "UPDATE merchant_categories SET"
+ " category_name=$3"
+ ",category_name_i18n=$4"
+ " WHERE merchant_serial="
+ " (SELECT merchant_serial"
+ " FROM merchant_instances"
+ " WHERE merchant_id=$1)"
+ " AND category_serial=$2");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_category",
+ params);
+}
diff --git a/src/backenddb/pg_update_category.h b/src/backenddb/pg_update_category.h
new file mode 100644
index 00000000..616a714a
--- /dev/null
+++ b/src/backenddb/pg_update_category.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU 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 backenddb/pg_update_category.h
+ * @brief implementation of the update_category function for Postgres
+ * @author Christian Grothoff
+ */
+#ifndef PG_UPDATE_CATEGORY_H
+#define PG_UPDATE_CATEGORY_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Update descriptions of a product category.
+ *
+ * @param cls closure
+ * @param instance_id instance to update OTP device for
+ * @param category_id category to update
+ * @param category_name name of the category
+ * @param category_name_i18n translations of the category name
+ * @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the template
+ * does not yet exist.
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_category (void *cls,
+ const char *instance_id,
+ uint64_t category_id,
+ const char *category_name,
+ const json_t *category_name_i18n);
+
+#endif
diff --git a/src/backenddb/pg_update_instance.c b/src/backenddb/pg_update_instance.c
index 228e2031..91026638 100644
--- a/src/backenddb/pg_update_instance.c
+++ b/src/backenddb/pg_update_instance.c
@@ -32,7 +32,6 @@ TMH_PG_update_instance (void *cls,
const struct TALER_MERCHANTDB_InstanceSettings *is)
{
struct PostgresClosure *pg = cls;
- uint32_t ut32 = (uint32_t) is->ut;
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (is->id),
GNUNET_PQ_query_param_string (is->name),
@@ -52,7 +51,6 @@ TMH_PG_update_instance (void *cls,
(NULL == is->logo)
? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_string (is->logo),
- GNUNET_PQ_query_param_uint32 (&ut32),
GNUNET_PQ_query_param_end
};
@@ -69,7 +67,6 @@ TMH_PG_update_instance (void *cls,
",website=$8"
",email=$9"
",logo=$10"
- ",user_type=$11"
" WHERE merchant_id=$1");
return GNUNET_PQ_eval_prepared_non_select (pg->conn,
"update_instance",
diff --git a/src/backenddb/pg_update_otp.c b/src/backenddb/pg_update_otp.c
index bdcb9624..218ae74e 100644
--- a/src/backenddb/pg_update_otp.c
+++ b/src/backenddb/pg_update_otp.c
@@ -26,17 +26,6 @@
#include "pg_helper.h"
-/**
- * Update details about a particular OTP device.
- *
- * @param cls closure
- * @param instance_id instance to update OTP device for
- * @param otp_id OTP device to update
- * @param td update to the OTP device details on success, can be NULL
- * (in that case we only want to check if the template exists)
- * @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the template
- * does not yet exist.
- */
enum GNUNET_DB_QueryStatus
TMH_PG_update_otp (void *cls,
const char *instance_id,
@@ -52,7 +41,7 @@ TMH_PG_update_otp (void *cls,
GNUNET_PQ_query_param_uint32 (&pos32),
GNUNET_PQ_query_param_uint64 (&td->otp_ctr),
(NULL == td->otp_key)
- ? GNUNET_PQ_query_param_null()
+ ? GNUNET_PQ_query_param_null ()
: GNUNET_PQ_query_param_string (td->otp_key),
GNUNET_PQ_query_param_end
};
@@ -74,5 +63,3 @@ TMH_PG_update_otp (void *cls,
"update_otp",
params);
}
-
-
diff --git a/src/backenddb/pg_update_product.c b/src/backenddb/pg_update_product.c
index cd7c1857..d7b63b82 100644
--- a/src/backenddb/pg_update_product.c
+++ b/src/backenddb/pg_update_product.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2022 Taler Systems SA
+ Copyright (C) 2022, 2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
@@ -26,11 +26,20 @@
#include "pg_update_product.h"
#include "pg_helper.h"
+
enum GNUNET_DB_QueryStatus
TMH_PG_update_product (void *cls,
const char *instance_id,
const char *product_id,
- const struct TALER_MERCHANTDB_ProductDetails *pd)
+ const struct TALER_MERCHANTDB_ProductDetails *pd,
+ size_t num_cats,
+ const uint64_t *cats,
+ bool *no_instance,
+ ssize_t *no_cat,
+ bool *no_product,
+ bool *lost_reduced,
+ bool *sold_reduced,
+ bool *stocked_reduced)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
@@ -42,14 +51,37 @@ TMH_PG_update_product (void *cls,
GNUNET_PQ_query_param_string (pd->image), /* $6 */
TALER_PQ_query_param_json (pd->taxes),
TALER_PQ_query_param_amount_with_currency (pg->conn,
- &pd->price), /* $8 */
+ &pd->price), /* $8 */
GNUNET_PQ_query_param_uint64 (&pd->total_stock), /* $9 */
GNUNET_PQ_query_param_uint64 (&pd->total_lost),
TALER_PQ_query_param_json (pd->address),
GNUNET_PQ_query_param_timestamp (&pd->next_restock),
GNUNET_PQ_query_param_uint32 (&pd->minimum_age),
+ GNUNET_PQ_query_param_array_uint64 (num_cats,
+ cats,
+ pg->conn),
GNUNET_PQ_query_param_end
};
+ uint64_t ncat;
+ bool cats_found = true;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_bool ("no_instance",
+ no_instance),
+ GNUNET_PQ_result_spec_bool ("no_product",
+ no_product),
+ GNUNET_PQ_result_spec_bool ("lost_reduced",
+ lost_reduced),
+ GNUNET_PQ_result_spec_bool ("sold_reduced",
+ sold_reduced),
+ GNUNET_PQ_result_spec_bool ("stocked_reduced",
+ stocked_reduced),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_uint64 ("no_cat",
+ &ncat),
+ &cats_found),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
if ( (pd->total_stock < pd->total_lost + pd->total_sold) ||
(pd->total_lost < pd->total_lost
@@ -61,26 +93,20 @@ TMH_PG_update_product (void *cls,
check_connection (pg);
PREPARE (pg,
"update_product",
- "UPDATE merchant_inventory SET"
- " description=$3"
- ",description_i18n=$4"
- ",unit=$5"
- ",image=$6"
- ",taxes=$7"
- ",price=$8"
- ",total_stock=$9"
- ",total_lost=$10"
- ",address=$11"
- ",next_restock=$12"
- ",minimum_age=$13"
- " WHERE merchant_serial="
- " (SELECT merchant_serial"
- " FROM merchant_instances"
- " WHERE merchant_id=$1)"
- " AND product_id=$2"
- " AND total_stock <= $9"
- " AND total_lost <= $10");
- return GNUNET_PQ_eval_prepared_non_select (pg->conn,
- "update_product",
- params);
+ "SELECT"
+ " out_lost_reduced AS lost_reduced"
+ ",out_sold_reduced AS sold_reduced"
+ ",out_stocked_reduced AS stocked_reduced"
+ ",out_no_product AS no_product"
+ ",out_no_cat AS no_cat"
+ ",out_no_instance AS no_instance"
+ " FROM merchant_do_update_product"
+ "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "update_product",
+ params,
+ rs);
+ GNUNET_PQ_cleanup_query_params_closures (params);
+ *no_cat = (cats_found) ? -1 : (ssize_t) ncat;
+ return qs;
}
diff --git a/src/backenddb/pg_update_product.h b/src/backenddb/pg_update_product.h
index 3ad280ef..ffba1073 100644
--- a/src/backenddb/pg_update_product.h
+++ b/src/backenddb/pg_update_product.h
@@ -28,26 +28,40 @@
/**
* Update details about a particular product. Note that the
* transaction must enforce that the sold/stocked/lost counters
- * are not reduced (i.e. by expanding the WHERE clause on the existing
- * values).
+ * are not reduced.
*
* @param cls closure
* @param instance_id instance to lookup products for
* @param product_id product to lookup
+ * @param num_cats length of @a cats array
+ * @param cats number of categories the product is in
+ * @param[out] no_instance the update failed as the instance is unknown
+ * @param[out] no_cat set to -1 on success, otherwise the update failed and this is set
+ * to the index of a category in @a cats that is unknown
+ * @param[out] no_product the @a product_id is unknown
+ * @param[out] lost_reduced the update failed as the counter of units lost would have been lowered
+ * @param[out] sold_reduced the update failed as the counter of units sold would have been lowered
+ * @param[out] stocked_reduced the update failed as the counter of units stocked would have been lowered
* @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)
* total_sold in @a pd is ignored, total_lost must not
* exceed total_stock minus the existing total_sold;
* total_sold and total_stock must be larger or equal to
* the existing value;
- * @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the
- * non-decreasing constraints are not met *or* if the product
- * does not yet exist.
+ * @return database result code
*/
enum GNUNET_DB_QueryStatus
TMH_PG_update_product (void *cls,
const char *instance_id,
const char *product_id,
- const struct TALER_MERCHANTDB_ProductDetails *pd);
+ const struct TALER_MERCHANTDB_ProductDetails *pd,
+ size_t num_cats,
+ const uint64_t *cats,
+ bool *no_instance,
+ ssize_t *no_cat,
+ bool *no_product,
+ bool *lost_reduced,
+ bool *sold_reduced,
+ bool *stocked_reduced);
#endif
diff --git a/src/backenddb/pg_update_product.sql b/src/backenddb/pg_update_product.sql
new file mode 100644
index 00000000..6b5a416b
--- /dev/null
+++ b/src/backenddb/pg_update_product.sql
@@ -0,0 +1,139 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2024 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU 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/>
+--
+
+
+CREATE OR REPLACE FUNCTION merchant_do_update_product (
+ IN in_instance_id TEXT,
+ IN in_product_id TEXT,
+ IN in_description TEXT,
+ IN in_description_i18n BYTEA,
+ IN in_unit TEXT,
+ IN in_image TEXT,
+ IN in_taxes BYTEA,
+ IN in_price taler_amount_currency,
+ IN in_total_stock INT8,
+ IN in_total_lost INT8,
+ IN in_address BYTEA,
+ IN in_next_restock INT8,
+ IN in_minimum_age INT4,
+ IN ina_categories INT8[],
+ OUT out_no_instance BOOL,
+ OUT out_no_product BOOL,
+ OUT out_lost_reduced BOOL,
+ OUT out_sold_reduced BOOL,
+ OUT out_stocked_reduced BOOL,
+ OUT out_no_cat INT8)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_merchant_id INT8;
+ my_product_serial INT8;
+ i INT8;
+ ini_cat INT8;
+ rec RECORD;
+BEGIN
+
+out_no_instance=FALSE;
+out_no_product=FALSE;
+out_lost_reduced=FALSE;
+out_sold_reduced=FALSE; -- We currently don't allow updating 'sold', hence always FALSE
+out_stocked_reduced=FALSE;
+out_no_cat=NULL;
+
+-- Which instance are we using?
+SELECT merchant_serial
+ INTO my_merchant_id
+ FROM merchant_instances
+ WHERE merchant_id=in_instance_id;
+
+IF NOT FOUND
+THEN
+ out_no_instance=TRUE;
+ RETURN;
+END IF;
+
+-- Check existing entry satisfies constraints
+SELECT total_stock
+ ,total_lost
+ ,product_serial
+ INTO rec
+ FROM merchant_inventory
+ WHERE merchant_serial=my_merchant_id
+ AND product_id=in_product_id;
+
+IF NOT FOUND
+THEN
+ out_no_product=TRUE;
+ RETURN;
+END IF;
+
+my_product_serial = rec.product_serial;
+
+IF rec.total_stock > in_total_stock
+THEN
+ out_stocked_reduced=TRUE;
+ RETURN;
+END IF;
+
+IF rec.total_lost > in_total_lost
+THEN
+ out_lost_reduced=TRUE;
+ RETURN;
+END IF;
+
+-- Remove old categories
+DELETE FROM merchant_product_categories
+ WHERE product_serial=my_product_serial;
+
+-- Add new categories
+FOR i IN 1..COALESCE(array_length(ina_categories,1),0)
+LOOP
+ ini_cat=ina_categories[i];
+
+ INSERT INTO merchant_product_categories
+ (product_serial
+ ,category_serial)
+ VALUES
+ (my_product_serial
+ ,ini_cat)
+ ON CONFLICT DO NOTHING;
+
+ IF NOT FOUND
+ THEN
+ out_no_cat=i;
+ RETURN;
+ END IF;
+END LOOP;
+
+UPDATE merchant_inventory SET
+ description=in_description
+ ,description_i18n=in_description_i18n
+ ,unit=in_unit
+ ,image=in_image
+ ,taxes=in_taxes
+ ,price=in_price
+ ,total_stock=in_total_stock
+ ,total_lost=in_total_lost
+ ,address=in_address
+ ,next_restock=in_next_restock
+ ,minimum_age=in_minimum_age
+ WHERE merchant_serial=my_merchant_id
+ AND product_serial=my_product_serial; -- could also match on product_id
+
+ASSERT FOUND,'SELECTED it earlier, should UPDATE it now';
+
+-- Success!
+END $$;
diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c
index e5f3f2a1..6c5c7a5b 100644
--- a/src/backenddb/plugin_merchantdb_postgres.c
+++ b/src/backenddb/plugin_merchantdb_postgres.c
@@ -44,6 +44,11 @@
#include "pg_lookup_instances.h"
#include "pg_lookup_transfers.h"
#include "pg_lookup_pending_deposits.h"
+#include "pg_lookup_categories.h"
+#include "pg_select_category.h"
+#include "pg_update_category.h"
+#include "pg_insert_category.h"
+#include "pg_delete_category.h"
#include "pg_update_wirewatch_progress.h"
#include "pg_select_wirewatch_accounts.h"
#include "pg_select_open_transfers.h"
@@ -57,12 +62,14 @@
#include "pg_account_kyc_set_status.h"
#include "pg_account_kyc_get_status.h"
#include "pg_delete_instance_private_key.h"
+#include "pg_select_category_by_name.h"
#include "pg_purge_instance.h"
#include "pg_update_instance.h"
#include "pg_update_instance_auth.h"
#include "pg_inactivate_account.h"
#include "pg_activate_account.h"
#include "pg_lookup_products.h"
+#include "pg_lookup_all_products.h"
#include "pg_lookup_product.h"
#include "pg_delete_product.h"
#include "pg_insert_product.h"
@@ -407,6 +414,8 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
= &TMH_PG_update_transfer_status;
plugin->lookup_products
= &TMH_PG_lookup_products;
+ plugin->lookup_all_products
+ = &TMH_PG_lookup_all_products;
plugin->lookup_product
= &TMH_PG_lookup_product;
plugin->delete_product
@@ -561,6 +570,18 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
= &TMH_PG_insert_pending_webhook;
plugin->update_pending_webhook
= &TMH_PG_update_pending_webhook;
+ plugin->lookup_categories
+ = &TMH_PG_lookup_categories;
+ plugin->select_category_by_name
+ = &TMH_PG_select_category_by_name;
+ plugin->select_category
+ = &TMH_PG_select_category;
+ plugin->update_category
+ = &TMH_PG_update_category;
+ plugin->insert_category
+ = &TMH_PG_insert_category;
+ plugin->delete_category
+ = &TMH_PG_delete_category;
plugin->delete_exchange_accounts
= &TMH_PG_delete_exchange_accounts;
plugin->select_accounts_by_exchange
diff --git a/src/backenddb/procedures.sql.in b/src/backenddb/procedures.sql.in
index 3ebf8b8b..9afa246b 100644
--- a/src/backenddb/procedures.sql.in
+++ b/src/backenddb/procedures.sql.in
@@ -19,6 +19,8 @@ BEGIN;
SET search_path TO merchant;
#include "pg_insert_deposit_to_transfer.sql"
+#include "pg_insert_product.sql"
#include "pg_insert_transfer_details.sql"
+#include "pg_update_product.sql"
COMMIT;
diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c
index fbb662f8..83fd8dcd 100644
--- a/src/backenddb/test_merchantdb.c
+++ b/src/backenddb/test_merchantdb.c
@@ -797,20 +797,48 @@ check_products_equal (const struct TALER_MERCHANTDB_ProductDetails *a,
*
* @param instance the instance to insert the product for.
* @param product the product data to insert.
+ * @param num_cats length of the @a cats array
+ * @param cats array of categories for the product
* @param expected_result the result we expect the db to return.
+ * @param expect_conflict expected conflict status
+ * @param expect_no_instance expected instance missing status
+ * @param expected_no_cat expected category missing index
* @return 0 when successful, 1 otherwise.
*/
static int
test_insert_product (const struct InstanceData *instance,
const struct ProductData *product,
- enum GNUNET_DB_QueryStatus expected_result)
+ unsigned int num_cats,
+ const uint64_t *cats,
+ enum GNUNET_DB_QueryStatus expected_result,
+ bool expect_conflict,
+ bool expect_no_instance,
+ ssize_t expected_no_cat)
{
+ bool conflict;
+ bool no_instance;
+ ssize_t no_cat;
+
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->insert_product (plugin->cls,
instance->instance.id,
product->id,
- &product->product),
+ &product->product,
+ num_cats,
+ cats,
+ &no_instance,
+ &conflict,
+ &no_cat),
"Insert product failed\n");
+ if (expected_result > 0)
+ {
+ TEST_COND_RET_ON_FAIL (no_instance == expect_no_instance,
+ "No instance wrong");
+ TEST_COND_RET_ON_FAIL (conflict == expect_conflict,
+ "Conflict wrong");
+ TEST_COND_RET_ON_FAIL (no_cat == expected_no_cat,
+ "Wrong category missing returned");
+ }
return 0;
}
@@ -826,14 +854,54 @@ test_insert_product (const struct InstanceData *instance,
static int
test_update_product (const struct InstanceData *instance,
const struct ProductData *product,
- enum GNUNET_DB_QueryStatus expected_result)
-{
- TEST_COND_RET_ON_FAIL (expected_result ==
- plugin->update_product (plugin->cls,
- instance->instance.id,
- product->id,
- &product->product),
- "Update product failed\n");
+ unsigned int num_cats,
+ const uint64_t *cats,
+ enum GNUNET_DB_QueryStatus expected_result,
+ bool expect_no_instance,
+ bool expect_no_product,
+ bool expect_lost_reduced,
+ bool expect_sold_reduced,
+ bool expect_stocked_reduced,
+ ssize_t expected_no_cat)
+
+{
+ bool no_instance;
+ ssize_t no_cat;
+ bool no_product;
+ bool lost_reduced;
+ bool sold_reduced;
+ bool stocked_reduced;
+
+ TEST_COND_RET_ON_FAIL (
+ expected_result ==
+ plugin->update_product (plugin->cls,
+ instance->instance.id,
+ product->id,
+ &product->product,
+ num_cats,
+ cats,
+ &no_instance,
+ &no_cat,
+ &no_product,
+ &lost_reduced,
+ &sold_reduced,
+ &stocked_reduced),
+ "Update product failed\n");
+ if (expected_result > 0)
+ {
+ TEST_COND_RET_ON_FAIL (no_instance == expect_no_instance,
+ "No instance wrong");
+ TEST_COND_RET_ON_FAIL (no_product == expect_no_product,
+ "No product wrong");
+ TEST_COND_RET_ON_FAIL (lost_reduced == expect_lost_reduced,
+ "No product wrong");
+ TEST_COND_RET_ON_FAIL (stocked_reduced == expect_stocked_reduced,
+ "Stocked reduced wrong");
+ TEST_COND_RET_ON_FAIL (sold_reduced == expect_sold_reduced,
+ "Sold reduced wrong");
+ TEST_COND_RET_ON_FAIL (no_cat == expected_no_cat,
+ "Wrong category missing returned");
+ }
return 0;
}
@@ -850,25 +918,34 @@ test_lookup_product (const struct InstanceData *instance,
const struct ProductData *product)
{
struct TALER_MERCHANTDB_ProductDetails lookup_result;
+ size_t num_categories = 0;
+ uint64_t *categories = NULL;
+
if (0 > plugin->lookup_product (plugin->cls,
instance->instance.id,
product->id,
- &lookup_result))
+ &lookup_result,
+ &num_categories,
+ &categories))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup product failed\n");
TALER_MERCHANTDB_product_details_free (&lookup_result);
return 1;
}
- const struct TALER_MERCHANTDB_ProductDetails *to_cmp = &product->product;
- if (0 != check_products_equal (&lookup_result,
- to_cmp))
+ GNUNET_free (categories);
{
- GNUNET_break (0);
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup product failed: incorrect product returned\n");
- TALER_MERCHANTDB_product_details_free (&lookup_result);
- return 1;
+ const struct TALER_MERCHANTDB_ProductDetails *to_cmp = &product->product;
+
+ if (0 != check_products_equal (&lookup_result,
+ to_cmp))
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup product failed: incorrect product returned\n");
+ TALER_MERCHANTDB_product_details_free (&lookup_result);
+ return 1;
+ }
}
TALER_MERCHANTDB_product_details_free (&lookup_result);
return 0;
@@ -1076,31 +1153,67 @@ run_test_products (struct TestProducts_Closure *cls)
/* Test that insert without an instance fails */
TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
&cls->products[0],
- GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+ 0,
+ NULL,
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
+ false,
+ true,
+ -1));
/* Insert the instance */
TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test inserting a product */
TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
&cls->products[0],
- GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
- /* Test that double insert fails */
+ 0,
+ NULL,
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
+ false,
+ false,
+ -1));
+ /* Test that double insert succeeds */
TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
&cls->products[0],
- GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+ 0,
+ NULL,
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
+ false,
+ false,
+ -1));
+ /* Test that conflicting insert fails */
+ {
+ uint64_t cat = 42;
+
+ TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
+ &cls->products[0],
+ 1,
+ &cat,
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
+ true,
+ false,
+ -1));
+ }
/* Test lookup of individual products */
TEST_RET_ON_FAIL (test_lookup_product (&cls->instance,
&cls->products[0]));
/* Make sure it fails correctly for products that don't exist */
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
- plugin->lookup_product (plugin->cls,
- cls->instance.instance.id,
- "nonexistent_product",
- NULL))
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Lookup product failed\n");
- return 1;
+ size_t num_categories = 0;
+ uint64_t *categories = NULL;
+
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ plugin->lookup_product (plugin->cls,
+ cls->instance.instance.id,
+ "nonexistent_product",
+ NULL,
+ &num_categories,
+ &categories))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Lookup product failed\n");
+ return 1;
+ }
+ GNUNET_free (categories);
}
/* Test product update */
cls->products[0].product.description =
@@ -1126,37 +1239,76 @@ run_test_products (struct TestProducts_Closure *cls)
json_array_append_new (cls->products[0].product.address,
json_string ("444 Some Street")));
cls->products[0].product.next_restock = GNUNET_TIME_timestamp_get ();
- TEST_RET_ON_FAIL (test_update_product (&cls->instance,
- &cls->products[0],
- GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+ TEST_RET_ON_FAIL (test_update_product (
+ &cls->instance,
+ &cls->products[0],
+ 0,
+ NULL,
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
+ false,
+ false,
+ false,
+ false,
+ false,
+ -1));
{
struct ProductData stock_dec = cls->products[0];
stock_dec.product.total_stock = 40;
- TEST_RET_ON_FAIL (test_update_product (&cls->instance,
- &stock_dec,
- GNUNET_DB_STATUS_SUCCESS_NO_RESULTS))
- ;
+ TEST_RET_ON_FAIL (test_update_product (
+ &cls->instance,
+ &stock_dec,
+ 0,
+ NULL,
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
+ false,
+ false,
+ false,
+ false,
+ true,
+ -1));
}
{
struct ProductData lost_dec = cls->products[0];
lost_dec.product.total_lost = 1;
- TEST_RET_ON_FAIL (test_update_product (&cls->instance,
- &lost_dec,
- GNUNET_DB_STATUS_SUCCESS_NO_RESULTS))
- ;
+ TEST_RET_ON_FAIL (test_update_product (
+ &cls->instance,
+ &lost_dec,
+ 0,
+ NULL,
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
+ false,
+ false,
+ true,
+ false,
+ false,
+ -1));
}
TEST_RET_ON_FAIL (test_lookup_product (&cls->instance,
&cls->products[0]));
- TEST_RET_ON_FAIL (test_update_product (&cls->instance,
- &cls->products[1],
- GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+ TEST_RET_ON_FAIL (test_update_product (
+ &cls->instance,
+ &cls->products[1],
+ 0,
+ NULL,
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
+ false,
+ true,
+ false,
+ false,
+ false,
+ -1));
/* Test collective product lookup */
TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
&cls->products[1],
- GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+ 0,
+ NULL,
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
+ false,
+ false,
+ -1));
TEST_RET_ON_FAIL (test_lookup_products (&cls->instance,
2,
cls->products));
@@ -2059,7 +2211,12 @@ run_test_orders (struct TestOrders_Closure *cls)
/* Test order lock */
TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
&cls->product,
- GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+ 0,
+ NULL,
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
+ false,
+ false,
+ -1));
if (1 != plugin->insert_order_lock (plugin->cls,
cls->instance.instance.id,
cls->orders[0].id,
diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h
index 057c9eff..c36328a7 100644
--- a/src/include/taler_merchant_service.h
+++ b/src/include/taler_merchant_service.h
@@ -470,11 +470,6 @@ struct TALER_MERCHANT_InstanceInformation
*/
const json_t *payment_targets;
- /**
- * User type for the instance.
- */
- enum TALER_KYCLOGIC_KycUserType ut;
-
};
@@ -602,7 +597,6 @@ TALER_MERCHANT_instances_post (
const char *backend_url,
const char *instance_id,
const char *name,
- enum TALER_KYCLOGIC_KycUserType ut,
const json_t *address,
const json_t *jurisdiction,
bool use_stefan,
@@ -668,7 +662,6 @@ TALER_MERCHANT_instance_patch (
const char *backend_url,
const char *instance_id,
const char *name,
- enum TALER_KYCLOGIC_KycUserType ut,
const json_t *address,
const json_t *jurisdiction,
bool use_stefan,
@@ -790,10 +783,6 @@ struct TALER_MERCHANT_InstanceDetails
*/
struct GNUNET_TIME_Relative default_pay_delay;
- /**
- * User type for the instance.
- */
- enum TALER_KYCLOGIC_KycUserType ut;
};
@@ -1516,12 +1505,12 @@ struct TALER_MERCHANT_ProductGetResponse
struct TALER_Amount price;
/**
- * base64-encoded product image
+ * base64-encoded product image, can be NULL if none is set.
*/
const char *image;
/**
- * list of taxes paid by the merchant
+ * list of taxes paid by the merchant, can be NULL if no taxes were specified.
*/
const json_t *taxes;
@@ -1544,7 +1533,7 @@ struct TALER_MERCHANT_ProductGetResponse
uint64_t total_lost;
/**
- * where the product is in stock
+ * where the product is in stock, can be NULL if no location was given.
*/
const json_t *location;
@@ -1707,6 +1696,54 @@ TALER_MERCHANT_products_post2 (
/**
+ * Make a POST /products request to add a product to the
+ * inventory.
+ *
+ * @param ctx the context
+ * @param backend_url HTTP base URL for the backend
+ * @param product_id identifier to use for the product
+ * @param description description of the product
+ * @param description_i18n Map from IETF BCP 47 language tags to localized descriptions
+ * @param unit unit in which the product is measured (liters, kilograms, packages, etc.)
+ * @param price the price for one @a unit of the product, zero is used to imply that
+ * this product is not sold separately or that the price is not fixed and
+ * must be supplied by the front-end. If non-zero, price must include
+ * applicable taxes.
+ * @param image base64-encoded product image
+ * @param taxes list of taxes paid by the merchant
+ * @param total_stock in @a units, -1 to indicate "infinite" (i.e. electronic books)
+ * @param address where the product is in stock
+ * @param next_restock when the next restocking is expected to happen, 0 for unknown,
+ * #GNUNET_TIME_UNIT_FOREVER_ABS for 'never'.
+ * @param minimum_age minimum age the buyer must have
+ * @param num_cats length of the @a cats array
+ * @param cats array of categories the product is in
+ * @param cb function to call with the backend's result
+ * @param cb_cls closure for @a cb
+ * @return the request handle; NULL upon error
+ */
+struct TALER_MERCHANT_ProductsPostHandle *
+TALER_MERCHANT_products_post3 (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *product_id,
+ const char *description,
+ const json_t *description_i18n,
+ const char *unit,
+ const struct TALER_Amount *price,
+ const char *image,
+ const json_t *taxes,
+ int64_t total_stock,
+ const json_t *address,
+ struct GNUNET_TIME_Timestamp next_restock,
+ uint32_t minimum_age,
+ unsigned int num_cats,
+ const uint64_t *cats,
+ TALER_MERCHANT_ProductsPostCallback cb,
+ void *cb_cls);
+
+
+/**
* Cancel POST /products operation.
*
* @param pph operation to cancel
diff --git a/src/include/taler_merchantdb_lib.h b/src/include/taler_merchantdb_lib.h
index 3a641a54..a78d01f1 100644
--- a/src/include/taler_merchantdb_lib.h
+++ b/src/include/taler_merchantdb_lib.h
@@ -97,6 +97,16 @@ void
TALER_MERCHANTDB_token_family_details_free (
struct TALER_MERCHANTDB_TokenFamilyDetails *tf);
+
+/**
+ * Free members of @a cd, but not @a cd itself.
+ *
+ * @param[in] cd token family details to clean up
+ */
+void
+TALER_MERCHANTDB_category_details_free (
+ struct TALER_MERCHANTDB_CategoryDetails *cd);
+
#endif /* MERCHANT_DB_H */
/* end of taler_merchantdb_lib.h */
diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h
index 44fdc0ab..476f5785 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2023 Taler Systems SA
+ Copyright (C) 2014-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
@@ -210,10 +210,6 @@ struct TALER_MERCHANTDB_InstanceSettings
*/
struct GNUNET_TIME_Relative default_pay_delay;
- /**
- * Type of user this merchant represents.
- */
- enum TALER_KYCLOGIC_KycUserType ut;
};
@@ -335,6 +331,27 @@ struct TALER_MERCHANTDB_ProductDetails
/**
+ * Typically called by `lookup_all_products`.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @param product_serial row ID of the product
+ * @param product_id ID of the product
+ * @param pd full product details
+ * @param num_categories length of @a categories array
+ * @param categories array of categories the
+ * product is in
+ */
+typedef void
+(*TALER_MERCHANTDB_ProductCallback)(
+ void *cls,
+ uint64_t product_serial,
+ const char *product_id,
+ const struct TALER_MERCHANTDB_ProductDetails *pd,
+ size_t num_categories,
+ const uint64_t *categories);
+
+
+/**
* Typically called by `lookup_templates`.
*
* @param cls closure
@@ -425,6 +442,75 @@ struct TALER_MERCHANTDB_OtpDeviceDetails
/**
+ * Typically called by `lookup_categories`.
+ *
+ * @param cls closure
+ * @param category_id ID of the category
+ * @param category_name name of the category
+ * @param category_name_i18n translations of the @a category_name
+ * @param product_count number of products in the category
+ */
+typedef void
+(*TALER_MERCHANTDB_CategoriesCallback)(
+ void *cls,
+ uint64_t category_id,
+ const char *category_name,
+ const json_t *category_name_i18n,
+ uint64_t product_count);
+
+
+/**
+ * Details about a product category.
+ */
+struct TALER_MERCHANTDB_ProductSummary
+{
+ /**
+ * ID of the product.
+ */
+ char *product_id;
+
+ /**
+ * Description for the product.
+ */
+ char *description;
+
+ /**
+ * Translation of the @e description.
+ */
+ json_t *description_i18n;
+
+};
+
+/**
+ * Details about a product category.
+ */
+struct TALER_MERCHANTDB_CategoryDetails
+{
+
+ /**
+ * Name of the category.
+ */
+ char *category_name;
+
+ /**
+ * Translations of the name of the category.
+ */
+ json_t *category_name_i18n;
+
+ /**
+ * Products in the category.
+ */
+ struct TALER_MERCHANTDB_ProductSummary *products;
+
+ /**
+ * Length of the @e products array.
+ */
+ unsigned int num_products;
+
+};
+
+
+/**
* Typically called by `lookup_webhooks`.
*
* @param cls a `json_t *` JSON array to build
@@ -1619,6 +1705,22 @@ struct TALER_MERCHANTDB_Plugin
TALER_MERCHANTDB_ProductsCallback cb,
void *cb_cls);
+
+ /**
+ * Lookup full details of all of the products the given instance has configured (expensive).
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup products for
+ * @param cb function to call on all products found
+ * @param cb_cls closure for @a cb
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_all_products)(void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_ProductCallback cb,
+ void *cb_cls);
+
/**
* Lookup details about a particular product.
*
@@ -1627,13 +1729,18 @@ struct TALER_MERCHANTDB_Plugin
* @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)
+ * @param[out] num_categories set to length of @a categories array
+ * @param[out] categories set to array of categories the
+ * product is in, caller must free() it.
* @return database result code
*/
enum GNUNET_DB_QueryStatus
(*lookup_product)(void *cls,
const char *instance_id,
const char *product_id,
- struct TALER_MERCHANTDB_ProductDetails *pd);
+ struct TALER_MERCHANTDB_ProductDetails *pd,
+ size_t *num_categories,
+ uint64_t **categories);
/**
* Delete information about a product. Note that the transaction must
@@ -1657,13 +1764,24 @@ struct TALER_MERCHANTDB_Plugin
* @param instance_id instance to insert product for
* @param product_id product identifier of product to insert
* @param pd the product details to insert
+ * @param num_cats length of @a cats array
+ * @param cats array of categories the product is in
+ * @param[out] no_instance set to true if @a instance_id is unknown
+ * @param[out] conflict set to true if a conflicting
+ * product already exists in the database
+ * @param[out] no_cat set to index of non-existing category from @a cats, or -1 if all @a cats were found
* @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);
+ const struct TALER_MERCHANTDB_ProductDetails *pd,
+ size_t num_cats,
+ const uint64_t *cats,
+ bool *no_instance,
+ bool *conflict,
+ ssize_t *no_cat);
/**
* Update details about a particular product. Note that the
@@ -1674,21 +1792,31 @@ struct TALER_MERCHANTDB_Plugin
* @param cls closure
* @param instance_id instance to lookup products for
* @param product_id product to lookup
- * @param pd set to the product details on success, can be NULL
- * (in that case we only want to check if the product exists);
- * total_sold in @a pd is ignored (!), total_lost must not
- * exceed total_stock minus the existing total_sold;
- * total_sold and total_stock must be larger or equal to
- * the existing value;
- * @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the
- * non-decreasing constraints are not met *or* if the product
- * does not yet exist.
+ * @param pd product details with updated values
+ * @param num_cats length of @a cats array
+ * @param cats number of categories the product is in
+ * @param[out] no_instance the update failed as the instance is unknown
+ * @param[out] no_cat set to -1 on success, otherwise the update failed and this is set
+ * to the index of a category in @a cats that is unknown
+ * @param[out] no_product the @a product_id is unknown
+ * @param[out] lost_reduced the update failed as the counter of units lost would have been lowered
+ * @param[out] sold_reduced the update failed as the counter of units sold would have been lowered
+ * @param[out] stocked_reduced the update failed as the counter of units stocked would have been lowered
+ * @return database result code
*/
enum GNUNET_DB_QueryStatus
(*update_product)(void *cls,
const char *instance_id,
const char *product_id,
- const struct TALER_MERCHANTDB_ProductDetails *pd);
+ const struct TALER_MERCHANTDB_ProductDetails *pd,
+ size_t num_cats,
+ const uint64_t *cats,
+ bool *no_instance,
+ ssize_t *no_cat,
+ bool *no_product,
+ bool *lost_reduced,
+ bool *sold_reduced,
+ bool *stocked_reduced);
/**
* Lock stocks of a particular product. Note that the transaction must
@@ -2844,6 +2972,24 @@ struct TALER_MERCHANTDB_Plugin
/**
+ * Update details about a particular template.
+ *
+ * @param cls closure
+ * @param instance_id instance to update template for
+ * @param template_id template to update
+ * @param td update to the template details on success, can be NULL
+ * (in that case we only want to check if the template exists)
+ * @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the template
+ * does not yet exist.
+ */
+ enum GNUNET_DB_QueryStatus
+ (*update_template)(void *cls,
+ const char *instance_id,
+ const char *template_id,
+ const struct TALER_MERCHANTDB_TemplateDetails *td);
+
+
+ /**
* Delete information about an OTP device.
*
* @param cls closure
@@ -2939,21 +3085,104 @@ struct TALER_MERCHANTDB_Plugin
/**
- * Update details about a particular template.
+ * Delete information about a product category.
*
* @param cls closure
- * @param instance_id instance to update template for
- * @param template_id template to update
- * @param td update to the template details on success, can be NULL
- * (in that case we only want to check if the template exists)
+ * @param instance_id instance to delete category of
+ * @param category_id identifies the category to delete
+ * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+ * if template unknown.
+ */
+ enum GNUNET_DB_QueryStatus
+ (*delete_category)(void *cls,
+ const char *instance_id,
+ uint64_t category_id);
+
+ /**
+ * Insert new product category.
+ *
+ * @param cls closure
+ * @param instance_id instance to insert OTP device for
+ * @param category_name name of the category
+ * @param category_name_i18n translations of the category name
+ * @param[out] category_id set to the category id on success
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_category)(void *cls,
+ const char *instance_id,
+ const char *category_name,
+ const json_t *category_name_i18n,
+ uint64_t *category_id);
+
+
+ /**
+ * Update descriptions of a product category.
+ *
+ * @param cls closure
+ * @param instance_id instance to update OTP device for
+ * @param category_id category to update
+ * @param category_name name of the category
+ * @param category_name_i18n translations of the category name
* @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the template
* does not yet exist.
*/
enum GNUNET_DB_QueryStatus
- (*update_template)(void *cls,
+ (*update_category)(void *cls,
const char *instance_id,
- const char *template_id,
- const struct TALER_MERCHANTDB_TemplateDetails *td);
+ uint64_t category_id,
+ const char *category_name,
+ const json_t *category_name_i18n);
+
+ /**
+ * Lookup all of the product categories the given instance has configured.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup OTP devices for
+ * @param cb function to call on all categories found
+ * @param cb_cls closure for @a cb
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_categories)(void *cls,
+ const char *instance_id,
+ TALER_MERCHANTDB_CategoriesCallback cb,
+ void *cb_cls);
+
+
+ /**
+ * Lookup details about product category.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup template for
+ * @param category_id category to update
+ * @param[out] cd set to the category details on success, can be NULL
+ * (in that case we only want to check if the category exists)
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_category)(void *cls,
+ const char *instance_id,
+ uint64_t category_id,
+ struct TALER_MERCHANTDB_CategoryDetails *cd);
+
+
+ /**
+ * Lookup details about product category by name.
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup template for
+ * @param category_name category name to look for
+ * @param[out] name_i18n category name translation
+ * @param[out] category_id category ID
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_category_by_name)(void *cls,
+ const char *instance_id,
+ const char *category_name,
+ json_t **name_i18n,
+ uint64_t *category_id);
/**
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index 1e7430d4..4e6a901a 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -10,7 +10,7 @@ lib_LTLIBRARIES = \
libtalermerchant.la
libtalermerchant_la_LDFLAGS = \
- -version-info 5:2:0 \
+ -version-info 6:0:0 \
-no-undefined
libtalermerchant_la_SOURCES = \
diff --git a/src/lib/merchant_api_get_config.c b/src/lib/merchant_api_get_config.c
index b4b700bd..9b501342 100644
--- a/src/lib/merchant_api_get_config.c
+++ b/src/lib/merchant_api_get_config.c
@@ -34,12 +34,12 @@
* Which version of the Taler protocol is implemented
* by this library? Used to determine compatibility.
*/
-#define MERCHANT_PROTOCOL_CURRENT 14
+#define MERCHANT_PROTOCOL_CURRENT 16
/**
* How many configs are we backwards-compatible with?
*/
-#define MERCHANT_PROTOCOL_AGE 2
+#define MERCHANT_PROTOCOL_AGE 4
/**
* How many exchanges do we allow at most per merchant?
diff --git a/src/lib/merchant_api_get_instance.c b/src/lib/merchant_api_get_instance.c
index eef95b84..76f3691a 100644
--- a/src/lib/merchant_api_get_instance.c
+++ b/src/lib/merchant_api_get_instance.c
@@ -136,16 +136,6 @@ handle_get_instance_finished (void *cls,
}
igr.details.ok.details.address = address;
igr.details.ok.details.jurisdiction = jurisdiction;
- if (GNUNET_OK !=
- TALER_KYCLOGIC_kyc_user_type_from_string (
- uts,
- &igr.details.ok.details.ut))
- {
- GNUNET_break_op (0);
- igr.hr.http_status = 0;
- igr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
- break;
- }
igh->cb (igh->cb_cls,
&igr);
TALER_MERCHANT_instance_get_cancel (igh);
diff --git a/src/lib/merchant_api_get_instances.c b/src/lib/merchant_api_get_instances.c
index b2f99853..c47c4895 100644
--- a/src/lib/merchant_api_get_instances.c
+++ b/src/lib/merchant_api_get_instances.c
@@ -126,13 +126,6 @@ parse_instances (const json_t *json,
GNUNET_break_op (0);
return GNUNET_SYSERR;
}
- if (GNUNET_OK !=
- TALER_KYCLOGIC_kyc_user_type_from_string (uts,
- &ii->ut))
- {
- GNUNET_break_op (0);
- return GNUNET_SYSERR;
- }
for (size_t i = 0; i<json_array_size (ii->payment_targets); i++)
{
if (! json_is_string (json_array_get (ii->payment_targets,
diff --git a/src/lib/merchant_api_get_product.c b/src/lib/merchant_api_get_product.c
index 3f026bf3..bb10a438 100644
--- a/src/lib/merchant_api_get_product.c
+++ b/src/lib/merchant_api_get_product.c
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2023 Taler Systems SA
+ Copyright (C) 2014-2024 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
@@ -105,12 +105,16 @@ handle_get_product_finished (void *cls,
TALER_JSON_spec_amount_any (
"price",
&pgr.details.ok.price),
- GNUNET_JSON_spec_string (
- "image",
- &pgr.details.ok.image),
- GNUNET_JSON_spec_array_const (
- "taxes",
- &pgr.details.ok.taxes),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string (
+ "image",
+ &pgr.details.ok.image),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const (
+ "taxes",
+ &pgr.details.ok.taxes),
+ NULL),
GNUNET_JSON_spec_int64 (
"total_stock",
&pgr.details.ok.total_stock),
@@ -120,12 +124,15 @@ handle_get_product_finished (void *cls,
GNUNET_JSON_spec_uint64 (
"total_lost",
&pgr.details.ok.total_lost),
- GNUNET_JSON_spec_object_const (
- "address",
- &pgr.details.ok.location),
GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("next_restock",
- &pgr.details.ok.next_restock),
+ GNUNET_JSON_spec_object_const (
+ "address",
+ &pgr.details.ok.location),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp (
+ "next_restock",
+ &pgr.details.ok.next_restock),
NULL),
GNUNET_JSON_spec_end ()
};
diff --git a/src/lib/merchant_api_patch_instance.c b/src/lib/merchant_api_patch_instance.c
index 420cd549..f4f39642 100644
--- a/src/lib/merchant_api_patch_instance.c
+++ b/src/lib/merchant_api_patch_instance.c
@@ -159,7 +159,6 @@ TALER_MERCHANT_instance_patch (
const char *backend_url,
const char *instance_id,
const char *name,
- enum TALER_KYCLOGIC_KycUserType ut,
const json_t *address,
const json_t *jurisdiction,
bool use_stefan,
@@ -170,19 +169,10 @@ TALER_MERCHANT_instance_patch (
{
struct TALER_MERCHANT_InstancePatchHandle *iph;
json_t *req_obj;
- const char *uts;
- uts = TALER_KYCLOGIC_kyc_user_type2s (ut);
- if (NULL == uts)
- {
- GNUNET_break (0);
- return NULL;
- }
req_obj = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("name",
name),
- GNUNET_JSON_pack_string ("user_type",
- uts),
GNUNET_JSON_pack_object_incref ("address",
(json_t *) address),
GNUNET_JSON_pack_object_incref ("jurisdiction",
diff --git a/src/lib/merchant_api_post_instances.c b/src/lib/merchant_api_post_instances.c
index 73d36369..c8e5add6 100644
--- a/src/lib/merchant_api_post_instances.c
+++ b/src/lib/merchant_api_post_instances.c
@@ -165,7 +165,6 @@ TALER_MERCHANT_instances_post (
const char *backend_url,
const char *instance_id,
const char *name,
- enum TALER_KYCLOGIC_KycUserType ut,
const json_t *address,
const json_t *jurisdiction,
bool use_stefan,
@@ -178,14 +177,7 @@ TALER_MERCHANT_instances_post (
struct TALER_MERCHANT_InstancesPostHandle *iph;
json_t *req_obj;
json_t *auth_obj;
- const char *uts;
- uts = TALER_KYCLOGIC_kyc_user_type2s (ut);
- if (NULL == uts)
- {
- GNUNET_break (0);
- return NULL;
- }
if (NULL != auth_token)
{
if (0 != strncasecmp (RFC_8959_PREFIX,
@@ -219,8 +211,6 @@ TALER_MERCHANT_instances_post (
instance_id),
GNUNET_JSON_pack_string ("name",
name),
- GNUNET_JSON_pack_string ("user_type",
- uts),
GNUNET_JSON_pack_object_incref ("address",
(json_t *) address),
GNUNET_JSON_pack_object_incref ("jurisdiction",
diff --git a/src/lib/merchant_api_post_products.c b/src/lib/merchant_api_post_products.c
index 0f09f397..5d0ad27e 100644
--- a/src/lib/merchant_api_post_products.c
+++ b/src/lib/merchant_api_post_products.c
@@ -159,7 +159,7 @@ handle_post_products_finished (void *cls,
struct TALER_MERCHANT_ProductsPostHandle *
-TALER_MERCHANT_products_post2 (
+TALER_MERCHANT_products_post3 (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
const char *product_id,
@@ -173,12 +173,28 @@ TALER_MERCHANT_products_post2 (
const json_t *address,
struct GNUNET_TIME_Timestamp next_restock,
uint32_t minimum_age,
+ unsigned int num_cats,
+ const uint64_t *cats,
TALER_MERCHANT_ProductsPostCallback cb,
void *cb_cls)
{
struct TALER_MERCHANT_ProductsPostHandle *pph;
json_t *req_obj;
+ json_t *categories;
+ if (0 == num_cats)
+ {
+ categories = NULL;
+ }
+ else
+ {
+ categories = json_array ();
+ GNUNET_assert (NULL != categories);
+ for (unsigned int i = 0; i<num_cats; i++)
+ GNUNET_assert (0 ==
+ json_array_append_new (categories,
+ json_integer (cats[i])));
+ }
req_obj = GNUNET_JSON_PACK (
GNUNET_JSON_pack_string ("product_id",
product_id),
@@ -187,6 +203,9 @@ TALER_MERCHANT_products_post2 (
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("description_i18n",
(json_t *) description_i18n)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_steal ("categories",
+ categories)),
GNUNET_JSON_pack_string ("unit",
unit),
TALER_JSON_pack_amount ("price",
@@ -243,6 +262,44 @@ TALER_MERCHANT_products_post2 (
struct TALER_MERCHANT_ProductsPostHandle *
+TALER_MERCHANT_products_post2 (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const char *product_id,
+ const char *description,
+ const json_t *description_i18n,
+ const char *unit,
+ const struct TALER_Amount *price,
+ const char *image,
+ const json_t *taxes,
+ int64_t total_stock,
+ const json_t *address,
+ struct GNUNET_TIME_Timestamp next_restock,
+ uint32_t minimum_age,
+ TALER_MERCHANT_ProductsPostCallback cb,
+ void *cb_cls)
+{
+ return TALER_MERCHANT_products_post3 (ctx,
+ backend_url,
+ product_id,
+ description,
+ description_i18n,
+ unit,
+ price,
+ image,
+ taxes,
+ total_stock,
+ address,
+ next_restock,
+ minimum_age,
+ 0,
+ NULL,
+ cb,
+ cb_cls);
+}
+
+
+struct TALER_MERCHANT_ProductsPostHandle *
TALER_MERCHANT_products_post (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
diff --git a/src/merchant-tools/taler-merchant-passwd.c b/src/merchant-tools/taler-merchant-passwd.c
index bfd6534d..bcb856e5 100644
--- a/src/merchant-tools/taler-merchant-passwd.c
+++ b/src/merchant-tools/taler-merchant-passwd.c
@@ -54,7 +54,7 @@ run (void *cls,
const char *pw = args[0];
struct TALER_MERCHANTDB_InstanceAuthSettings ias;
enum GNUNET_DB_QueryStatus qs;
-
+
if (NULL == pw)
pw = getenv ("TALER_MERCHANT_PASSWORD");
if (NULL == pw)
@@ -64,6 +64,16 @@ run (void *cls,
global_ret = -1;
return;
}
+ if (0 != strncmp (pw,
+ RFC_8959_PREFIX,
+ strlen (RFC_8959_PREFIX)))
+ {
+ fprintf (stderr,
+ "Invalid password specified, does not begin with `%s'\n",
+ RFC_8959_PREFIX);
+ global_ret = 1;
+ return;
+ }
if (NULL == instance)
instance = GNUNET_strdup ("default");
cfg = GNUNET_CONFIGURATION_dup (config);
@@ -112,7 +122,7 @@ run (void *cls,
.size = ntohs (sizeof (es)),
.type = ntohs (TALER_DBEVENT_MERCHANT_INSTANCE_SETTINGS)
};
-
+
plugin->event_notify (plugin->cls,
&es,
instance,
diff --git a/src/testing/test_merchant_order_refund.sh b/src/testing/test_merchant_order_refund.sh
new file mode 100755
index 00000000..af07d233
--- /dev/null
+++ b/src/testing/test_merchant_order_refund.sh
@@ -0,0 +1,233 @@
+#!/bin/bash
+#!/bin/bash
+# This file is in the public domain.
+
+set -eu
+
+function clean_wallet() {
+ echo rm -f "${WALLET_DB}"
+ exit_cleanup
+}
+
+
+# Replace with 0 for nexus...
+USE_FAKEBANK=1
+if [ 1 = "$USE_FAKEBANK" ]
+then
+ ACCOUNT="exchange-account-2"
+ BANK_FLAGS="-f -d x-taler-bank -u $ACCOUNT"
+ BANK_URL="http://localhost:8082/"
+else
+ ACCOUNT="exchange-account-1"
+ BANK_FLAGS="-ns -d iban -u $ACCOUNT"
+ BANK_URL="http://localhost:18082/"
+ echo -n "Testing for libeufin-bank"
+ libeufin-bank --help >/dev/null </dev/null || exit_skip " MISSING"
+ echo " FOUND"
+
+fi
+
+. setup.sh
+
+echo -n "Testing for taler-harness"
+taler-harness --help >/dev/null </dev/null || exit_skip " MISSING"
+echo " FOUND"
+
+# Launch exchange, merchant and bank.
+setup -c "test_template.conf" \
+ -em \
+ $BANK_FLAGS
+LAST_RESPONSE=$(mktemp -p "${TMPDIR:-/tmp}" test_response.conf-XXXXXX)
+CONF="test_template.conf.edited"
+WALLET_DB=$(mktemp -p "${TMPDIR:-/tmp}" test_wallet.json-XXXXXX)
+EXCHANGE_URL="http://localhost:8081/"
+
+# Install cleanup handler (except for kill -9)
+trap clean_wallet EXIT
+
+echo -n "First prepare wallet with coins ..."
+rm -f "$WALLET_DB"
+taler-wallet-cli \
+ --no-throttle \
+ --wallet-db="$WALLET_DB" \
+ api \
+ --expect-success 'withdrawTestBalance' \
+ "$(jq -n '
+ {
+ amount: "TESTKUDOS:99",
+ corebankApiBaseUrl: $BANK_URL,
+ exchangeBaseUrl: $EXCHANGE_URL
+ }' \
+ --arg BANK_URL "${BANK_URL}" \
+ --arg EXCHANGE_URL "$EXCHANGE_URL"
+ )" 2>wallet-withdraw-1.err >wallet-withdraw-1.out
+echo -n "."
+# FIXME-MS: add logic to have nexus check immediately here.
+# sleep 10
+echo -n "."
+# NOTE: once libeufin can do long-polling, we should
+# be able to reduce the delay here and run wirewatch
+# always in the background via setup
+taler-exchange-wirewatch \
+ -a "$ACCOUNT" \
+ -L "INFO" \
+ -c "$CONF" \
+ -t &> taler-exchange-wirewatch.out
+echo -n "."
+taler-wallet-cli \
+ --wallet-db="$WALLET_DB" \
+ run-until-done \
+ 2>wallet-withdraw-finish-1.err \
+ >wallet-withdraw-finish-1.out
+echo " OK"
+
+CURRENCY_COUNT=$(taler-wallet-cli --wallet-db="$WALLET_DB" balance | jq '.balances|length')
+if [ "$CURRENCY_COUNT" = "0" ]
+then
+ exit_fail "Expected least one currency, withdrawal failed. check log."
+fi
+
+#
+# CREATE INSTANCE FOR TESTING
+#
+
+echo -n "Configuring merchant instance ..."
+
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ -H 'Authorization: Bearer secret-token:super_secret' \
+ http://localhost:9966/management/instances \
+ -d '{"auth":{"method":"external"},"id":"default","name":"default","user_type":"business","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000000},"default_pay_delay":{"d_us": 60000000000}}' \
+ -w "%{http_code}" -s -o /dev/null)
+
+if [ "$STATUS" != "204" ]
+then
+ exit_fail "Expected '204 No content' response. Got instead $STATUS"
+fi
+echo "Ok"
+
+echo -n "Configuring merchant account ..."
+
+if [ 1 = "$USE_FAKEBANK" ]
+then
+ FORTYTHREE="payto://x-taler-bank/localhost/fortythree?receiver-name=fortythree"
+else
+ FORTYTHREE=$(get_payto_uri fortythree x)
+fi
+# create with 2 bank account addresses
+STATUS=$(curl -H "Content-Type: application/json" -X POST \
+ -H 'Authorization: Bearer secret-token:super_secret' \
+ http://localhost:9966/private/accounts \
+ -d '{"payto_uri":"'"$FORTYTHREE"'"}' \
+ -w "%{http_code}" -s -o /dev/null)
+
+if [ "$STATUS" != "200" ]
+then
+ exit_fail "Expected '200 OK' response. Got instead $STATUS"
+fi
+
+echo "Ok"
+
+
+#
+# CREATE ORDER AND SELL IT
+#
+
+echo -n "Creating order to be paid..."
+STATUS=$(curl 'http://localhost:9966/private/orders' \
+ -d '{"order":{"amount":"TESTKUDOS:5","summary":"payme","auto_refund":{"d_us":180000000}}}' \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ jq . < "$LAST_RESPONSE"
+ exit_fail "Expected 200, order created. got: $STATUS"
+fi
+
+ORDER_ID=$(jq -e -r .order_id < "$LAST_RESPONSE")
+TOKEN=$(jq -e -r .token < "$LAST_RESPONSE")
+
+STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ jq . < "$LAST_RESPONSE"
+ exit_fail "Expected 200, getting order info before claming it. got: $STATUS"
+fi
+
+PAY_URL=$(jq -e -r .taler_pay_uri < "$LAST_RESPONSE")
+
+echo "OK"
+
+#
+# PAY THE ORDER
+#
+# set -x
+PAYMENT_START=$(date +%s)
+echo "Pay first order ${PAY_URL} ..."
+taler-wallet-cli --no-throttle -V --wallet-db="$WALLET_DB" handle-uri "${PAY_URL}" -y 2> wallet-pay1.err > wallet-pay1.log
+PAYMENT_END=$(date +%s)
+echo " OK (payment took $(( PAYMENT_END - PAYMENT_START )) secs )"
+
+STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}" \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ jq . < "$LAST_RESPONSE"
+ exit_fail "Expected 200, after pay. got: $STATUS"
+fi
+
+ORDER_STATUS=$(jq -r .order_status < "$LAST_RESPONSE")
+
+if [ "$ORDER_STATUS" != "paid" ]
+then
+ jq . < "$LAST_RESPONSE"
+ exit_fail "Order status should be 'paid'. got: $ORDER_STATUS"
+fi
+
+
+echo Sending refund for TESTKUDOS:1
+
+STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}/refund" \
+ -d '{"refund":"TESTKUDOS:1","reason":"duplicated"}' \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ jq . < "$LAST_RESPONSE"
+ exit_fail "Expected 200, after refund. got: $STATUS"
+fi
+
+REFUND_URI=$(jq -e -r .taler_refund_uri < "$LAST_RESPONSE")
+
+REFUND_START=$(date +%s)
+echo "First refund order ${REFUND_URI} ..."
+set -x
+taler-wallet-cli --no-throttle --wallet-db="$WALLET_DB" handle-uri "${REFUND_URI}" -y 2> wallet-refund1.err > wallet-refund1.log
+taler-wallet-cli --no-throttle --wallet-db="$WALLET_DB" run-pending -y 2> wallet-pending-refund1.err > wallet-pending-refund1.log
+REFUND_END=$(date +%s)
+echo " OK (refund1 took $(( REFUND_END - REFUND_START )) secs )"
+
+echo Increasing refund for TESTKUDOS:3
+
+STATUS=$(curl "http://localhost:9966/private/orders/${ORDER_ID}/refund" \
+ -d '{"refund":"TESTKUDOS:5","reason":"duplicated"}' \
+ -w "%{http_code}" -s -o "$LAST_RESPONSE")
+
+if [ "$STATUS" != "200" ]
+then
+ jq . < "$LAST_RESPONSE"
+ exit_fail "Expected 200, after refund. got: $STATUS"
+fi
+
+REFUND_URI=$(jq -e -r .taler_refund_uri < "$LAST_RESPONSE")
+
+REFUND2_START=$(date +%s)
+echo "Second refund order ${REFUND_URI} ..."
+taler-wallet-cli --no-throttle --wallet-db="$WALLET_DB" handle-uri "${REFUND_URI}" -y 2> wallet-refund2.err > wallet-refund2.log
+taler-wallet-cli --no-throttle --wallet-db="$WALLET_DB" run-pending -y 2> wallet-pending-refund2.err > wallet-pending-refund2.log
+REFUND2_END=$(date +%s)
+echo " OK (refund2 took $(( REFUND2_END - REFUND_START )) secs )"
+
+exit 0
diff --git a/src/testing/testing_api_cmd_patch_instance.c b/src/testing/testing_api_cmd_patch_instance.c
index cef38bec..f4ae5cf7 100644
--- a/src/testing/testing_api_cmd_patch_instance.c
+++ b/src/testing/testing_api_cmd_patch_instance.c
@@ -158,7 +158,6 @@ patch_instance_run (void *cls,
pis->merchant_url,
pis->instance_id,
pis->name,
- TALER_KYCLOGIC_KYC_UT_BUSINESS,
pis->address,
pis->jurisdiction,
pis->use_stefan,
diff --git a/src/testing/testing_api_cmd_post_instances.c b/src/testing/testing_api_cmd_post_instances.c
index 0d081026..18afb2e8 100644
--- a/src/testing/testing_api_cmd_post_instances.c
+++ b/src/testing/testing_api_cmd_post_instances.c
@@ -164,7 +164,6 @@ post_instances_run (void *cls,
pis->merchant_url,
pis->instance_id,
pis->name,
- TALER_KYCLOGIC_KYC_UT_BUSINESS,
pis->address,
pis->jurisdiction,
pis->use_stefan,