commit a1cf5ed17a55dd3bb43b97a476dd1d64c646c614
parent 590e22e63779f18c2dc79f1226439e248d9d11e6
Author: Christian Grothoff <christian@grothoff.org>
Date: Sat, 27 Dec 2025 15:28:18 +0100
work on report generation, e-mail submission, etc.
Diffstat:
27 files changed, 1902 insertions(+), 44 deletions(-)
diff --git a/debian/control b/debian/control
@@ -59,7 +59,8 @@ Depends:
Recommends:
postgresql (>=15.0),
taler-terms-generator,
- apache2 | nginx | httpd
+ apache2 | nginx | httpd,
+ sharutils
Description: GNU's payment system merchant backend.
.
The GNU Taler merchant backend provides e-commerce
diff --git a/debian/rules b/debian/rules
@@ -43,6 +43,7 @@ override_dh_installsystemd:
dh_installsystemd -ptaler-merchant --name=taler-merchant-donaukeyupdate --no-start --no-enable
dh_installsystemd -ptaler-merchant --name=taler-merchant-kyccheck --no-start --no-enable
dh_installsystemd -ptaler-merchant --name=taler-merchant-reconciliation --no-start --no-enable
+ dh_installsystemd -ptaler-merchant --name=taler-merchant-report-generator --no-start --no-enable
dh_installsystemd -ptaler-merchant --name=taler-merchant-webhook --no-start --no-enable
dh_installsystemd -ptaler-merchant --name=taler-merchant-wirewatch --no-start --no-enable
dh_installsystemd -ptaler-merchant --name=taler-merchant --no-start --no-enable
diff --git a/debian/taler-merchant.taler-merchant-report-generator.service b/debian/taler-merchant.taler-merchant-report-generator.service
@@ -0,0 +1,21 @@
+[Unit]
+Description=GNU Taler merchant report generation service
+After=postgresql.service
+PartOf=taler-merchant.target
+
+[Service]
+User=taler-merchant-httpd
+Type=simple
+Restart=always
+RestartMode=direct
+RestartSec=1s
+RestartPreventExitStatus=9
+ExecStart=/usr/bin/taler-merchant-report-generator -c /etc/taler-merchant/taler-merchant.conf -L INFO
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+RuntimeMaxSec=3600s
+Slice=taler-merchant.slice
+
+StandardOutput=journal
+StandardError=journal
diff --git a/debian/taler-merchant.taler-merchant.target b/debian/taler-merchant.taler-merchant.target
@@ -8,6 +8,7 @@ Wants=taler-merchant-donaukeyupdate.service
Wants=taler-merchant-httpd.service
Wants=taler-merchant-kyccheck.service
Wants=taler-merchant-reconciliation.service
+Wants=taler-merchant-report-generator.service
Wants=taler-merchant-webhook.service
Wants=taler-merchant-wirewatch.service
diff --git a/doc/Makefile.am b/doc/Makefile.am
@@ -13,6 +13,8 @@ man_MANS = \
prebuilt/man/taler-merchant-kyccheck.1 \
prebuilt/man/taler-merchant-passwd.1 \
prebuilt/man/taler-merchant-reconciliation.1 \
+ prebuilt/man/taler-merchant-report-generator.1 \
+ prebuilt/man/taler-merchant-report-generator-email.1 \
prebuilt/man/taler-merchant-rproxy-setup.1 \
prebuilt/man/taler-merchant-webhook.1 \
prebuilt/man/taler-merchant-wirewatch.1
diff --git a/src/backend/.gitignore b/src/backend/.gitignore
@@ -5,3 +5,4 @@ taler-merchant-reconciliation
taler-merchant-webhook
taler-merchant-wirewatch
taler-merchant-donaukeyupdate
+taler-merchant-report-generator
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
@@ -13,8 +13,13 @@ pkgcfg_DATA = \
merchant.conf \
tops.conf
+
+bin_SCRIPTS = \
+ taler-merchant-report-generator-email
+
EXTRA_DIST = \
- $(pkgcfg_DATA)
+ $(pkgcfg_DATA) \
+ $(bin_SCRIPTS)
bin_PROGRAMS = \
taler-merchant-depositcheck \
@@ -245,6 +250,16 @@ taler_merchant_httpd_SOURCES = \
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-pot-ID.c \
+ taler-merchant-httpd_private-delete-pot-ID.h \
+ taler-merchant-httpd_private-get-pot-ID.c \
+ taler-merchant-httpd_private-get-pot-ID.h \
+ taler-merchant-httpd_private-get-pots.c \
+ taler-merchant-httpd_private-get-pots.h \
+ taler-merchant-httpd_private-patch-pot-ID.c \
+ taler-merchant-httpd_private-patch-pot-ID.h \
+ taler-merchant-httpd_private-post-pots.c \
+ taler-merchant-httpd_private-post-pots.h \
taler-merchant-httpd_private-delete-group-ID.c \
taler-merchant-httpd_private-delete-group-ID.h \
taler-merchant-httpd_private-get-groups.c \
diff --git a/src/backend/merchant.conf b/src/backend/merchant.conf
@@ -69,3 +69,7 @@ AML_FREQ = 6h
# How long do we wait between AML status requests to the
# exchange if we do not expect any AML status changes?
AML_LOW_FREQ = 7d
+
+
+[report-generator-email]
+BINARY = taler-merchant-report-generator-email
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c
@@ -117,6 +117,11 @@
#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-delete-pot-ID.h"
+#include "taler-merchant-httpd_private-get-pot-ID.h"
+#include "taler-merchant-httpd_private-get-pots.h"
+#include "taler-merchant-httpd_private-patch-pot-ID.h"
+#include "taler-merchant-httpd_private-post-pots.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"
@@ -2231,6 +2236,41 @@ url_handler (void *cls,
.have_id_segment = true,
},
+ /* Money pots endpoints */
+ {
+ .url_prefix = "pots",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler = &TMH_private_get_pots,
+ .permission = "pots-read",
+ },
+ {
+ .url_prefix = "pots",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler = &TMH_private_post_pots,
+ .permission = "pots-write"
+ },
+ {
+ .url_prefix = "pots",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler = &TMH_private_get_pot,
+ .have_id_segment = true,
+ .permission = "pots-read",
+ },
+ {
+ .url_prefix = "pots",
+ .method = MHD_HTTP_METHOD_PATCH,
+ .handler = &TMH_private_patch_pot,
+ .have_id_segment = true,
+ .permission = "pots-write"
+ },
+ {
+ .url_prefix = "pots",
+ .method = MHD_HTTP_METHOD_DELETE,
+ .handler = &TMH_private_delete_pot,
+ .have_id_segment = true,
+ .permission = "pots-write"
+ },
+
{
.url_prefix = "*",
.method = MHD_HTTP_METHOD_OPTIONS,
diff --git a/src/backend/taler-merchant-httpd_private-delete-pot-ID.c b/src/backend/taler-merchant-httpd_private-delete-pot-ID.c
@@ -0,0 +1,75 @@
+/*
+ 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-pot-ID.c
+ * @brief implementation of DELETE /private/pots/$POT_ID
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-delete-pot-ID.h"
+#include <taler/taler_json_lib.h>
+
+
+MHD_RESULT
+TMH_private_delete_pot (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ const char *pot_id_str = hc->infix;
+ unsigned long long pot_id;
+ enum GNUNET_DB_QueryStatus qs;
+ char dummy;
+
+ (void) rh;
+ if (1 != sscanf (pot_id_str,
+ "%llu%c",
+ &pot_id,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "pot_id");
+ }
+
+ qs = TMH_db->delete_money_pot (TMH_db->cls,
+ hc->instance->settings.id,
+ pot_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_money_pot");
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_MONEY_POT_UNKNOWN,
+ pot_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-pot-ID.h b/src/backend/taler-merchant-httpd_private-delete-pot-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-pot-ID.h
+ * @brief HTTP serving layer for deleting money pots
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_DELETE_POT_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_DELETE_POT_ID_H
+
+#include "taler-merchant-httpd.h"
+
+/**
+ * Handle DELETE /private/pots/$POT_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_pot (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-pot-ID.c b/src/backend/taler-merchant-httpd_private-get-pot-ID.c
@@ -0,0 +1,93 @@
+/*
+ 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-pot-ID.c
+ * @brief implementation of GET /private/pots/$POT_ID
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-pot-ID.h"
+#include <taler/taler_json_lib.h>
+
+
+MHD_RESULT
+TMH_private_get_pot (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ const char *pot_id_str = hc->infix;
+ unsigned long long pot_id;
+ char *pot_name;
+ char *description;
+ struct TALER_Amount pot_total;
+ enum GNUNET_DB_QueryStatus qs;
+ char dummy;
+
+ (void) rh;
+ if (1 != sscanf (pot_id_str,
+ "%llu%c",
+ &pot_id,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "pot_id");
+ }
+ qs = TMH_db->select_money_pot (TMH_db->cls,
+ hc->instance->settings.id,
+ pot_id,
+ &pot_name,
+ &description,
+ &pot_total);
+
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_money_pot");
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_MONEY_POT_UNKNOWN,
+ pot_id_str);
+ }
+
+ {
+ MHD_RESULT ret;
+
+ ret = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("description",
+ description),
+ GNUNET_JSON_pack_string ("pot_name",
+ pot_name),
+ TALER_JSON_pack_amount ("pot_total",
+ &pot_total));
+ GNUNET_free (pot_name);
+ GNUNET_free (description);
+ return ret;
+ }
+}
diff --git a/src/backend/taler-merchant-httpd_private-get-pot-ID.h b/src/backend/taler-merchant-httpd_private-get-pot-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-get-pot-ID.h
+ * @brief HTTP serving layer for getting pot details
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_POT_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_POT_ID_H
+
+#include "taler-merchant-httpd.h"
+
+/**
+ * Handle GET /private/pots/$POT_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_pot (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-pots.c b/src/backend/taler-merchant-httpd_private-get-pots.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-pots.c
+ * @brief implementation of GET /private/pots
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-get-pots.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * Sensible bound on the limit.
+ */
+#define MAX_DELTA 1024
+
+
+/**
+ * Callback for listing money pots.
+ *
+ * @param cls closure with a `json_t *`
+ * @param money_pot_id unique identifier of the pot
+ * @param name name of the pot
+ * @param description human-readable description (ignored for listing)
+ * @param pot_total current total amount in the pot
+ */
+static void
+add_pot (void *cls,
+ uint64_t money_pot_id,
+ const char *name,
+ const struct TALER_Amount *pot_total)
+{
+ json_t *pots = cls;
+ json_t *entry;
+
+ entry = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("pot_serial",
+ money_pot_id),
+ GNUNET_JSON_pack_string ("pot_name",
+ name),
+ TALER_JSON_pack_amount ("pot_total",
+ pot_total));
+ GNUNET_assert (NULL != entry);
+ GNUNET_assert (0 ==
+ json_array_append_new (pots,
+ entry));
+}
+
+
+MHD_RESULT
+TMH_private_get_pots (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ int64_t limit = -20;
+ uint64_t offset;
+ json_t *pots;
+
+ (void) rh;
+ TALER_MHD_parse_request_snumber (connection,
+ "limit",
+ &limit);
+ if (limit > 0)
+ offset = 0;
+ else
+ offset = INT64_MAX;
+ TALER_MHD_parse_request_number (connection,
+ "offset",
+ &offset);
+ 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");
+ }
+
+ pots = json_array ();
+ GNUNET_assert (NULL != pots);
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TMH_db->select_money_pots (TMH_db->cls,
+ hc->instance->settings.id,
+ limit,
+ offset,
+ &add_pot,
+ pots);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ json_decref (pots);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_money_pots");
+ }
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("pots",
+ pots));
+}
diff --git a/src/backend/taler-merchant-httpd_private-get-pots.h b/src/backend/taler-merchant-httpd_private-get-pots.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-pots.h
+ * @brief HTTP serving layer for listing money pots
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_GET_POTS_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_GET_POTS_H
+
+#include "taler-merchant-httpd.h"
+
+/**
+ * Handle GET /private/pots 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_pots (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-patch-pot-ID.c b/src/backend/taler-merchant-httpd_private-patch-pot-ID.c
@@ -0,0 +1,165 @@
+/*
+ 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-pot.c
+ * @brief implementation of PATCH /private/pots/$POT_ID
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-patch-pot-ID.h"
+#include <taler/taler_json_lib.h>
+
+
+/**
+ * ISSUE: The database API and REST API have a conflict handling mismatch.
+ *
+ * REST API spec says:
+ * - Return 409 Conflict if "the pot total did not match the expected total"
+ *
+ * Database API provides:
+ * - conflict parameter that is set if old_pot_total doesn't match
+ *
+ * However, there's a semantic problem:
+ * - The conflict parameter is about pot_name uniqueness in update_product_group
+ * - But here it's about pot total mismatch in update_money_pot
+ * - This is inconsistent terminology across the database API
+ *
+ * The implementation below assumes 'conflict' means "pot total mismatch"
+ * for money pots, but this should be clarified.
+ */
+
+
+MHD_RESULT
+TMH_private_patch_pot (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ const char *pot_id_str = hc->infix;
+ unsigned long long pot_id;
+ const char *pot_name;
+ const char *description;
+ struct TALER_Amount expected_pot_total;
+ bool no_expected_total;
+ struct TALER_Amount new_pot_total;
+ bool no_new_total;
+ enum GNUNET_DB_QueryStatus qs;
+ bool conflict_total;
+ bool conflict_name;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("pot_name",
+ &pot_name),
+ GNUNET_JSON_spec_string ("description",
+ &description),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_amount_any ("expected_pot_total",
+ &expected_pot_total),
+ &no_expected_total),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_amount_any ("new_pot_total",
+ &new_pot_total),
+ &no_new_total),
+ GNUNET_JSON_spec_end ()
+ };
+
+ (void) rh;
+ {
+ char dummy;
+
+ if (1 != sscanf (pot_id_str,
+ "%llu%c",
+ &pot_id,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "pot_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_money_pot (TMH_db->cls,
+ hc->instance->settings.id,
+ pot_id,
+ pot_name,
+ description,
+ no_expected_total
+ ? NULL
+ : &expected_pot_total,
+ no_new_total
+ ? NULL
+ : &new_pot_total,
+ &conflict_total,
+ &conflict_name);
+
+ 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_money_pot");
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_MONEY_POT_UNKNOWN,
+ pot_id_str);
+ }
+ if (conflict_total)
+ {
+ /* Pot total mismatch - expected_pot_total didn't match current value */
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_MONEY_POT_CONFLICTING_TOTAL,
+ NULL);
+ }
+ if (conflict_name)
+ {
+ /* Pot name conflict - name exists */
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_PRIVATE_MONEY_POT_CONFLICTING_NAME,
+ pot_name);
+ }
+
+ return TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
diff --git a/src/backend/taler-merchant-httpd_private-patch-pot-ID.h b/src/backend/taler-merchant-httpd_private-patch-pot-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-pot-ID.h
+ * @brief HTTP serving layer for updating money pots
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_PATCH_POT_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_PATCH_POT_ID_H
+
+#include "taler-merchant-httpd.h"
+
+/**
+ * Handle PATCH /private/pots/$POT_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_pot (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-post-pots.c b/src/backend/taler-merchant-httpd_private-post-pots.c
@@ -0,0 +1,105 @@
+/*
+ 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-pots.c
+ * @brief implementation of POST /private/pots
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler-merchant-httpd_private-post-pots.h"
+#include <taler/taler_json_lib.h>
+
+
+MHD_RESULT
+TMH_private_post_pots (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ const char *pot_name;
+ const char *description;
+ const char *currency;
+ enum GNUNET_DB_QueryStatus qs;
+ uint64_t pot_id;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("pot_name",
+ &pot_name),
+ GNUNET_JSON_spec_string ("description",
+ &description),
+ GNUNET_JSON_spec_string ("currency",
+ ¤cy),
+ 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;
+ }
+ }
+
+ /* Validate currency string */
+ if (GNUNET_OK !=
+ TALER_check_currency (currency))
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_CURRENCY_MISMATCH,
+ currency);
+ }
+
+ qs = TMH_db->insert_money_pot (TMH_db->cls,
+ hc->instance->settings.id,
+ pot_name,
+ description,
+ currency,
+ &pot_id);
+
+ if (qs < 0)
+ {
+ /* NOTE: Like product groups, we cannot distinguish between a
+ * generic DB error and a unique constraint violation on pot_name.
+ */
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_money_pot");
+ }
+ 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_MONEY_POT_CONFLICTING_NAME,
+ pot_name);
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_uint64 ("pot_serial_id",
+ pot_id));
+}
diff --git a/src/backend/taler-merchant-httpd_private-post-pots.h b/src/backend/taler-merchant-httpd_private-post-pots.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-post-pots.h
+ * @brief HTTP serving layer for creating money pots
+ * @author Christian Grothoff
+ */
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_POST_POTS_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_POST_POTS_H
+#include "taler-merchant-httpd.h"
+
+/**
+ * Handle POST /private/pots 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_pots (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-report-generator-email b/src/backend/taler-merchant-report-generator-email
@@ -0,0 +1,161 @@
+#!/bin/bash
+#
+# This file is part of TALER
+# Copyright (C) 2025 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/>
+#
+
+#
+# Script to email Taler merchant reports using the UNIX mail command.
+# Reads report data from stdin and sends it via email with appropriate formatting.
+#
+# Usage: taler-merchant-report-generator-email -d DESCRIPTION -m MIME_TYPE -t TARGET_ADDRESS
+#
+
+set -eu
+
+DESCRIPTION=""
+MIME_TYPE=""
+TARGET_ADDRESS=""
+TMPDIR="${TMPDIR:-/tmp}"
+
+while getopts "d:m:t:h" opt; do
+ case $opt in
+ d)
+ DESCRIPTION="$OPTARG"
+ ;;
+ m)
+ MIME_TYPE="$OPTARG"
+ ;;
+ t)
+ TARGET_ADDRESS="$OPTARG"
+ ;;
+ h)
+ echo "Usage: $0 -d DESCRIPTION -m MIME_TYPE -t EMAIL_ADDRESS"
+ echo ""
+ echo "Sends reports via email."
+ echo ""
+ echo "Options:"
+ echo " -d DESCRIPTION Subject line for the email"
+ echo " -m MIME_TYPE MIME type of the report (e.g., text/plain, application/pdf)"
+ echo " -t EMAIL_ADDRESS Email address to send the report to"
+ echo " -h Show this help message"
+ echo ""
+ echo "The report data is read from stdin."
+ exit 0
+ ;;
+ \?)
+ echo "Invalid option: -$OPTARG" >&2
+ echo "Use -h for help" >&2
+ exit 1
+ ;;
+ esac
+done
+
+if [ -z "$DESCRIPTION" ];
+then
+ echo "Error: Description (-d) is required" >&2
+ exit 1
+fi
+
+if [ -z "$MIME_TYPE" ];
+then
+ echo "Error: MIME type (-m) is required" >&2
+ exit 1
+fi
+
+if [ -z "$TARGET_ADDRESS" ];
+then
+ echo "Error: Target address (-t) is required" >&2
+ exit 1
+fi
+
+# Validate email address format (basic check)
+if ! echo "$TARGET_ADDRESS" | grep -qE '^[^@]+@[^@]+\.[^@]+$';
+then
+ echo "Error: Invalid email address format: $TARGET_ADDRESS" >&2
+ exit 1
+fi
+
+if ! command -v mail >/dev/null 2>&1;
+then
+ echo "Error: 'mail' command not found." >&2
+ exit 1
+fi
+if ! command -v uuencode >/dev/null 2>&1;
+then
+ echo "Error: 'uuencode' command not found." >&2
+ exit 1
+fi
+
+# Normalize MIME type to lowercase for comparison
+MIME_TYPE=$(echo "$MIME_TYPE" | tr '[:upper:]' '[:lower:]')
+
+# Handle different MIME types
+case "$MIME_TYPE" in
+ text/plain)
+ # For plain text, send directly as email body
+ mail -s "$DESCRIPTION" "$TARGET_ADDRESS"
+ ;;
+
+ *)
+ # For all other MIME types, create a MIME attachment
+ # Create temporary files
+ TMPFILE=$(mktemp "$TMPDIR/taler-report.XXXXXX")
+ MIMEFILE=$(mktemp "$TMPDIR/taler-mime.XXXXXX")
+
+ # Ensure cleanup on exit
+ trap "rm -f '$TMPFILE' '$MIMEFILE'" EXIT
+
+ # Save stdin to temporary file
+ cat - > "$TMPFILE"
+
+ # Determine file extension based on MIME type
+ case "$MIME_TYPE" in
+ application/pdf)
+ EXT="pdf"
+ ;;
+ application/json)
+ EXT="json"
+ ;;
+ text/html)
+ EXT="html"
+ ;;
+ text/csv)
+ EXT="csv"
+ ;;
+ application/xml)
+ EXT="xml"
+ ;;
+ application/zip)
+ EXT="zip"
+ ;;
+ image/png)
+ EXT="png"
+ ;;
+ image/jpeg)
+ EXT="jpg"
+ ;;
+ *)
+ EXT="dat"
+ ;;
+ esac
+
+ FILENAME="report.$EXT"
+
+ # Use uuencode method (works with traditional mail command)
+ uuencode "$TMPFILE" "$FILENAME" | mail -s "$DESCRIPTION" "$TARGET_ADDRESS"
+ ;;
+esac
+
+exit 0
+\ No newline at end of file
diff --git a/src/backend/taler-merchant-report-generator.c b/src/backend/taler-merchant-report-generator.c
@@ -0,0 +1,823 @@
+/*
+ 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 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-report-generator.c
+ * @brief Service for fetching and transmitting merchant reports
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_db_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include <taler/taler_merchantdb_plugin.h>
+#include <taler/taler_merchantdb_lib.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_error_codes.h>
+#include <taler/taler_merchant_service.h>
+#include <curl/curl.h>
+
+
+/**
+ * Information about an active reporting activity.
+ */
+struct ReportActivity
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct ReportActivity *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct ReportActivity *prev;
+
+ /**
+ * Transmission program that is running.
+ */
+ struct GNUNET_OS_Process *proc;
+
+ /**
+ * Handle to wait for @e proc to terminate.
+ */
+ struct GNUNET_ChildWaitHandle *cwh;
+
+ /**
+ * CURL easy handle for the HTTP request.
+ */
+ CURL *eh;
+
+ /**
+ * Job handle for the HTTP request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * URL of the request.
+ */
+ char *url;
+
+ /**
+ * ID of the instance we are working on.
+ */
+ char *instance_id;
+
+ /**
+ * Report program section.
+ */
+ char *report_program_section;
+
+ /**
+ * Report description.
+ */
+ char *report_description;
+
+ /**
+ * Target address for transmission.
+ */
+ char *target_address;
+
+ /**
+ * MIME type of the report.
+ */
+ char *mime_type;
+
+ /**
+ * Report we are working on.
+ */
+ uint64_t report_id;
+
+ /**
+ * Next transmission time, already calculated.
+ */
+ struct GNUNET_TIME_Absolute next_transmission;
+
+ /**
+ * HTTP response code.
+ */
+ long response_code;
+
+};
+
+
+/**
+ * Global return value.
+ */
+static int global_ret;
+
+/**
+ * #GNUNET_YES if we are in test mode and should exit when idle.
+ */
+static int test_mode;
+
+/**
+ * Base URL of the merchant backend.
+ */
+static char *base_url;
+
+/**
+ * Our configuration.
+ */
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+/**
+ * Database plugin.
+ */
+static struct TALER_MERCHANTDB_Plugin *db_plugin;
+
+/**
+ * Event handler for database change notifications.
+ */
+static struct GNUNET_DB_EventHandler *eh;
+
+/**
+ * Task for checking pending reports.
+ */
+static struct GNUNET_SCHEDULER_Task *report_task;
+
+/**
+ * Context for CURL operations.
+ */
+static struct GNUNET_CURL_Context *curl_ctx;
+
+/**
+ * Reschedule context for CURL.
+ */
+static struct GNUNET_CURL_RescheduleContext *curl_rc;
+
+/**
+ * Head of DLL of active report activities.
+ */
+static struct ReportActivity *ra_head;
+
+/**
+ * Tail of DLL of active report activities.
+ */
+static struct ReportActivity *ra_tail;
+
+
+/**
+ * Free a report activity structure.
+ *
+ * @param[in] ra report activity to free
+ */
+static void
+free_ra (struct ReportActivity *ra)
+{
+ if (NULL != ra->cwh)
+ {
+ GNUNET_wait_child_cancel (ra->cwh);
+ ra->cwh = NULL;
+ }
+ if (NULL != ra->proc)
+ {
+ GNUNET_OS_process_kill (ra->proc,
+ SIGKILL);
+ GNUNET_OS_process_wait (ra->proc);
+ GNUNET_OS_process_destroy (ra->proc);
+ ra->proc = NULL;
+ }
+ if (NULL != ra->eh)
+ {
+ curl_easy_cleanup (ra->eh);
+ ra->eh = NULL;
+ }
+ if (NULL != ra->job)
+ {
+ GNUNET_CURL_job_cancel (ra->job);
+ ra->job = NULL;
+ }
+ GNUNET_CONTAINER_DLL_remove (ra_head,
+ ra_tail,
+ ra);
+ GNUNET_free (ra->instance_id);
+ GNUNET_free (ra->report_program_section);
+ GNUNET_free (ra->report_description);
+ GNUNET_free (ra->target_address);
+ GNUNET_free (ra->mime_type);
+ GNUNET_free (ra->url);
+ GNUNET_free (ra);
+}
+
+
+/**
+ * Finish transmission of a report and update database.
+ *
+ * @param[in] ra report activity to finish
+ * @param ec error code (#TALER_EC_NONE on success)
+ * @param error_details human-readable error details (NULL on success)
+ */
+static void
+finish_transmission (struct ReportActivity *ra,
+ enum TALER_ErrorCode ec,
+ const char *error_details)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Timestamp next_ts;
+
+ next_ts = GNUNET_TIME_absolute_to_timestamp (ra->next_transmission);
+ qs = db_plugin->update_report_status (db_plugin->cls,
+ ra->instance_id,
+ ra->report_id,
+ next_ts,
+ ec,
+ error_details);
+ free_ra (ra);
+ if (qs < 0)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to update report status: %d\n",
+ qs);
+ global_ret = 1;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+}
+
+
+/**
+ * Callback invoked when the child process terminates.
+ *
+ * @param cls closure, a `struct ReportActivity *`
+ * @param type type of the process
+ * @param exit_code exit code of the process
+ */
+static void
+child_completed_cb (void *cls,
+ enum GNUNET_OS_ProcessStatusType type,
+ long unsigned int exit_code)
+{
+ struct ReportActivity *ra = cls;
+ enum TALER_ErrorCode ec;
+ char *error_details = NULL;
+
+ ra->cwh = NULL;
+ if ( (GNUNET_OS_PROCESS_EXITED != type) ||
+ (0 != exit_code) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Report transmission program failed with status %d/%lu\n",
+ (int) type,
+ exit_code);
+ ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ GNUNET_asprintf (&error_details,
+ "Report transmission program exited with status %d/%lu",
+ (int) type,
+ exit_code);
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Report transmitted successfully\n");
+ ec = TALER_EC_NONE;
+ }
+ finish_transmission (ra,
+ ec,
+ error_details);
+ GNUNET_free (error_details);
+}
+
+
+/**
+ * Transmit a report using the respective report program.
+ *
+ * @param[in,out] ra which report activity are we working on
+ * @param report_len length of @a report
+ * @param report binary report data to transmit
+ */
+static void
+transmit_report (struct ReportActivity *ra,
+ size_t report_len,
+ const void *report)
+{
+ const char *binary;
+ struct GNUNET_DISK_FileHandle *stdin_handle;
+
+ {
+ char *section;
+
+ GNUNET_asprintf (§ion,
+ "report-generator-%s",
+ ra->report_program_section);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (cfg,
+ section,
+ "BINARY",
+ (char **) &binary))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "BINARY");
+ finish_transmission (ra,
+ TALER_EC_MERCHANT_GENERIC_REPORT_GENERATOR_UNCONFIGURED,
+ section);
+ GNUNET_free (section);
+ return;
+ }
+ GNUNET_free (section);
+ }
+
+ {
+ struct GNUNET_DISK_PipeHandle *stdin_pipe;
+
+ stdin_pipe = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW);
+ if (NULL == stdin_pipe)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "pipe");
+ finish_transmission (ra,
+ TALER_EC_GENERIC_OS_RESOURCE_ALLOCATION_FAILURE,
+ "pipe");
+ return;
+ }
+
+ ra->proc = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR,
+ stdin_pipe,
+ NULL,
+ NULL,
+ binary,
+ binary,
+ "-d",
+ ra->report_description,
+ "-m",
+ ra->mime_type,
+ "-t",
+ ra->target_address,
+ NULL);
+ if (NULL == ra->proc)
+ {
+ GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
+ "exec",
+ binary);
+ GNUNET_DISK_pipe_close (stdin_pipe);
+ finish_transmission (ra,
+ TALER_EC_MERCHANT_REPORT_GENERATOR_FAILED,
+ "Could not execute report generator binary");
+ return;
+ }
+
+ /* Write report data to stdin of child process */
+ stdin_handle = GNUNET_DISK_pipe_detach_end (stdin_pipe,
+ GNUNET_DISK_PIPE_END_WRITE);
+ GNUNET_DISK_pipe_close (stdin_pipe);
+ }
+
+ {
+ size_t off = 0;
+
+ while (off < report_len)
+ {
+ ssize_t wrote;
+
+ wrote = GNUNET_DISK_file_write (stdin_handle,
+ report,
+ report_len);
+ if (wrote <= 0)
+ break;
+ off += (size_t) wrote;
+ }
+ GNUNET_DISK_file_close (stdin_handle);
+
+ if (off != report_len)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to write report data to child process stdin\n");
+ finish_transmission (ra,
+ TALER_EC_MERCHANT_REPORT_GENERATOR_FAILED,
+ "Failed to write to transmission program");
+ return;
+ }
+ }
+
+ /* Wait for child to complete */
+ ra->cwh = GNUNET_wait_child (ra->proc,
+ &child_completed_cb,
+ ra);
+}
+
+
+/**
+ * Callback invoked when CURL request completes.
+ *
+ * @param cls closure, a `struct ReportActivity *`
+ * @param response_code HTTP response code
+ * @param body http body of the response
+ * @param body_size number of bytes in @a body
+ */
+static void
+curl_completed_cb (void *cls,
+ long response_code,
+ const void *body,
+ size_t body_size)
+{
+ struct ReportActivity *ra = cls;
+
+ ra->eh = NULL;
+ ra->response_code = response_code;
+ if (MHD_HTTP_OK != response_code)
+ {
+ char *error_details;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to fetch report data: HTTP %ld\n",
+ response_code);
+ GNUNET_asprintf (&error_details,
+ "HTTP request failed with status %ld from `%s'",
+ response_code,
+ ra->url);
+ finish_transmission (ra,
+ TALER_EC_MERCHANT_REPORT_FETCH_FAILED,
+ error_details);
+ GNUNET_free (error_details);
+ return;
+ }
+ transmit_report (ra,
+ body_size,
+ body);
+}
+
+
+/**
+ * Function to fetch data from @a data_source at @a instance_id
+ * and to send it to the @a target_address
+ *
+ * @param[in,out] ra which report activity are we working on
+ * @param mime_type mime type to request from @a data_source
+ * @param data_source relative URL path to request data from
+ */
+static void
+fetch_and_transmit (struct ReportActivity *ra,
+ const char *mime_type,
+ const char *data_source)
+{
+ struct curl_slist *headers = NULL;
+ char *accept_header;
+
+ GNUNET_asprintf (&ra->url,
+ "%sinstances/%s%s",
+ base_url,
+ ra->instance_id,
+ data_source);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Fetching report from %s\n",
+ ra->url);
+ ra->eh = curl_easy_init ();
+ if (NULL == ra->eh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to initialize CURL handle\n");
+ finish_transmission (ra,
+ TALER_EC_GENERIC_CURL_ALLOCATION_FAILURE,
+ "curl_easy_init");
+ return;
+ }
+
+ GNUNET_asprintf (&accept_header,
+ "Accept: %s",
+ mime_type);
+ headers = curl_slist_append (headers,
+ accept_header);
+ GNUNET_free (accept_header);
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_URL,
+ ra->url));
+ GNUNET_assert (CURLE_OK ==
+ curl_easy_setopt (eh,
+ CURLOPT_HTTPHEADER,
+ headers));
+ // FIXME: need to set Authorization header!!!
+ ra->job = GNUNET_CURL_job_add_raw (curl_ctx,
+ ra->eh,
+ headers,
+ &curl_completed_cb,
+ ra);
+ ra->eh = NULL;
+ curl_slist_free_all (headers);
+}
+
+
+/**
+ * Callback invoked for each pending report.
+ *
+ * @param cls closure
+ * @param instance_id name of the instance
+ * @param report_id serial number of the report
+ * @param report_program_section configuration section of program
+ * @param report_description text describing the report
+ * @param mime_type mime type to request
+ * @param data_source relative URL to request report data from
+ * @param target_address where to send report data
+ * @param frequency report frequency
+ * @param frequency_shift time shift from frequency multiple
+ */
+static void
+process_pending_report (void *cls,
+ const char *instance_id,
+ uint64_t report_id,
+ const char *report_program_section,
+ const char *report_description,
+ const char *mime_type,
+ const char *data_source,
+ const char *target_address,
+ struct GNUNET_TIME_Relative frequency,
+ struct GNUNET_TIME_Relative frequency_shift,
+ struct GNUNET_TIME_Absolute next_transmission)
+{
+ struct GNUNET_TIME_Absolute *next = cls;
+ struct ReportActivity *ra;
+
+ *next = next_transmission;
+ if (GNUNET_TIME_absolute_is_future (next_transmission))
+ return;
+ *next = GNUNET_TIME_UNIT_ZERO_ABS; /* there might be more! */
+ next_transmission =
+ GNUNET_TIME_absolute_add (
+ GNUNET_TIME_absolute_round_down (GNUNET_TIME_absolute_get (),
+ frequency),
+ GNUNET_TIME_relative_add (frequency,
+ frequency_shift));
+ if (! GNUNET_TIME_absolute_is_future (next_transmission))
+ {
+ /* frequency near-zero!? */
+ GNUNET_break (0);
+ next_transmission = GNUNET_TIME_relative_to_absolute (
+ GNUNET_TIME_UNIT_MINUTES);
+ }
+ ra = GNUNET_new (struct ReportActivity);
+ ra->instance_id = GNUNET_strdup (instance_id);
+ ra->report_id = report_id;
+ ra->next_transmission = next_transmission;
+ ra->report_program_section = GNUNET_strdup (report_program_section);
+ ra->report_description = GNUNET_strdup (report_description);
+ ra->target_address = GNUNET_strdup (target_address);
+ ra->mime_type = GNUNET_strdup (mime_type);
+ GNUNET_CONTAINER_DLL_insert (ra_head,
+ ra_tail,
+ ra);
+ fetch_and_transmit (ra,
+ mime_type,
+ data_source);
+}
+
+
+/**
+ * Check for pending reports and process them.
+ *
+ * @param cls closure (unused)
+ */
+static void
+check_pending_reports (void *cls)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Absolute next;
+
+ (void) cls;
+ report_task = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Checking for pending reports...\n");
+ next = GNUNET_TIME_UNIT_FOREVER_ABS;
+ qs = db_plugin->lookup_reports_pending (db_plugin->cls,
+ &process_pending_report,
+ &next);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ global_ret = 1;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ GNUNET_assert (NULL == report_task);
+ if (test_mode &&
+ GNUNET_TIME_absolute_is_future (next) &&
+ (NULL == ra_head))
+ {
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ report_task = GNUNET_SCHEDULER_add_at (next,
+ &check_pending_reports,
+ NULL);
+}
+
+
+/**
+ * Callback invoked when a MERCHANT_REPORT_UPDATE event is received.
+ *
+ * @param cls closure (unused)
+ * @param extra additional event data (unused)
+ * @param extra_size size of @a extra
+ */
+static void
+report_update_cb (void *cls,
+ const void *extra,
+ size_t extra_size)
+{
+ (void) cls;
+ (void) extra;
+ (void) extra_size;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received MERCHANT_REPORT_UPDATE event\n");
+ /* Cancel any pending check and schedule immediate execution */
+ if (NULL != report_task)
+ GNUNET_SCHEDULER_cancel (report_task);
+ report_task = GNUNET_SCHEDULER_add_now (&check_pending_reports,
+ NULL);
+}
+
+
+/**
+ * Shutdown the service cleanly.
+ *
+ * @param cls closure (unused)
+ */
+static void
+do_shutdown (void *cls)
+{
+ struct ReportActivity *ra;
+
+ (void) cls;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Shutting down report generator service\n");
+
+ while (NULL != (ra = ra_head))
+ free_ra (ra);
+
+ if (NULL != report_task)
+ {
+ GNUNET_SCHEDULER_cancel (report_task);
+ report_task = NULL;
+ }
+ if (NULL != curl_rc)
+ {
+ GNUNET_CURL_gnunet_rc_destroy (curl_rc);
+ curl_rc = NULL;
+ }
+ if (NULL != curl_ctx)
+ {
+ GNUNET_CURL_fini (curl_ctx);
+ curl_ctx = NULL;
+ }
+ if (NULL != eh)
+ {
+ db_plugin->event_listen_cancel (eh);
+ eh = NULL;
+ }
+ if (NULL != db_plugin)
+ {
+ TALER_MERCHANTDB_plugin_unload (db_plugin);
+ db_plugin = NULL;
+ }
+ GNUNET_free (base_url);
+ base_url = NULL;
+}
+
+
+/**
+ * Main function for the report generator service.
+ *
+ * @param cls closure
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used
+ * @param config configuration
+ */
+static void
+run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *config)
+{
+ (void) cls;
+ (void) args;
+ (void) cfgfile;
+
+ cfg = config;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "merchant",
+ "BASE_URL",
+ &base_url))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "BASE_URL not configured in section [merchant]\n");
+ global_ret = 1;
+ return;
+ }
+
+ /* Ensure base_url ends with '/' */
+ if ('/' != base_url[strlen (base_url) - 1])
+ {
+ char *tmp;
+
+ GNUNET_asprintf (&tmp,
+ "%s/",
+ base_url);
+ GNUNET_free (base_url);
+ base_url = tmp;
+ }
+
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+ NULL);
+
+ curl_ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &curl_rc);
+ if (NULL == curl_ctx)
+ {
+ GNUNET_break (0);
+ global_ret = 1;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ curl_rc = GNUNET_CURL_gnunet_rc_create (curl_ctx);
+
+ db_plugin = TALER_MERCHANTDB_plugin_load (cfg);
+ if (NULL == db_plugin)
+ {
+ GNUNET_break (0);
+ global_ret = 1;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+ {
+ struct GNUNET_DB_EventHeaderP ev = {
+ .size = htons (sizeof (ev)),
+ .type = htons (TALER_DBEVENT_MERCHANT_REPORT_UPDATE)
+ };
+
+ eh = db_plugin->event_listen (db_plugin->cls,
+ &ev,
+ GNUNET_TIME_UNIT_FOREVER_REL,
+ &report_update_cb,
+ NULL);
+ if (NULL == eh)
+ {
+ GNUNET_break (0);
+ global_ret = 1;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ }
+ report_task = GNUNET_SCHEDULER_add_now (&check_pending_reports,
+ NULL);
+}
+
+
+/**
+ * The main function of the report generator service.
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc,
+ char *const *argv)
+{
+ struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_flag ('t',
+ "test",
+ "run in test mode and exit when idle",
+ &test_mode),
+ GNUNET_GETOPT_option_timetravel ('T',
+ "timetravel"),
+ GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
+ GNUNET_GETOPT_OPTION_END
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = GNUNET_PROGRAM_run (
+ TALER_MERCHANT_project_data (),
+ argc, argv,
+ "taler-merchant-report-generator",
+ "Fetch and transmit periodic merchant reports",
+ options,
+ &run,
+ NULL);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
+ return global_ret;
+}
+
+
+/* end of taler-merchant-report-generator.c */
diff --git a/src/backenddb/pg_insert_money_pot.c b/src/backenddb/pg_insert_money_pot.c
@@ -32,7 +32,7 @@ TMH_PG_insert_money_pot (
const char *instance_id,
const char *name,
const char *description,
- const char *pod_currency,
+ const char *pot_currency,
uint64_t *money_pot_id)
{
struct PostgresClosure *pg = cls;
@@ -52,7 +52,7 @@ TMH_PG_insert_money_pot (
};
if (GNUNET_OK !=
- TALER_amount_set_zero (pod_currency,
+ TALER_amount_set_zero (pot_currency,
&zero_c))
{
GNUNET_break (0);
@@ -69,6 +69,7 @@ TMH_PG_insert_money_pot (
" SELECT merchant_serial, $2, $3, $4"
" FROM merchant_instances"
" WHERE merchant_id=$1"
+ " ON CONFLICT DO NOTHING"
" RETURNING money_pot_serial;");
return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
"insert_money_pot",
diff --git a/src/backenddb/pg_select_money_pots.c b/src/backenddb/pg_select_money_pots.c
@@ -67,15 +67,12 @@ lookup_money_pots_cb (void *cls,
{
uint64_t money_pot_serial;
char *money_pot_name;
- char *money_pot_description;
struct TALER_Amount pot_total;
struct GNUNET_PQ_ResultSpec rs[] = {
GNUNET_PQ_result_spec_uint64 ("money_pot_serial",
&money_pot_serial),
GNUNET_PQ_result_spec_string ("money_pot_name",
&money_pot_name),
- GNUNET_PQ_result_spec_string ("money_pot_description",
- &money_pot_description),
TALER_PQ_result_spec_amount_with_currency ("pot_total",
&pot_total),
GNUNET_PQ_result_spec_end
@@ -93,7 +90,6 @@ lookup_money_pots_cb (void *cls,
plc->cb (plc->cb_cls,
money_pot_serial,
money_pot_name,
- money_pot_description,
&pot_total);
GNUNET_PQ_cleanup_result (rs);
}
@@ -130,7 +126,6 @@ TMH_PG_select_money_pots (void *cls,
"SELECT"
" money_pot_serial"
" ,money_pot_name"
- " ,money_pot_description"
" ,pot_total"
" FROM merchant_money_pots"
" JOIN merchant_instances"
@@ -144,7 +139,6 @@ TMH_PG_select_money_pots (void *cls,
"SELECT"
" money_pot_serial"
" ,money_pot_name"
- " ,money_pot_description"
" ,pot_total"
" FROM merchant_money_pots"
" JOIN merchant_instances"
diff --git a/src/backenddb/pg_update_money_pot.c b/src/backenddb/pg_update_money_pot.c
@@ -35,7 +35,8 @@ TMH_PG_update_money_pot (
const char *description,
const struct TALER_Amount *old_pot_total,
const struct TALER_Amount *new_pot_total,
- bool *conflict)
+ bool *conflict_total,
+ bool *conflict_name)
{
struct PostgresClosure *pg = cls;
struct GNUNET_PQ_QueryParam params[] = {
@@ -43,16 +44,22 @@ TMH_PG_update_money_pot (
GNUNET_PQ_query_param_uint64 (&money_pot_id),
GNUNET_PQ_query_param_string (name),
GNUNET_PQ_query_param_string (description),
- TALER_PQ_query_param_amount_with_currency (pg->conn,
- old_pot_total),
- TALER_PQ_query_param_amount_with_currency (pg->conn,
- new_pot_total),
+ (NULL == old_pot_total)
+ ? GNUNET_PQ_query_param_null ()
+ : TALER_PQ_query_param_amount_with_currency (pg->conn,
+ old_pot_total),
+ (NULL == new_pot_total)
+ ? GNUNET_PQ_query_param_null ()
+ : TALER_PQ_query_param_amount_with_currency (pg->conn,
+ new_pot_total),
GNUNET_PQ_query_param_end
};
bool not_found;
struct GNUNET_PQ_ResultSpec rs[] = {
- GNUNET_PQ_result_spec_bool ("conflict",
- conflict),
+ GNUNET_PQ_result_spec_bool ("conflict_total",
+ conflict_total),
+ GNUNET_PQ_result_spec_bool ("conflict_name",
+ conflict_name),
GNUNET_PQ_result_spec_bool ("not_found",
¬_found),
GNUNET_PQ_result_spec_end
@@ -60,11 +67,11 @@ TMH_PG_update_money_pot (
enum GNUNET_DB_QueryStatus qs;
check_connection (pg);
-
PREPARE (pg,
"update_money_pot",
"SELECT"
- " out_conflict AS conflict"
+ " out_conflict_name AS conflict_name"
+ " out_conflict_total AS conflict_total"
" out_not_found AS not_found"
" FROM merchant_do_update_money_pot"
"($1,$2,$3,$4,$5,$6);");
diff --git a/src/backenddb/pg_update_money_pot.h b/src/backenddb/pg_update_money_pot.h
@@ -33,9 +33,12 @@
* @param money_pot_id serial number of the pot to delete
* @param name set to name of the pot
* @param description set to description of the pot
- * @param old_pot_total amount expected currently in the pot
- * @param new_pot_total new amount in the pot
- * @param[out] conflict set to true if @a old_pot_total does not match
+ * @param old_pot_total amount expected currently in the pot,
+ * NULL to not check
+ * @param new_pot_total new amount in the pot,
+ * NULL to not update
+ * @param[out] conflict_total set to true if @a old_pot_total does not match
+ * @param[out] conflict_name set to true if @a name is used by another pot
* @return database result code
*/
enum GNUNET_DB_QueryStatus
@@ -47,7 +50,8 @@ TMH_PG_update_money_pot (
const char *description,
const struct TALER_Amount *old_pot_total,
const struct TALER_Amount *new_pot_total,
- bool *conflict);
+ bool *conflict_total,
+ bool *conflict_name);
#endif
diff --git a/src/backenddb/pg_update_money_pot.sql b/src/backenddb/pg_update_money_pot.sql
@@ -18,12 +18,13 @@
DROP FUNCTION IF EXISTS merchant_do_update_money_pot;
CREATE FUNCTION merchant_do_update_money_pot (
IN in_instance_id TEXT,
- IN in_money_pot_serial TEXT,
+ IN in_money_pot_serial INT8,
IN in_name TEXT,
IN in_description TEXT,
- IN in_old_total taler_amount_currency,
- IN in_new_total taler_amount_currency,
- OUT out_conflict BOOL,
+ IN in_old_total taler_amount_currency, -- can be NULL!
+ IN in_new_total taler_amount_currency, -- can be NULL!
+ OUT out_conflict_total BOOL,
+ OUT out_conflict_name BOOL,
OUT out_not_found BOOL)
LANGUAGE plpgsql
AS $$
@@ -36,32 +37,45 @@ SELECT merchant_serial
FROM merchant_instances
WHERE merchant_id=in_instance_id;
+IF NOT FOUND
+THEN
+ -- If instance does not exist, pot cannot exist
+ out_conflict_total = FALSE;
+ out_conflict_name = FALSE;
+ out_not_found = TRUE;
+ RETURN;
+END IF;
+
BEGIN
UPDATE merchant_money_pots SET
money_pot_name=in_name
,money_pot_description=in_description
- ,pot_total=in_new_total
+ ,pot_total=COALESCE(in_new_total, pot_total)
WHERE merchant_serial=my_merchant_id
AND money_pot_serial=in_money_pot_serial
- AND pot_total=in_old_total;
+ AND ( (in_old_total IS NULL) OR (pot_total=in_old_total) );
IF NOT FOUND
THEN
-- Check if pot_total was the problem
PERFORM FROM merchant_money_pots
WHERE merchant_serial=my_merchant_id
AND money_pot_serial=in_money_pot_serial;
- out_conflict = FOUND;
- out_not_found = FALSE;
+ out_conflict_total = FOUND;
+ out_not_found = NOT FOUND;
+ out_conflict_name = FALSE;
ELSE
- out_conflict = FALSE;
+ out_conflict_total = FALSE;
out_not_found = FALSE;
+ out_conflict_name = FALSE;
END IF;
RETURN;
EXCEPTION
-- money_pot_name already used
WHEN unique_violation
THEN
- out_conflict = TRUE;
+ out_conflict_name = TRUE;
+ out_conflict_total = FALSE;
+ out_not_found = FALSE;
RETURN;
END;
diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h
@@ -1337,15 +1337,13 @@ typedef void
*
* @param cls closure
* @param name set to name of the pot
- * @param description set to description of the pot
- * @param pod_total amount currently in the pot
+ * @param pot_total amount currently in the pot
*/
typedef void
(*TALER_MERCHANTDB_MoneyPotsCallback)(
void *cls,
uint64_t money_pot_id,
const char *name,
- const char *description,
const struct TALER_Amount *pot_total);
@@ -4920,7 +4918,7 @@ struct TALER_MERCHANTDB_Plugin
* @param instance_id instance to lookup token families for
* @param limit number of entries to return, negative for descending in execution time,
* positive for ascending in execution time
- * @param offset expected_transfer_serial number of the transfer we want to offset from
+ * @param offset number of the money pot we want to offset from
* @param cb function to call on all money pots found
* @param cb_cls closure for @a cb
* @return database result code
@@ -4973,9 +4971,12 @@ struct TALER_MERCHANTDB_Plugin
* @param money_pot_id serial number of the pot to delete
* @param name set to name of the pot
* @param description set to description of the pot
- * @param old_pot_total amount expected currently in the pot
- * @param new_pot_total new amount in the pot
- * @param[out] conflict set to true if @a old_pot_total does not match
+ * @param old_pot_total amount expected currently in the pot,
+ * NULL to not check
+ * @param new_pot_total new amount in the pot,
+ * NULL to not update
+ * @param[out] conflict_total set to true if @a old_pot_total does not match
+ * @param[out] conflict_name set to true if @a name is used by another pot
* @return database result code
*/
enum GNUNET_DB_QueryStatus
@@ -4987,7 +4988,8 @@ struct TALER_MERCHANTDB_Plugin
const char *description,
const struct TALER_Amount *old_pot_total,
const struct TALER_Amount *new_pot_total,
- bool *conflict);
+ bool *conflict_total,
+ bool *conflict_name);
/**
@@ -4997,9 +4999,10 @@ struct TALER_MERCHANTDB_Plugin
* @param instance_id instance to insert pot for
* @param name set to name of the pot
* @param description set to description of the pot
- * @param pod_currency the expected currency in the pot
+ * @param pot_currency the expected currency in the pot
* @param[out] money_pot_id serial number of the new pot
- * @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_money_pot)(
@@ -5007,7 +5010,7 @@ struct TALER_MERCHANTDB_Plugin
const char *instance_id,
const char *name,
const char *description,
- const char *pod_currency,
+ const char *pot_currency,
uint64_t *money_pot_id);
// Reports