commit 590e22e63779f18c2dc79f1226439e248d9d11e6
parent 867b0b85d31d8eeba341acd262913096e3bf4021
Author: Christian Grothoff <christian@grothoff.org>
Date: Sat, 27 Dec 2025 12:59:09 +0100
basic REST API for product groups
Diffstat:
16 files changed, 788 insertions(+), 117 deletions(-)
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
@@ -22,6 +22,7 @@ bin_PROGRAMS = \
taler-merchant-httpd \
taler-merchant-kyccheck \
taler-merchant-reconciliation \
+ taler-merchant-report-generator \
taler-merchant-webhook \
taler-merchant-wirewatch
@@ -234,6 +235,24 @@ taler_merchant_httpd_SOURCES = \
taler-merchant-httpd_qr.h \
taler-merchant-httpd_spa.c \
taler-merchant-httpd_spa.h \
+ taler-merchant-httpd_private-delete-report-ID.c \
+ taler-merchant-httpd_private-delete-report-ID.h \
+ taler-merchant-httpd_private-get-report-ID.c \
+ taler-merchant-httpd_private-get-report-ID.h \
+ taler-merchant-httpd_private-get-reports.c \
+ taler-merchant-httpd_private-get-reports.h \
+ taler-merchant-httpd_private-patch-report-ID.c \
+ taler-merchant-httpd_private-patch-report-ID.h \
+ taler-merchant-httpd_private-post-reports.c \
+ taler-merchant-httpd_private-post-reports.h \
+ taler-merchant-httpd_private-delete-group-ID.c \
+ taler-merchant-httpd_private-delete-group-ID.h \
+ taler-merchant-httpd_private-get-groups.c \
+ taler-merchant-httpd_private-get-groups.h \
+ taler-merchant-httpd_private-patch-group-ID.c \
+ taler-merchant-httpd_private-patch-group-ID.h \
+ taler-merchant-httpd_private-post-groups.c \
+ taler-merchant-httpd_private-post-groups.h \
taler-merchant-httpd_statics.c \
taler-merchant-httpd_statics.h
@@ -313,6 +332,25 @@ taler_merchant_reconciliation_CFLAGS = \
$(AM_CFLAGS)
+taler_merchant_report_generator_SOURCES = \
+ taler-merchant-report-generator.c
+taler_merchant_report_generator_LDADD = \
+ $(top_builddir)/src/backenddb/libtalermerchantdb.la \
+ $(top_builddir)/src/util/libtalermerchantutil.la \
+ -ltalerexchange \
+ -ltalerjson \
+ -ltalerutil \
+ -ltalerpq \
+ -lgnunetpq \
+ -lgnunetjson \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -lcurl \
+ $(XLIB)
+taler_merchant_report_generator_CFLAGS = \
+ $(AM_CFLAGS)
+
+
taler_merchant_webhook_SOURCES = \
taler-merchant-webhook.c
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c
@@ -112,6 +112,15 @@
#include "taler-merchant-httpd_spa.h"
#include "taler-merchant-httpd_statics.h"
#include "taler-merchant-httpd_terms.h"
+#include "taler-merchant-httpd_private-delete-report-ID.h"
+#include "taler-merchant-httpd_private-get-report-ID.h"
+#include "taler-merchant-httpd_private-get-reports.h"
+#include "taler-merchant-httpd_private-patch-report-ID.h"
+#include "taler-merchant-httpd_private-post-reports.h"
+#include "taler-merchant-httpd_private-get-groups.h"
+#include "taler-merchant-httpd_private-post-groups.h"
+#include "taler-merchant-httpd_private-patch-group-ID.h"
+#include "taler-merchant-httpd_private-delete-group-ID.h"
#ifdef HAVE_DONAU_DONAU_SERVICE_H
#include "taler-merchant-httpd_private-get-donau-instances.h"
@@ -2158,6 +2167,70 @@ url_handler (void *cls,
/* Body should be pretty small. */
.max_upload = 1024 * 1024
},
+
+ /* Reports endpoints */
+ {
+ .url_prefix = "reports",
+ .method = MHD_HTTP_METHOD_GET,
+ .permission = "reports-read",
+ .handler = &TMH_private_get_reports,
+ },
+ {
+ .url_prefix = "reports",
+ .method = MHD_HTTP_METHOD_POST,
+ .permission = "reports-write",
+ .handler = &TMH_private_post_reports,
+ },
+ {
+ .url_prefix = "reports",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler = &TMH_private_get_report,
+ .permission = "reports-read",
+ .have_id_segment = true,
+ },
+ {
+ .url_prefix = "reports",
+ .method = MHD_HTTP_METHOD_PATCH,
+ .handler = &TMH_private_patch_report,
+ .permission = "reports-write",
+ .have_id_segment = true,
+ },
+ {
+ .url_prefix = "reports",
+ .method = MHD_HTTP_METHOD_DELETE,
+ .handler = &TMH_private_delete_report,
+ .permission = "reports-write",
+ .have_id_segment = true,
+ },
+
+ /* Groups endpoints */
+ {
+ .url_prefix = "groups",
+ .method = MHD_HTTP_METHOD_GET,
+ .permission = "groups-read",
+ .handler = &TMH_private_get_groups,
+ },
+ {
+ .url_prefix = "groups",
+ .method = MHD_HTTP_METHOD_POST,
+ .permission = "groups-write",
+ .handler = &TMH_private_post_groups,
+ },
+ {
+ .url_prefix = "groups",
+ .method = MHD_HTTP_METHOD_PATCH,
+ .handler = &TMH_private_patch_group,
+ .permission = "groups-write",
+ .have_id_segment = true,
+ },
+ {
+ .url_prefix = "groups",
+ .method = MHD_HTTP_METHOD_DELETE,
+ .handler = &TMH_private_delete_group,
+ .permission = "groups-write",
+ .have_id_segment = true,
+ },
+
{
.url_prefix = "*",
.method = MHD_HTTP_METHOD_OPTIONS,
diff --git a/src/backend/taler-merchant-httpd_private-delete-group-ID.c b/src/backend/taler-merchant-httpd_private-delete-group-ID.c
@@ -0,0 +1,74 @@
+/*
+ This file is part of TALER
+ (C) 2025 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 Affero General Public License for more
+ details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant/backend/taler-merchant-httpd_private-delete-group-ID.c
+ * @brief implementation of DELETE /private/groups/$GROUP_ID
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-delete-group-ID.h"
+#include <taler/taler_json_lib.h>
+
+
+MHD_RESULT
+TMH_private_delete_group (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ const char *group_id_str = hc->infix;
+ unsigned long long group_id;
+ enum GNUNET_DB_QueryStatus qs;
+ char dummy;
+
+ (void) rh;
+ if (1 != sscanf (group_id_str,
+ "%llu%c",
+ &group_id,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "group_id");
+ }
+ qs = TMH_db->delete_product_group (TMH_db->cls,
+ hc->instance->settings.id,
+ group_id);
+
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "delete_product_group");
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_PRODUCT_GROUP_UNKNOWN,
+ group_id_str);
+ }
+ return TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
diff --git a/src/backend/taler-merchant-httpd_private-delete-group-ID.h b/src/backend/taler-merchant-httpd_private-delete-group-ID.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ (C) 2025 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 Affero General Public License for more
+ details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant/backend/taler-merchant-httpd_private-delete-group-ID.h
+ * @brief HTTP serving layer for deleting product groups
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_DELETE_GROUP_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_DELETE_GROUP_ID_H
+
+#include "taler-merchant-httpd.h"
+
+/**
+ * Handle DELETE /private/groups/$GROUP_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_group (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-delete-report-ID.c b/src/backend/taler-merchant-httpd_private-delete-report-ID.c
@@ -0,0 +1,76 @@
+/*
+ This file is part of TALER
+ (C) 2025 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 Affero General Public License for more
+ details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant/backend/taler-merchant-httpd_private-delete-report-ID.c
+ * @brief implementation of DELETE /private/reports/$REPORT_ID
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-delete-report-ID.h"
+#include <taler/taler_json_lib.h>
+
+
+MHD_RESULT
+TMH_private_delete_report (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ const char *report_id_str = hc->infix;
+ unsigned long long report_id;
+ enum GNUNET_DB_QueryStatus qs;
+ char dummy;
+
+ (void) rh;
+
+ if (1 !=
+ sscanf (report_id_str,
+ "%llu%c",
+ &report_id,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "report_id");
+ }
+
+ qs = TMH_db->delete_report (TMH_db->cls,
+ hc->instance->settings.id,
+ (uint64_t) report_id);
+ if (qs < 0)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "delete_report");
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_REPORT_UNKNOWN,
+ report_id_str);
+ }
+ return TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
diff --git a/src/backend/taler-merchant-httpd_private-delete-report-ID.h b/src/backend/taler-merchant-httpd_private-delete-report-ID.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ (C) 2025 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 Affero General Public License for more
+ details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant/backend/taler-merchant-httpd_private-delete-report-ID.h
+ * @brief HTTP serving layer for deleting reports
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_DELETE_REPORT_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_DELETE_REPORT_ID_H
+#include "taler-merchant-httpd.h"
+
+/**
+ * Handle DELETE /private/reports/$REPORT_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_report (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-delete-report.c b/src/backend/taler-merchant-httpd_private-delete-report.c
@@ -1,76 +0,0 @@
-/*
- This file is part of TALER
- (C) 2025 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 Affero General Public License for more
- details.
-
- You should have received a copy of the GNU Affero General Public License
- along with TALER; see the file COPYING. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file merchant/backend/taler-merchant-httpd_private-delete-report.c
- * @brief implementation of DELETE /private/reports/$REPORT_ID
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler-merchant-httpd_private-delete-report.h"
-#include <taler/taler_json_lib.h>
-
-
-MHD_RESULT
-TMH_private_delete_report (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc)
-{
- const char *report_id_str = hc->infix;
- unsigned long long report_id;
- enum GNUNET_DB_QueryStatus qs;
- char dummy;
-
- (void) rh;
-
- if (1 !=
- sscanf (report_id_str,
- "%llu%c",
- &report_id,
- &dummy))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "report_id");
- }
-
- qs = TMH_db->delete_report (TMH_db->cls,
- hc->instance->settings.id,
- (uint64_t) report_id);
- if (qs < 0)
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "delete_report");
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_MERCHANT_GENERIC_REPORT_UNKNOWN,
- report_id_str);
- }
- return TALER_MHD_reply_static (connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
-}
diff --git a/src/backend/taler-merchant-httpd_private-delete-report.h b/src/backend/taler-merchant-httpd_private-delete-report.h
@@ -1,40 +0,0 @@
-/*
- This file is part of TALER
- (C) 2025 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 Affero General Public License for more
- details.
-
- You should have received a copy of the GNU Affero General Public License
- along with TALER; see the file COPYING. If not, see
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file merchant/backend/taler-merchant-httpd_private-delete-report.h
- * @brief HTTP serving layer for deleting reports
- * @author Christian Grothoff
- */
-#ifndef TALER_MERCHANT_HTTPD_PRIVATE_DELETE_REPORT_H
-#define TALER_MERCHANT_HTTPD_PRIVATE_DELETE_REPORT_H
-#include "taler-merchant-httpd.h"
-
-/**
- * Handle DELETE /private/reports/$REPORT_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_report (const struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- struct TMH_HandlerContext *hc);
-
-#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-groups.c b/src/backend/taler-merchant-httpd_private-get-groups.c
@@ -0,0 +1,122 @@
+/*
+ This file is part of TALER
+ (C) 2025 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 Affero General Public License for more
+ details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant/backend/taler-merchant-httpd_private-get-groups.c
+ * @brief implementation of GET /private/groups
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-groups.h"
+#include <taler/taler_json_lib.h>
+
+/**
+ * Sensible bound on the number of results to return
+ */
+#define MAX_DELTA 1024
+
+
+/**
+ * Callback for listing product groups.
+ *
+ * @param cls closure with a `json_t *`
+ * @param product_group_id unique identifier of the group
+ * @param group_name name of the group
+ * @param group_description human-readable description
+ */
+static void
+add_group (void *cls,
+ uint64_t product_group_id,
+ const char *group_name,
+ const char *group_description)
+{
+ json_t *groups = cls;
+ json_t *entry;
+
+ entry = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("group_serial",
+ product_group_id),
+ GNUNET_JSON_pack_string ("group_name",
+ group_name),
+ GNUNET_JSON_pack_string ("description",
+ group_description));
+ GNUNET_assert (NULL != entry);
+ GNUNET_assert (0 ==
+ json_array_append_new (groups,
+ entry));
+}
+
+
+MHD_RESULT
+TMH_private_get_groups (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ int64_t limit = -20;
+ uint64_t offset;
+ json_t *groups;
+
+ (void) rh;
+ TALER_MHD_parse_request_snumber (connection,
+ "limit",
+ &limit);
+ if ( (-MAX_DELTA > limit) ||
+ (limit > MAX_DELTA) )
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "limit");
+ }
+ if (limit > 0)
+ offset = 0;
+ else
+ offset = INT64_MAX;
+ TALER_MHD_parse_request_number (connection,
+ "offset",
+ &offset);
+
+ groups = json_array ();
+ GNUNET_assert (NULL != groups);
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TMH_db->select_product_groups (TMH_db->cls,
+ hc->instance->settings.id,
+ limit,
+ offset,
+ &add_group,
+ groups);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ json_decref (groups);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_product_groups");
+ }
+ }
+
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("groups",
+ groups));
+}
diff --git a/src/backend/taler-merchant-httpd_private-get-groups.h b/src/backend/taler-merchant-httpd_private-get-groups.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ (C) 2025 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 Affero General Public License for more
+ details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant/backend/taler-merchant-httpd_private-get-groups.h
+ * @brief HTTP serving layer for listing product groups
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_GROUPS_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_GROUPS_H
+
+#include "taler-merchant-httpd.h"
+
+/**
+ * Handle GET /private/groups 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_groups (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-patch-group-ID.c b/src/backend/taler-merchant-httpd_private-patch-group-ID.c
@@ -0,0 +1,110 @@
+/*
+ This file is part of TALER
+ (C) 2025 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 Affero General Public License for more
+ details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant/backend/taler-merchant-httpd_private-patch-group-ID.c
+ * @brief implementation of PATCH /private/groups/$GROUP_ID
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-patch-group-ID.h"
+#include <taler/taler_json_lib.h>
+
+
+MHD_RESULT
+TMH_private_patch_group (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ const char *group_id_str = hc->infix;
+ unsigned long long group_id;
+ const char *group_name;
+ const char *description;
+ enum GNUNET_DB_QueryStatus qs;
+ bool conflict;
+ char dummy;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("group_name",
+ &group_name),
+ GNUNET_JSON_spec_string ("description",
+ &description),
+ GNUNET_JSON_spec_end ()
+ };
+
+ (void) rh;
+ if (1 != sscanf (group_id_str,
+ "%llu%c",
+ &group_id,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "group_id");
+ }
+
+ {
+ 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;
+ }
+ }
+
+ qs = TMH_db->update_product_group (TMH_db->cls,
+ hc->instance->settings.id,
+ (uint64_t) group_id,
+ group_name,
+ description,
+ &conflict);
+
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "update_product_group");
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_PRODUCT_GROUP_UNKNOWN,
+ group_id_str);
+ }
+ if (conflict)
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_PRODUCT_GROUP_CONFLICTING_NAME,
+ group_name);
+ }
+ return TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
diff --git a/src/backend/taler-merchant-httpd_private-patch-group-ID.h b/src/backend/taler-merchant-httpd_private-patch-group-ID.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ (C) 2025 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 Affero General Public License for more
+ details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant/backend/taler-merchant-httpd_private-patch-group-ID.h
+ * @brief HTTP serving layer for updating product groups
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_PATCH_GROUP_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_PATCH_GROUP_ID_H
+
+#include "taler-merchant-httpd.h"
+
+/**
+ * Handle PATCH /private/groups/$GROUP_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_patch_group (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-post-groups.c b/src/backend/taler-merchant-httpd_private-post-groups.c
@@ -0,0 +1,88 @@
+/*
+ This file is part of TALER
+ (C) 2025 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 Affero General Public License for more
+ details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant/backend/taler-merchant-httpd_private-post-groups.c
+ * @brief implementation of POST /private/groups
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-post-groups.h"
+#include <taler/taler_json_lib.h>
+
+
+MHD_RESULT
+TMH_private_post_groups (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ const char *group_name;
+ const char *description;
+ enum GNUNET_DB_QueryStatus qs;
+ uint64_t group_id;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("group_name",
+ &group_name),
+ GNUNET_JSON_spec_string ("description",
+ &description),
+ GNUNET_JSON_spec_end ()
+ };
+
+ (void) rh;
+ {
+ 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;
+ }
+ }
+
+ qs = TMH_db->insert_product_group (TMH_db->cls,
+ hc->instance->settings.id,
+ group_name,
+ description,
+ &group_id);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_product_group");
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ /* Zero will be returned on conflict */
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_PRODUCT_GROUP_CONFLICTING_NAME,
+ group_name);
+ }
+
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_uint64 ("group_serial_id",
+ group_id));
+}
diff --git a/src/backend/taler-merchant-httpd_private-post-groups.h b/src/backend/taler-merchant-httpd_private-post-groups.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ (C) 2025 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 Affero General Public License for more
+ details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with TALER; see the file COPYING. If not, see
+ <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant/backend/taler-merchant-httpd_private-post-groups.h
+ * @brief HTTP serving layer for creating product groups
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_GROUPS_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_POST_GROUPS_H
+
+#include "taler-merchant-httpd.h"
+
+/**
+ * Handle POST /private/groups 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_post_groups (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backenddb/pg_insert_product_group.c b/src/backenddb/pg_insert_product_group.c
@@ -57,6 +57,7 @@ TMH_PG_insert_product_group (
" SELECT merchant_serial, $2, $3"
" FROM merchant_instances"
" WHERE merchant_id=$1"
+ " ON CONFLICT DO NOTHING"
" RETURNING product_group_serial");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h
@@ -5244,7 +5244,8 @@ struct TALER_MERCHANTDB_Plugin
* @param name set to name of the group
* @param description set to description of the group
* @param[out] product_group_id serial number of the new group
- * @return database result code
+ * @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+ * on conflict (@a name already in use at @a instance_id).
*/
enum GNUNET_DB_QueryStatus
(*insert_product_group)(