commit f97dbdb7497e2d156f424321d283c209836f33bb
parent 8b8e4f45737a436702a504e1464a920098fdd0f8
Author: Christian Grothoff <grothoff@gnunet.org>
Date: Mon, 11 Aug 2025 12:56:45 +0200
fix merge conflict
Diffstat:
75 files changed, 10039 insertions(+), 375 deletions(-)
diff --git a/debian/taler-merchant.postinst b/debian/taler-merchant.postinst
@@ -11,6 +11,7 @@ if [ "$1" = "remove" ]; then
if [ -x "/usr/bin/deb-systemd-helper" ]; then
deb-systemd-helper mask 'taler-merchant-depositcheck.service' >/dev/null || true
deb-systemd-helper mask 'taler-merchant-exchangekeyupdate.service' >/dev/null || true
+ deb-systemd-helper mask 'taler-merchant-donaukeyupdate.service' >/dev/null || true
deb-systemd-helper mask 'taler-merchant-httpd.service' >/dev/null || true
deb-systemd-helper mask 'taler-merchant-kyccheck.service' >/dev/null || true
deb-systemd-helper mask 'taler-merchant-reconciliation.service' >/dev/null || true
@@ -24,6 +25,8 @@ if [ "$1" = "purge" ]; then
if [ -x "/usr/bin/deb-systemd-helper" ]; then
deb-systemd-helper purge 'taler-merchant-depositcheck.service' >/dev/null || true
deb-systemd-helper unmask 'taler-merchant-depositcheck.service' >/dev/null || true
+ deb-systemd-helper purge 'taler-merchant-donaukeyupdate.service' >/dev/null || true
+ deb-systemd-helper unmask 'taler-merchant-donaukeyupdate.service' >/dev/null || true
deb-systemd-helper purge 'taler-merchant-exchangekeyupdate.service' >/dev/null || true
deb-systemd-helper unmask 'taler-merchant-exchangekeyupdate.service' >/dev/null || true
deb-systemd-helper purge 'taler-merchant-httpd.service' >/dev/null || true
diff --git a/debian/taler-merchant.taler-merchant-donaukeyupdate.service b/debian/taler-merchant.taler-merchant-donaukeyupdate.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=GNU Taler merchant donau configuration data download service
+After=postgres.service
+ConditionPathExists=/usr/bin/taler-merchant-donaukeyupdate
+
+[Service]
+User=taler-merchant-httpd
+Type=simple
+Restart=always
+RestartMode=direct
+RestartSec=1s
+RestartPreventExitStatus=9
+ExecStart=/usr/bin/taler-merchant-donaukeyupdate -c /etc/taler-merchant/taler-merchant.conf -L INFO
+PrivateTmp=yes
+PrivateDevices=yes
+ProtectSystem=full
+RuntimeMaxSec=3600s
+Slice=taler-merchant.slice
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
@@ -25,6 +25,11 @@ bin_PROGRAMS = \
taler-merchant-webhook \
taler-merchant-wirewatch
+if HAVE_DONAU
+ bin_PROGRAMS += \
+ taler-merchant-donaukeyupdate
+endif
+
taler_merchant_depositcheck_SOURCES = \
taler-merchant-depositcheck.c
taler_merchant_depositcheck_LDADD = \
@@ -234,7 +239,16 @@ taler_merchant_httpd_LDADD = \
if HAVE_DONAU
taler_merchant_httpd_LDADD += \
- -ldonau
+ -ldonau \
+ -ldonaujson
+
+taler_merchant_httpd_SOURCES += \
+ taler-merchant-httpd_private-get-donau-instances.c \
+ taler-merchant-httpd_private-get-donau-instances.h \
+ taler-merchant-httpd_private-post-donau-instance.c \
+ taler-merchant-httpd_private-post-donau-instance.h \
+ taler-merchant-httpd_private-delete-donau-instance-ID.c \
+ taler-merchant-httpd_private-delete-donau-instance-ID.h
endif
taler_merchant_httpd_CFLAGS = \
@@ -318,3 +332,24 @@ taler_merchant_wirewatch_LDADD = \
$(XLIB)
taler_merchant_wirewatch_CFLAGS = \
$(AM_CFLAGS)
+
+
+if HAVE_DONAU
+ taler_merchant_donaukeyupdate_SOURCES = \
+ taler-merchant-donaukeyupdate.c
+ taler_merchant_donaukeyupdate_LDADD = \
+ $(top_builddir)/src/util/libtalermerchantutil.la \
+ $(top_builddir)/src/backenddb/libtalermerchantdb.la \
+ -ltalerexchange \
+ -ltalerjson \
+ -ltalerutil \
+ -ltalerpq \
+ -lgnunetjson \
+ -lgnunetcurl \
+ -lgnunetutil \
+ -lcurl \
+ -ldonau \
+ $(XLIB)
+ taler_merchant_donaukeyupdate_CFLAGS = \
+ $(AM_CFLAGS)
+endif
diff --git a/src/backend/taler-merchant-depositcheck.c b/src/backend/taler-merchant-depositcheck.c
@@ -595,7 +595,7 @@ deposit_get_cb (
* @param cls NULL
* @param deposit_serial identifies the deposit operation
* @param wire_deadline when is the wire due
- * @param retry_backoff current value for the retry backoff
+ * @param retry_time current value for the retry backoff
* @param h_contract_terms hash of the contract terms
* @param merchant_priv private key of the merchant
* @param instance_id row ID of the instance
diff --git a/src/backend/taler-merchant-donaukeyupdate.c b/src/backend/taler-merchant-donaukeyupdate.c
@@ -0,0 +1,1044 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024, 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 taler-merchant-donaukeyupdate.c
+ * @brief Process that ensures our /keys data for all Donau instances is current
+ * @author Bohdan Potuzhnyi
+ */
+#include "platform.h"
+#include "microhttpd.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <pthread.h>
+#include <taler/taler_dbevents.h>
+#include "donau/donau_service.h"
+#include "taler_merchant_util.h"
+#include "taler_merchantdb_lib.h"
+#include "taler_merchantdb_plugin.h"
+#include "taler_merchant_bank_lib.h"
+
+/**
+ * Maximum frequency for the Donau interaction.
+ */
+#define DONAU_MAXFREQ GNUNET_TIME_relative_multiply ( \
+ GNUNET_TIME_UNIT_MINUTES, \
+ 5)
+
+/**
+ * How many inquiries do we process concurrently at most.
+ */
+#define OPEN_INQUIRY_LIMIT 1024
+
+/**
+ * How often do we retry after DB serialization errors (at most)?
+ */
+#define MAX_RETRIES 3
+
+/**
+ * Information about a Donau instance.
+ */
+struct Donau
+{
+ /**
+ * Pointer to the next Donau instance in the doubly linked list.
+ */
+ struct Donau *next;
+
+ /**
+ * Pointer to the previous Donau instance in the doubly linked list.
+ */
+ struct Donau *prev;
+
+ /**
+ * Base URL of the Donau instance being tracked.
+ * This URL is used to query the Donau service for keys and other resources.
+ */
+ char *donau_url;
+
+ /**
+ * Expected currency of the donau.
+ */
+ char *currency;
+
+ /**
+ * Pointer to the keys obtained from the Donau instance.
+ * This structure holds the cryptographic keys for the Donau instance.
+ */
+ struct DONAU_Keys *keys;
+
+ /**
+ * A handle for an ongoing /keys request to the Donau instance.
+ * This is NULL when there is no active request.
+ */
+ struct DONAU_GetKeysHandle *conn;
+
+ /**
+ * Scheduler task for retrying a failed /keys request.
+ * This task will trigger the next attempt to download the Donau keys if the previous request failed or needs to be retried.
+ */
+ struct GNUNET_SCHEDULER_Task *retry_task;
+
+ /**
+ * The earliest time at which the Donau instance can attempt another /keys request.
+ * This is used to manage the timing between requests and ensure compliance with rate-limiting rules.
+ */
+ struct GNUNET_TIME_Absolute first_retry;
+
+ /**
+ * The delay between the next retry for fetching /keys.
+ * Used to implement exponential backoff strategies for retries in case of failures.
+ */
+ struct GNUNET_TIME_Relative retry_delay;
+
+ /**
+ * A flag indicating whether this Donau instance is currently rate-limited.
+ * If true, the instance is temporarily paused from making further requests due to reaching a limit.
+ */
+ bool limited;
+
+ /**
+ * Are we force-retrying a /keys download because some keys
+ * were missing?
+ */
+ bool force_retry;
+};
+
+
+/**
+ * Head of known Donau instances.
+ */
+static struct Donau *d_head;
+
+/**
+ * Tail of known Donau instances.
+ */
+static struct Donau *d_tail;
+
+/**
+ * Context for the charity force download.
+ */
+struct ForceCharityCtx
+{
+ /**
+ * Pointer to the next ForceCharityCtx in the doubly linked list.
+ */
+ struct ForceCharityCtx *next;
+
+ /**
+ * Pointer to the previous ForceCharityCtx in the doubly linked list.
+ */
+ struct ForceCharityCtx *prev;
+
+ /**
+ * Serial of the Donau instance in our DB for which we running the force update.
+ */
+ uint64_t di_serial;
+
+ /**
+ * Base URL of the Donau instance for which we are running the force update.
+ */
+ char *donau_url;
+
+ /**
+ * ID of the charity for which we are running the force update.
+ */
+ uint64_t charity_id;
+
+ /**
+ * Handle to the charity update request.
+ */
+ struct DONAU_CharityGetHandle *h;
+};
+
+/**
+ * Head of the list of charity force updates.
+ */
+static struct ForceCharityCtx *fcc_head;
+
+/**
+ * Tail of the list of charity force updates.
+ */
+static struct ForceCharityCtx *fcc_tail;
+
+/**
+ * The merchant's configuration.
+ */
+static const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+/**
+ * Our database plugin.
+ */
+static struct TALER_MERCHANTDB_Plugin *db_plugin;
+
+/**
+ * Our event handler listening for /keys forced downloads.
+ */
+static struct GNUNET_DB_EventHandler *eh;
+
+/**
+ * Our event handler listening for /charity_id forced downloads.
+ */
+static struct GNUNET_DB_EventHandler *eh_charity;
+
+/**
+ * Handle to the context for interacting with the Donau services.
+ */
+static struct GNUNET_CURL_Context *ctx;
+
+/**
+ * Scheduler context for running the @e ctx.
+ */
+static struct GNUNET_CURL_RescheduleContext *rc;
+
+/**
+ * How many active inquiries do we have right now.
+ */
+static unsigned int active_inquiries;
+
+/**
+ * Value to return from main(). 0 on success, non-zero on errors.
+ */
+static int global_ret;
+
+/**
+ * #GNUNET_YES if we are in test mode and should exit when idle.
+ */
+static int test_mode;
+
+/**
+ * True if the last DB query was limited by the
+ * #OPEN_INQUIRY_LIMIT and we thus should check again
+ * as soon as we are substantially below that limit,
+ * and not only when we get a DB notification.
+ */
+static bool at_limit;
+
+
+/**
+ * Function that initiates a /keys download for a Donau instance.
+ *
+ * @param cls closure with a `struct Donau *`
+ */
+static void
+download_keys (void *cls);
+
+
+/**
+ * An inquiry finished, check if we need to start more.
+ */
+static void
+end_inquiry (void)
+{
+ GNUNET_assert (active_inquiries > 0);
+ active_inquiries--;
+ if ( (active_inquiries < OPEN_INQUIRY_LIMIT / 2) &&
+ (at_limit) )
+ {
+ at_limit = false;
+ for (struct Donau *d = d_head;
+ NULL != d;
+ d = d->next)
+ {
+ if (! d->limited)
+ continue;
+ d->limited = false;
+ /* done synchronously so that the active_inquiries
+ is updated immediately */
+ download_keys (d);
+ if (at_limit)
+ break;
+ }
+ }
+ if ( (! at_limit) &&
+ (0 == active_inquiries) &&
+ (test_mode) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No more open inquiries and in test mode. Exiting.\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+}
+
+
+/**
+ * Update Donau keys in the database.
+ *
+ * @param keys Donau keys to persist
+ * @param first_retry earliest we may retry fetching the keys
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+insert_donau_keys_data (const struct DONAU_Keys *keys,
+ struct GNUNET_TIME_Absolute first_retry)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Inserting Donau keys into the database %s\n",
+ keys->donau_url);
+ return db_plugin->upsert_donau_keys (db_plugin->cls,
+ keys,
+ first_retry);
+}
+
+
+/**
+ * Store Donau keys in the database and handle retries.
+ *
+ * @param keys the keys to store
+ * @param first_retry earliest time we may retry fetching the keys
+ * @return true on success
+ */
+static bool
+store_donau_keys (struct DONAU_Keys *keys,
+ struct GNUNET_TIME_Absolute first_retry)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ db_plugin->preflight (db_plugin->cls);
+ for (unsigned int r = 0; r < MAX_RETRIES; r++)
+ {
+ if (GNUNET_OK !=
+ db_plugin->start (db_plugin->cls,
+ "update donau key data"))
+ {
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_break (0);
+ return false;
+ }
+
+ qs = insert_donau_keys_data (keys,
+ first_retry);
+ if (0 > qs)
+ {
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error while inserting Donau keys into the database: status %d",
+ qs);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ continue;
+ GNUNET_break (0);
+ return false;
+ }
+
+ qs = db_plugin->commit (db_plugin->cls);
+ if (0 > qs)
+ {
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to commit Donau keys to the database: status %d",
+ qs);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ continue;
+ GNUNET_break (0);
+ return false;
+ }
+ break;
+ }
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ return false;
+ }
+ return true;
+}
+
+
+/**
+ * Store Donau charity in the database and handle retries.
+ *
+ * @param charity_id the charity ID to store
+ * @param donau_url the base URL of the Donau instance
+ * @param charity the charity structure to store
+ */
+static bool
+store_donau_charity (uint64_t charity_id,
+ const char *donau_url,
+ const struct DONAU_Charity *charity)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Inserting/updating charity %llu for Donau `%s'\n",
+ (unsigned long long) charity_id,
+ donau_url);
+
+ db_plugin->preflight (db_plugin->cls);
+
+ for (unsigned int r = 0; r < MAX_RETRIES; r++)
+ {
+ if (GNUNET_OK !=
+ db_plugin->start (db_plugin->cls,
+ "update donau charity data"))
+ {
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_break (0);
+ return false;
+ }
+
+ qs = db_plugin->update_donau_instance (db_plugin->cls,
+ donau_url,
+ charity,
+ charity_id);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error while updating charity into the database: status %d",
+ qs);
+ continue;
+ }
+ if (0 >= qs)
+ {
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Error while updating charity into the database: status %d",
+ qs);
+ return false;
+ }
+
+ qs = db_plugin->commit (db_plugin->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to commit charity data to the database: status %d",
+ qs);
+ continue;
+ }
+ if (0 > qs)
+ {
+ db_plugin->rollback (db_plugin->cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to commit charity data to the database: status %d",
+ qs);
+ return false;
+ }
+ break;
+ }
+ if (0 >= qs)
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Retries exhausted while inserting charity %llu for Donau `%s': last status %d",
+ (unsigned long long) charity_id,
+ donau_url,
+ qs);
+ return false;
+ }
+ return true;
+}
+
+
+/**
+ * Callback after Donau keys are fetched.
+ *
+ * @param cls closure with a `struct Donau *`
+ * @param kr response data
+ * @param keys the keys of the Donau instance
+ */
+static void
+donau_cert_cb (
+ void *cls,
+ const struct DONAU_KeysResponse *kr,
+ struct DONAU_Keys *keys)
+{
+ struct Donau *d = cls;
+ struct GNUNET_TIME_Absolute n;
+ struct GNUNET_TIME_Absolute first_retry;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting donau cert with object \n");
+
+ d->conn = NULL;
+ switch (kr->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got new keys for %s, updating database\n",
+ d->donau_url);
+ first_retry = GNUNET_TIME_relative_to_absolute (DONAU_MAXFREQ);
+ if (! store_donau_keys (keys,
+ first_retry))
+ {
+ GNUNET_break (0);
+ DONAU_keys_decref (keys);
+ break;
+ }
+
+ d->keys = keys;
+ /* Reset back-off */
+ d->retry_delay = DONAU_MAXFREQ;
+ /* limit retry */
+ d->first_retry = first_retry;
+
+ /* FIXME: Might be good to reference some key_data_expiration and not first sign_key*/
+ n = GNUNET_TIME_absolute_max (d->first_retry,
+ keys->sign_keys[0].expire_sign.abs_time);
+ if (NULL != d->retry_task)
+ GNUNET_SCHEDULER_cancel (d->retry_task);
+ d->retry_task = GNUNET_SCHEDULER_add_at (n,
+ &download_keys,
+ d);
+ end_inquiry ();
+ return;
+ default:
+ GNUNET_break (NULL == keys);
+ break;
+ }
+
+ d->retry_delay
+ = GNUNET_TIME_STD_BACKOFF (d->retry_delay);
+ n = GNUNET_TIME_absolute_max (
+ d->first_retry,
+ GNUNET_TIME_relative_to_absolute (d->retry_delay));
+
+ if (NULL != d->retry_task)
+ GNUNET_SCHEDULER_cancel (d->retry_task);
+ d->retry_task
+ = GNUNET_SCHEDULER_add_at (n,
+ &download_keys,
+ d);
+ end_inquiry ();
+}
+
+
+/**
+ * Initiate the download of Donau keys.
+ *
+ * @param cls closure with a `struct Donau *`
+ */
+static void
+download_keys (void *cls)
+{
+ struct Donau *d = cls;
+
+ d->retry_task = NULL;
+ GNUNET_break (OPEN_INQUIRY_LIMIT >= active_inquiries);
+ if (OPEN_INQUIRY_LIMIT <= active_inquiries)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Cannot start more donaukeys inquiries, already at limit\n");
+ d->limited = true;
+ at_limit = true;
+ return;
+ }
+ d->retry_delay
+ = GNUNET_TIME_STD_BACKOFF (d->retry_delay);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Downloading keys from %s (%s)\n",
+ d->donau_url,
+ d->force_retry ? "forced" : "regular");
+ d->conn = DONAU_get_keys (ctx,
+ d->donau_url,
+ &donau_cert_cb,
+ d);
+ d->force_retry = false;
+ if (NULL != d->conn)
+ {
+ active_inquiries++;
+ }
+ else
+ {
+ struct GNUNET_TIME_Relative n;
+
+ n = GNUNET_TIME_relative_max (d->retry_delay,
+ DONAU_MAXFREQ);
+
+ d->retry_task
+ = GNUNET_SCHEDULER_add_delayed (n,
+ &download_keys,
+ d);
+ }
+}
+
+
+/**
+ * Callback for DONAU_charity_get() that stores the charity
+ * information in the DB and finishes the inquiry.
+ *
+ * @param cls closure with `struct ForceCharityCtx *`
+ * @param gcr response from DONAU
+ */
+static void
+donau_charity_cb (void *cls,
+ const struct DONAU_GetCharityResponse *gcr)
+{
+ struct ForceCharityCtx *fcc = cls;
+ fcc->h = NULL;
+
+ switch (gcr->hr.http_status)
+ {
+ case MHD_HTTP_OK:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got charity_id `%llu' details for donau `%s', updating DB\n",
+ (unsigned long long) fcc->charity_id,
+ fcc->donau_url);
+
+ if (! store_donau_charity (fcc->charity_id,
+ fcc->donau_url,
+ &gcr->details.ok.charity))
+ {
+ GNUNET_break (0);
+ }
+ break;
+
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "DONAU charity_get for `%s' failed with HTTP %u / ec %u\n",
+ fcc->donau_url,
+ gcr->hr.http_status,
+ gcr->hr.ec);
+ break;
+ }
+
+ end_inquiry ();
+}
+
+
+/**
+ * Download the charity_id for a Donau instance.
+ *
+ * @param cls closure with a `struct Donau *`
+ */
+static void
+download_charity_id (void *cls)
+{
+ struct ForceCharityCtx *fcc = cls;
+
+ /* nothing to do if a request is already outstanding */
+ if (NULL != fcc->h)
+ return;
+
+ /* respect global inquiry limit */
+ GNUNET_break (OPEN_INQUIRY_LIMIT >= active_inquiries);
+ if (OPEN_INQUIRY_LIMIT <= active_inquiries)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Cannot start more charity inquiries, already at limit\n");
+ return;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Downloading charity `%llu' from `%s'\n",
+ (unsigned long long) fcc->charity_id,
+ fcc->donau_url);
+
+ fcc->h = DONAU_charity_get (ctx,
+ fcc->donau_url,
+ fcc->charity_id,
+ NULL, /* bearer token -- not needed */
+ &donau_charity_cb,
+ fcc);
+
+ if (NULL != fcc->h)
+ {
+ active_inquiries++;
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to initiate DONAU_charity_get() for `%s'\n",
+ fcc->donau_url);
+ /* we do NOT retry here – simply finish the (failed) inquiry */
+ end_inquiry ();
+ }
+}
+
+
+/**
+ * Lookup donau by @a donau_url. Create one
+ * if it does not exist.
+ *
+ * @param donau_url base URL to match against
+ * @return NULL if not found
+ */
+static struct Donau *
+lookup_donau (const char *donau_url)
+{
+ for (struct Donau *d = d_head;
+ NULL != d;
+ d = d->next)
+ if (0 == strcmp (d->donau_url,
+ donau_url))
+ return d;
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Got notification about unknown Donau `%s'\n",
+ donau_url);
+ return NULL;
+}
+
+
+/**
+ * Lookup a ForceCharityCtx by donau-instance serial.
+ *
+ * @param di_serial serial to search for
+ * @return matching context or NULL
+ */
+static struct ForceCharityCtx *
+lookup_donau_charity (uint64_t di_serial)
+{
+ for (struct ForceCharityCtx *fcc = fcc_head;
+ NULL != fcc;
+ fcc = fcc->next)
+ if (fcc->di_serial == di_serial)
+ return fcc;
+ return NULL;
+}
+
+
+/**
+ * Force immediate (re)loading of /charity_id for an donau.
+ *
+ * @param cls NULL
+ * @param extra base URL of the donau that changed
+ * @param extra_len number of bytes in @a extra
+ */
+static void
+force_donau_charity_id (void *cls,
+ const void *extra,
+ size_t extra_len)
+{
+ if ( (sizeof(uint64_t) != extra_len)
+ || (NULL == extra) )
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Incorrect extra for the force_donau_charity_id");
+ return;
+ }
+ uint64_t di_serial;
+ char *donau_url = NULL;
+ uint64_t charity_id = -1;
+
+ GNUNET_memcpy (&di_serial,
+ extra,
+ sizeof(uint64_t));
+ di_serial = GNUNET_ntohll (di_serial);
+
+ enum GNUNET_DB_QueryStatus qs =
+ db_plugin->select_donau_instance_by_serial (db_plugin->cls,
+ di_serial,
+ &donau_url,
+ &charity_id);
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ {
+ GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "force_donau_charity_id: instance serial %llu not found (status %d)\n",
+ (unsigned long long) di_serial,
+ qs);
+ return;
+ }
+
+ struct ForceCharityCtx *fcc =
+ lookup_donau_charity (di_serial);
+ if (NULL == fcc)
+ {
+ fcc = GNUNET_new (struct ForceCharityCtx);
+ fcc->di_serial = di_serial;
+ fcc->donau_url = donau_url; /* take ownership */
+ fcc->charity_id = charity_id;
+ GNUNET_CONTAINER_DLL_insert (fcc_head, fcc_tail, fcc);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Created new ForceCharityCtx for donau `%s' "
+ "(serial %llu, charity %llu)\n",
+ donau_url,
+ (unsigned long long) di_serial,
+ (unsigned long long) charity_id);
+ }
+ else
+ {
+ GNUNET_free (donau_url);
+ if (NULL != fcc->h)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Already downloading charity_id for donau `%s'\n",
+ fcc->donau_url);
+ return;
+ }
+ }
+
+ download_charity_id (fcc);
+}
+
+
+/**
+ * Force immediate (re)loading of /keys for an donau.
+ *
+ * @param cls NULL
+ * @param extra base URL of the donau that changed
+ * @param extra_len number of bytes in @a extra
+ */
+static void
+force_donau_keys (void *cls,
+ const void *extra,
+ size_t extra_len)
+{
+ const char *url = extra;
+ struct Donau *d;
+
+ if ( (NULL == extra) ||
+ (0 == extra_len) )
+ {
+ GNUNET_break (0);
+ return;
+ }
+ if ('\0' != url[extra_len - 1])
+ {
+ GNUNET_break (0);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received keys update notification: reload `%s'\n",
+ url);
+
+ d = lookup_donau (url);
+ if (NULL == d)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Donau instance `%s' not found. Creating new instance.\n", url);
+
+ d = GNUNET_new (struct Donau);
+ d->donau_url = GNUNET_strdup (url);
+ d->retry_delay = DONAU_MAXFREQ;
+ d->first_retry = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_ZERO);
+
+ GNUNET_CONTAINER_DLL_insert (d_head,
+ d_tail,
+ d);
+ download_keys (d);
+ }
+
+ if (NULL != d->conn)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Already downloading %skeys\n",
+ url);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Will download %skeys in %s\n",
+ url,
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_remaining (
+ d->first_retry),
+ true));
+ if (NULL != d->retry_task)
+ GNUNET_SCHEDULER_cancel (d->retry_task);
+ d->force_retry = true;
+ d->retry_task
+ = GNUNET_SCHEDULER_add_at (d->first_retry,
+ &download_keys,
+ d);
+}
+
+
+/**
+ * We're being aborted with CTRL-C (or SIGTERM). Shut down.
+ *
+ * @param cls closure (NULL)
+ */
+static void
+shutdown_task (void *cls)
+{
+ (void) cls;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Running shutdown\n");
+ while (NULL != d_head)
+ {
+ struct Donau *d = d_head;
+
+ GNUNET_free (d->donau_url);
+ GNUNET_free (d->currency);
+ if (NULL != d->conn)
+ {
+ DONAU_get_keys_cancel (d->conn);
+ d->conn = NULL;
+ }
+ if (NULL != d->keys)
+ {
+ DONAU_keys_decref (d->keys);
+ d->keys = NULL;
+ }
+ if (NULL != d->retry_task)
+ {
+ GNUNET_SCHEDULER_cancel (d->retry_task);
+ d->retry_task = NULL;
+ }
+ GNUNET_CONTAINER_DLL_remove (d_head,
+ d_tail,
+ d);
+ GNUNET_free (d);
+ }
+ if (NULL != eh)
+ {
+ db_plugin->event_listen_cancel (eh);
+ eh = NULL;
+ }
+ if (NULL != eh_charity)
+ {
+ db_plugin->event_listen_cancel (eh_charity);
+ eh_charity = NULL;
+ }
+ TALER_MERCHANTDB_plugin_unload (db_plugin);
+ db_plugin = NULL;
+ cfg = NULL;
+ if (NULL != ctx)
+ {
+ GNUNET_CURL_fini (ctx);
+ ctx = NULL;
+ }
+ if (NULL != rc)
+ {
+ GNUNET_CURL_gnunet_rc_destroy (rc);
+ rc = NULL;
+ }
+}
+
+
+/**
+ * First task.
+ *
+ * @param cls closure, NULL
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be NULL!)
+ * @param c configuration
+ */
+static void
+run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *c)
+{
+ (void) args;
+ (void) cfgfile;
+
+ cfg = c;
+ GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
+ NULL);
+ ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &rc);
+ rc = GNUNET_CURL_gnunet_rc_create (ctx);
+ if (NULL == ctx)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ if (NULL == ctx)
+ {
+ GNUNET_break (0);
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ if (NULL ==
+ (db_plugin = TALER_MERCHANTDB_plugin_load (cfg)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to initialize DB subsystem\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_NOTCONFIGURED;
+ return;
+ }
+ if (GNUNET_OK !=
+ db_plugin->connect (db_plugin->cls))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to connect to database\n");
+ GNUNET_SCHEDULER_shutdown ();
+ global_ret = EXIT_FAILURE;
+ return;
+ }
+ {
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = ntohs (sizeof(es)),
+ .type = ntohs (TALER_DBEVENT_MERCHANT_DONAU_KEYS)
+ };
+
+ eh = db_plugin->event_listen (db_plugin->cls,
+ &es,
+ GNUNET_TIME_UNIT_FOREVER_REL,
+ &force_donau_keys,
+ NULL);
+ }
+ {
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = ntohs (sizeof(es)),
+ .type = ntohs (TALER_DBEVENT_MERCHANT_DONAU_CHARITY_ID)
+ };
+
+ eh_charity = db_plugin->event_listen (
+ db_plugin->cls,
+ &es,
+ GNUNET_TIME_UNIT_FOREVER_REL,
+ &force_donau_charity_id,
+ NULL);
+ }
+
+ if ( (0 == active_inquiries) &&
+ (test_mode) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No donaukeys inquiries to start, exiting.\n");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+}
+
+
+/**
+ * The main function of taler-merchant-donaukeyupdate
+ *
+ * @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_timetravel ('T',
+ "timetravel"),
+ GNUNET_GETOPT_option_flag ('t',
+ "test",
+ "run in test mode and exit when idle",
+ &test_mode),
+ 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-donaukeyupdate",
+ gettext_noop (
+ "background process that ensures our key and configuration data on Donau is up-to-date"),
+ options,
+ &run, NULL);
+ if (GNUNET_SYSERR == ret)
+ return EXIT_INVALIDARGUMENT;
+ if (GNUNET_NO == ret)
+ return EXIT_SUCCESS;
+ return global_ret;
+}
+
+
+/* end of taler-merchant-donaukeyupdate.c */
+\ No newline at end of file
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c
@@ -103,6 +103,11 @@
#include "taler-merchant-httpd_spa.h"
#include "taler-merchant-httpd_statics.h"
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+#include "taler-merchant-httpd_private-get-donau-instances.h"
+#include "taler-merchant-httpd_private-post-donau-instance.h"
+#include "taler-merchant-httpd_private-delete-donau-instance-ID.h"
+#endif
/**
* Backlog for listen operation on unix-domain sockets.
@@ -484,7 +489,7 @@ TMH_check_auth (const char *password,
*
* @param userpass base64 encoded "$USERNAME:$PASSWORD" value
* from HTTP Basic "Authentication" header
- * @param instances the access controlled instance
+ * @param instance the access controlled instance
*/
static enum GNUNET_GenericReturnValue
check_auth_instance (const char *userpass,
@@ -1772,6 +1777,27 @@ url_handler (void *cls,
.have_id_segment = true,
.handler = &TMH_private_patch_token_family_SLUG,
},
+ #ifdef HAVE_DONAU_DONAU_SERVICE_H
+ /* GET /donau */
+ {
+ .url_prefix = "/donau",
+ .method = MHD_HTTP_METHOD_GET,
+ .handler = &TMH_private_get_donau_instances
+ },
+ /* POST /donau */
+ {
+ .url_prefix = "/donau",
+ .method = MHD_HTTP_METHOD_POST,
+ .handler = &TMH_private_post_donau_instance
+ },
+ /* DELETE /donau/$charity-id */
+ {
+ .url_prefix = "/donau/",
+ .method = MHD_HTTP_METHOD_DELETE,
+ .have_id_segment = true,
+ .handler = &TMH_private_delete_donau_instance_ID
+ },
+ #endif
/* GET /statistics-counter/$SLUG: */
{
.url_prefix = "/statistics-counter/",
diff --git a/src/backend/taler-merchant-httpd.h b/src/backend/taler-merchant-httpd.h
@@ -451,7 +451,7 @@ enum TMH_AuthScope
/**
* Order full
- * Includes #TMH_ORDER_POS and #TMH_ORDER_MGMT
+ * Includes #TMH_AS_ORDER_POS and #TMH_AS_ORDER_MGMT
*/
TMH_AS_ORDER_FULL = 6,
diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c
@@ -47,6 +47,11 @@
#include "taler_merchant_util.h"
#include "taler_merchantdb_plugin.h"
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+#include <donau/donau_service.h>
+#include <donau/donau_util.h>
+#include <donau/donau_json_lib.h>
+#endif
/**
* How often do we retry the (complex!) database transaction?
@@ -120,7 +125,15 @@ enum PayPhase
*/
PP_PAY_TRANSACTION,
- // FIXME: new (optional) phase for DONAU interaction here.
+ /**
+ * Communicate with DONAU to generate a donation receipt from the donor BUDIs.
+ */
+ PP_REQUEST_DONATION_RECEIPT,
+
+ /**
+ * Process the donation receipt response from DONAU (save the donau_sigs to the db).
+ */
+ PP_FINAL_OUTPUT_TOKEN_PROCESSING,
/**
* Notify other processes about successful payment.
@@ -367,6 +380,29 @@ struct ExchangeGroup
/**
+ * Information about donau, that can be fetched even
+ * if the merhchant doesn't support donau
+ */
+struct DonauData
+{
+ /**
+ * The user-selected Donau URL.
+ */
+ char *donau_url;
+
+ /**
+ * The donation year, as parsed from "year".
+ */
+ uint64_t donation_year;
+
+ /**
+ * The original BUDI key-pairs array from the donor
+ * to be used for the receipt creation.
+ */
+ const json_t *budikeypairs;
+};
+
+/**
* Information we keep for an individual call to the /pay handler.
*/
struct PayContext
@@ -503,6 +539,58 @@ struct PayContext
*/
struct GNUNET_HashCode h_wallet_data;
+ /**
+ * Donau related information
+ */
+ struct DonauData donau;
+
+ /**
+ * Serial from the DB of the donau instance that we are using
+ */
+ uint64_t donau_instance_serial;
+
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+ /**
+ * Number of the blinded key pairs @e bkps
+ */
+ unsigned int num_bkps;
+
+ /**
+ * Blinded key pairs received from the wallet
+ */
+ struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkps;
+
+ /**
+ * The id of the charity as saved on the donau.
+ */
+ uint64_t charity_id;
+
+ /**
+ * Private key of the charity(related to the private key of the merchant).
+ */
+ struct DONAU_CharityPrivateKeyP charity_priv;
+
+ /**
+ * Maximum amount of donations that the charity can receive per year.
+ */
+ struct TALER_Amount charity_max_per_year;
+
+ /**
+ * Amount of donations that the charity has received so far this year.
+ */
+ struct TALER_Amount charity_receipts_to_date;
+
+ /**
+ * Donau keys, that we are using to get the information about the bkps.
+ */
+ struct DONAU_Keys *donau_keys;
+
+ /**
+ * Amount from BKPS
+ */
+ struct TALER_Amount donation_amount;
+#endif
+
} parse_wallet_data;
/**
@@ -659,6 +747,33 @@ struct PayContext
} batch_deposits;
+ #ifdef HAVE_DONAU_DONAU_SERVICE_H
+ /**
+ * Struct for #phase_request_donation_receipt()
+ */
+ struct
+ {
+ /**
+ * Handler of the donau request
+ */
+ struct DONAU_BatchIssueReceiptHandle *birh;
+
+ /**
+ * Receipts for the donations from the Donau.
+ */
+ struct DONAU_BlindedDonationUnitSignature *sigs;
+
+ /**
+ * Number of receipts in @e sigs for the donations from the Donau.
+ */
+ unsigned int num_sigs;
+
+ /**
+ * The donation receipt in json format.
+ */
+ json_t *donau_sigs_json;
+ } donau_receipt;
+ #endif
};
@@ -1660,6 +1775,7 @@ build_token_sigs (struct PayContext *pc)
GNUNET_assert (NULL != token_sigs);
for (unsigned int i = 0; i < pc->validate_tokens.output_tokens_len; i++)
{
+ /* FIXME: Do we want to output hash? */
GNUNET_assert (0 ==
json_array_append_new (
token_sigs,
@@ -1701,7 +1817,7 @@ phase_success_response (struct PayContext *pc)
token_sigs = (0 >= pc->validate_tokens.output_tokens_len)
? NULL
: build_token_sigs (pc);
- // FIXME: add signatures obtained from donau to response
+
pay_end (pc,
TALER_MHD_REPLY_JSON_PACK (
pc->connection,
@@ -1774,129 +1890,527 @@ phase_payment_notification (struct PayContext *pc)
}
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
/**
- * Function called with information about a coin that was deposited.
+ * Parse signatures to JSON.
*
- * @param cls closure
- * @param exchange_url exchange where @a coin_pub was deposited
- * @param coin_pub public key of the coin
- * @param amount_with_fee amount the exchange will deposit for this coin
- * @param deposit_fee fee the exchange will charge for this coin
- * @param refund_fee fee the exchange will charge for refunding this coin
+ * @param num_sig number of signatures
+ * @param signatures Blinded donation unit signatures
+ * @param[out] j_signatures JSON object
+ * @return true if all is fine,
+ * false if we could not compose the JSON
+ * is malformed.
*/
-static void
-check_coin_paid (void *cls,
- const char *exchange_url,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *amount_with_fee,
- const struct TALER_Amount *deposit_fee,
- const struct TALER_Amount *refund_fee)
+static bool
+signatures_to_json (size_t num_sig,
+ const struct DONAU_BlindedDonationUnitSignature *signatures,
+ json_t *j_signatures)
{
- struct PayContext *pc = cls;
-
- for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++)
+ for (size_t i = 0; i < num_sig; i++)
{
- struct DepositConfirmation *dc = &pc->parse_pay.dc[i];
+ const struct DONAU_BlindedDonationUnitSignature *signature = &signatures[i];
- if (dc->found_in_db)
- continue; /* processed earlier, skip "expensive" memcmp() */
- /* Get matching coin from results*/
- if ( (0 != GNUNET_memcmp (coin_pub,
- &dc->cdd.coin_pub)) ||
- (0 !=
- strcmp (exchange_url,
- dc->exchange_url)) ||
- (GNUNET_OK !=
- TALER_amount_cmp_currency (amount_with_fee,
- &dc->cdd.amount)) ||
- (0 != TALER_amount_cmp (amount_with_fee,
- &dc->cdd.amount)) )
- continue; /* does not match, skip */
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Deposit of coin `%s' already in our DB.\n",
- TALER_B2S (coin_pub));
- if ( (GNUNET_OK !=
- TALER_amount_cmp_currency (&pc->pay_transaction.total_paid,
- amount_with_fee)) ||
- (GNUNET_OK !=
- TALER_amount_cmp_currency (&pc->pay_transaction.total_fees_paid,
- deposit_fee)) )
+ json_t *sig_obj = GNUNET_JSON_PACK (
+ DONAU_JSON_pack_blinded_donation_unit_sig ("blinded_signature",
+ signature));
+ if (NULL == sig_obj)
{
- GNUNET_break_op (0);
- pc->pay_transaction.deposit_currency_mismatch = true;
- break;
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to pack blinded signature #%zu",
+ i);
+ return false;
+ }
+ if (0 != json_array_append_new (j_signatures, sig_obj))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to append blinded signature #%zu to JSON array",
+ i);
+ json_decref (sig_obj);
+ return false;
}
- GNUNET_assert (0 <=
- TALER_amount_add (&pc->pay_transaction.total_paid,
- &pc->pay_transaction.total_paid,
- amount_with_fee));
- GNUNET_assert (0 <=
- TALER_amount_add (&pc->pay_transaction.total_fees_paid,
- &pc->pay_transaction.total_fees_paid,
- deposit_fee));
- dc->deposit_fee = *deposit_fee;
- dc->refund_fee = *refund_fee;
- dc->cdd.amount = *amount_with_fee;
- dc->found_in_db = true;
- pc->pay_transaction.pending--;
}
+ return true;
}
+#endif /* HAVE_DONAU_DONAU_SERVICE_H */
+
+
/**
- * Function called with information about a refund. Check if this coin was
- * claimed by the wallet for the transaction, and if so add the refunded
- * amount to the pc's "total_refunded" amount.
+ * Phase to process received donation receipts and store them into the database.
*
- * @param cls closure with a `struct PayContext`
- * @param coin_pub public coin from which the refund comes from
- * @param refund_amount refund amount which is being taken from @a coin_pub
+ * @param pc payment context
*/
static void
-check_coin_refunded (void *cls,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *refund_amount)
+phase_final_output_token_processing (struct PayContext *pc)
{
- struct PayContext *pc = cls;
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+ if (pc->parse_wallet_data.num_bkps > 0)
+ {
+ if (pc->parse_wallet_data.num_bkps !=
+ pc->donau_receipt.num_sigs)
+ { /* Not supposed to happen, as it is already checked by donau c-api, but just in case */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Mismatch in wallet_data.num_bkps %llu and "
+ "donau_receipt.num_sigs %llu",
+ (unsigned long long) pc->parse_wallet_data.num_bkps,
+ (unsigned long long) pc->donau_receipt.num_sigs);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "Mismatch between blinded key pairs and donation signatures received from donau"));
+ return;
+ }
- /* We look at refunds here that apply to the coins
- that the customer is currently trying to pay us with.
+ if ( (NULL == pc->donau_receipt.donau_sigs_json) ||
+ (! signatures_to_json (pc->donau_receipt.num_sigs,
+ pc->donau_receipt.sigs,
+ pc->donau_receipt.donau_sigs_json)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to convert Donau signatures to JSON (order %s)",
+ pc->order_id);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_JSON_INVALID,
+ "donau signature JSON creation failure"));
+ return;
+ }
- Such refunds are not "normal" refunds, but abort-pay refunds, which are
- given in the case that the wallet aborts the payment.
- In the case the wallet then decides to complete the payment *after* doing
- an abort-pay refund (an unusual but possible case), we need
- to make sure that existing refunds are accounted for. */
+ /* Attaching sigs from the donau to output_tokens*/
+ uint64_t d_out = -1;
+ const struct TALER_MERCHANT_ContractChoice *choice =
+ &pc->check_contract.contract_terms->details.v1
+ .choices[pc->parse_wallet_data.choice_index];
- for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++)
+ for (size_t i = 0; i < pc->validate_tokens.output_tokens_len; i++)
+ if (i < choice->outputs_len &&
+ TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT ==
+ choice->outputs[i].type)
+ {
+ d_out = i;
+ break;
+ }
+ GNUNET_assert (-1 != d_out);
+
+ {
+ uint64_t ot_len
+ = pc->validate_tokens.output_tokens_len;
+ uint64_t ot_len_with_donau_sig
+ = pc->validate_tokens.output_tokens_len + pc->donau_receipt.num_sigs
+ - 1;
+
+ if (pc->donau_receipt.num_sigs > 1)
+ {
+ GNUNET_array_grow (pc->validate_tokens.output_tokens,
+ pc->validate_tokens.output_tokens_len,
+ ot_len_with_donau_sig);
+
+ memmove (
+ &pc->validate_tokens.output_tokens[d_out
+ + pc->donau_receipt.num_sigs],
+ &pc->validate_tokens.output_tokens[d_out + 1],
+ (ot_len - d_out - 1) * sizeof (struct SignedOutputToken));
+ }
+
+ for (unsigned int i = 0; i < pc->donau_receipt.num_sigs; i++)
+ {
+ struct SignedOutputToken *ot =
+ &pc->validate_tokens.output_tokens[d_out + i];
+
+ ot->sig.signature = pc->donau_receipt.sigs[i].blinded_sig;
+ ot->h_issue.hash =
+ pc->parse_wallet_data.bkps[i].h_donation_unit_pub.hash;
+
+ }
+ pc->validate_tokens.output_tokens_len = ot_len_with_donau_sig;
+ }
+ }
+#endif
+ /* Part of saving the output_token sigs so that on re-pay we can fetch them from db*/
+ enum GNUNET_DB_QueryStatus qs;
+ unsigned int retry;
+
+ TMH_db->preflight (TMH_db->cls);
+ if (GNUNET_OK != TMH_db->start (TMH_db->cls,
+ "insert_order_blinded_sigs"))
{
- struct DepositConfirmation *dc = &pc->parse_pay.dc[i];
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ "start insert blinded donation receipts failed"));
+ return;
+ }
- /* Get matching coins from results. */
- if (0 != GNUNET_memcmp (coin_pub,
- &dc->cdd.coin_pub))
+ for (retry = 0; retry < MAX_RETRIES; retry++)
+ {
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+ if (pc->parse_wallet_data.num_bkps > 0)
+ {
+ qs = TMH_db->update_donau_instance_receipts_amount (
+ TMH_db->cls,
+ &pc->parse_wallet_data.donau_instance_serial,
+ &pc->parse_wallet_data.charity_receipts_to_date);
+
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Retrying update of Donau instance receipts amount");
+ continue;
+ }
+ if (0 >= qs)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "update donau instance receipts amount"));
+ return;
+ }
+ }
+#endif /* HAVE_DONAU_DONAU_SERVICE_H*/
+
+ for (unsigned int i = 0;
+ i < pc->validate_tokens.output_tokens_len;
+ i++)
+ {
+ qs = TMH_db->insert_order_blinded_sigs (
+ TMH_db->cls,
+ pc->order_id,
+ i,
+ &pc->validate_tokens.output_tokens[i].h_issue.hash,
+ pc->validate_tokens.output_tokens[i].sig.signature);
+
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Retrying insert blinded token signature");
+ continue;
+ }
+
+ if (0 >= qs)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert blinded donation receipts")
+ );
+ return;
+ }
+ }
+
+ qs = TMH_db->commit (TMH_db->cls);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ {
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Retrying commit blinded donation receipts");
continue;
- if (GNUNET_OK !=
- TALER_amount_cmp_currency (&pc->pay_transaction.total_refunded,
- refund_amount))
+ }
+ if (0 > qs)
{
- GNUNET_break (0);
- pc->pay_transaction.refund_currency_mismatch = true;
- break;
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ "commit blinded donation receipts"));
+ return;
}
- GNUNET_assert (0 <=
- TALER_amount_add (&pc->pay_transaction.total_refunded,
- &pc->pay_transaction.total_refunded,
- refund_amount));
break;
}
+
+ if (retry == MAX_RETRIES)
+ {
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ "insert blinded donation receipts retry exhausted"));
+ return;
+ }
+ pc->phase = PP_PAYMENT_NOTIFICATION;
}
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
/**
- * Check whether the amount paid is sufficient to cover the price.
+ * Callback to handle the result of a batch issue request.
*
- * @param pc payment context to check
+ * @param cls our `struct PayContext`
+ * @param resp the response from Donau
+ */
+static void
+merchant_donau_issue_receipt_cb (void *cls,
+ const struct DONAU_BatchIssueResponse *resp)
+{
+ struct PayContext *pc = cls;
+ /* Donau replies asynchronously, so we expect the PayContext
+ * to be suspended. */
+ GNUNET_assert (GNUNET_YES == pc->suspended);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Donau responded with status=%u, ec=%u",
+ resp->hr.http_status,
+ resp->hr.ec);
+ switch (resp->hr.http_status)
+ {
+ case 0:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Donau batch issue request from merchant-httpd failed (http_status==0)");
+ resume_pay_with_error (pc,
+ resp->hr.ec,
+ "Donau batch issue request failed");
+ return;
+
+ case MHD_HTTP_OK:
+ case MHD_HTTP_CREATED:
+ if (TALER_EC_NONE != resp->hr.ec)
+ /* Most probably, it is just some small flaw from
+ * donau so no point in failing, yet we have to display it */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Donau signalled error %u despite HTTP %u",
+ resp->hr.ec,
+ resp->hr.http_status);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Donau accepted donation receipts with total_issued=%s",
+ TALER_amount2s (&resp->details.ok.issued_amount));
+
+ pc->donau_receipt.sigs =
+ resp->details.ok.blinded_sigs;
+ pc->donau_receipt.num_sigs =
+ resp->details.ok.num_blinded_sigs;
+ pc->donau_receipt.donau_sigs_json =
+ json_array ();
+ pc->phase = PP_FINAL_OUTPUT_TOKEN_PROCESSING;
+ pay_resume (pc);
+ return;
+
+ case MHD_HTTP_BAD_REQUEST:
+ case MHD_HTTP_FORBIDDEN:
+ case MHD_HTTP_NOT_FOUND:
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ default: /* make sure that everything except 200/201 will end up here*/
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Donau replied with HTTP %u (ec=%u)",
+ resp->hr.http_status,
+ resp->hr.ec);
+ resume_pay_with_error (pc,
+ resp->hr.ec,
+ "Donau HTTP error");
+ return;
+ }
+}
+
+
+/**
+ * Parse a bkp encoded in JSON.
+ *
+ * @param[out] bkp where to return the result
+ * @param bkp_key_obj json to parse
+ * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if @a bkp_key_obj
+ * is malformed.
+ */
+static enum GNUNET_GenericReturnValue
+merchant_parse_json_bkp (struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkp,
+ const json_t *bkp_key_obj)
+{
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("h_donation_unit_pub",
+ &bkp->h_donation_unit_pub),
+ DONAU_JSON_spec_blinded_donation_identifier ("blinded_udi",
+ &bkp->blinded_udi),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (bkp_key_obj,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+#endif
+
+
+/**
+ * Generate a donation signature for the bkp and charity.
+ *
+ * @param pc payment context containing the charity and bkps
+ */
+static void
+phase_request_donation_receipt (struct PayContext *pc)
+{
+#ifndef HAVE_DONAU_DONAU_SERVICE_H
+ /* If Donau is disabled at compile-time, skip. */
+ pc->phase = PP_FINAL_OUTPUT_TOKEN_PROCESSING;
+ return;
+#else
+ pc->donau_receipt.birh =
+ DONAU_charity_issue_receipt (TMH_curl_ctx,
+ pc->parse_wallet_data.donau.donau_url,
+ &pc->parse_wallet_data.charity_priv,
+ pc->parse_wallet_data.charity_id,
+ pc->parse_wallet_data.donau.donation_year,
+ pc->parse_wallet_data.num_bkps,
+ pc->parse_wallet_data.bkps,
+ &merchant_donau_issue_receipt_cb,
+ pc);
+ if (NULL == pc->donau_receipt.birh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to create Donau receipt request");
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_CLIENT_INTERNAL_ERROR,
+ "Donau request creation error"));
+ return;
+ }
+ MHD_suspend_connection (pc->connection);
+ pc->suspended = GNUNET_YES;
+#endif
+}
+
+
+/**
+ * Function called with information about a coin that was deposited.
+ *
+ * @param cls closure
+ * @param exchange_url exchange where @a coin_pub was deposited
+ * @param coin_pub public key of the coin
+ * @param amount_with_fee amount the exchange will deposit for this coin
+ * @param deposit_fee fee the exchange will charge for this coin
+ * @param refund_fee fee the exchange will charge for refunding this coin
+ */
+static void
+check_coin_paid (void *cls,
+ const char *exchange_url,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *amount_with_fee,
+ const struct TALER_Amount *deposit_fee,
+ const struct TALER_Amount *refund_fee)
+{
+ struct PayContext *pc = cls;
+
+ for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++)
+ {
+ struct DepositConfirmation *dc = &pc->parse_pay.dc[i];
+
+ if (dc->found_in_db)
+ continue; /* processed earlier, skip "expensive" memcmp() */
+ /* Get matching coin from results*/
+ if ( (0 != GNUNET_memcmp (coin_pub,
+ &dc->cdd.coin_pub)) ||
+ (0 !=
+ strcmp (exchange_url,
+ dc->exchange_url)) ||
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (amount_with_fee,
+ &dc->cdd.amount)) ||
+ (0 != TALER_amount_cmp (amount_with_fee,
+ &dc->cdd.amount)) )
+ continue; /* does not match, skip */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Deposit of coin `%s' already in our DB.\n",
+ TALER_B2S (coin_pub));
+ if ( (GNUNET_OK !=
+ TALER_amount_cmp_currency (&pc->pay_transaction.total_paid,
+ amount_with_fee)) ||
+ (GNUNET_OK !=
+ TALER_amount_cmp_currency (&pc->pay_transaction.total_fees_paid,
+ deposit_fee)) )
+ {
+ GNUNET_break_op (0);
+ pc->pay_transaction.deposit_currency_mismatch = true;
+ break;
+ }
+ GNUNET_assert (0 <=
+ TALER_amount_add (&pc->pay_transaction.total_paid,
+ &pc->pay_transaction.total_paid,
+ amount_with_fee));
+ GNUNET_assert (0 <=
+ TALER_amount_add (&pc->pay_transaction.total_fees_paid,
+ &pc->pay_transaction.total_fees_paid,
+ deposit_fee));
+ dc->deposit_fee = *deposit_fee;
+ dc->refund_fee = *refund_fee;
+ dc->cdd.amount = *amount_with_fee;
+ dc->found_in_db = true;
+ pc->pay_transaction.pending--;
+ }
+}
+
+
+/**
+ * Function called with information about a refund. Check if this coin was
+ * claimed by the wallet for the transaction, and if so add the refunded
+ * amount to the pc's "total_refunded" amount.
+ *
+ * @param cls closure with a `struct PayContext`
+ * @param coin_pub public coin from which the refund comes from
+ * @param refund_amount refund amount which is being taken from @a coin_pub
+ */
+static void
+check_coin_refunded (void *cls,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *refund_amount)
+{
+ struct PayContext *pc = cls;
+
+ /* We look at refunds here that apply to the coins
+ that the customer is currently trying to pay us with.
+
+ Such refunds are not "normal" refunds, but abort-pay refunds, which are
+ given in the case that the wallet aborts the payment.
+ In the case the wallet then decides to complete the payment *after* doing
+ an abort-pay refund (an unusual but possible case), we need
+ to make sure that existing refunds are accounted for. */
+
+ for (size_t i = 0; i<pc->parse_pay.coins_cnt; i++)
+ {
+ struct DepositConfirmation *dc = &pc->parse_pay.dc[i];
+
+ /* Get matching coins from results. */
+ if (0 != GNUNET_memcmp (coin_pub,
+ &dc->cdd.coin_pub))
+ continue;
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (&pc->pay_transaction.total_refunded,
+ refund_amount))
+ {
+ GNUNET_break (0);
+ pc->pay_transaction.refund_currency_mismatch = true;
+ break;
+ }
+ GNUNET_assert (0 <=
+ TALER_amount_add (&pc->pay_transaction.total_refunded,
+ &pc->pay_transaction.total_refunded,
+ refund_amount));
+ break;
+ }
+}
+
+
+/**
+ * Check whether the amount paid is sufficient to cover the price.
+ *
+ * @param pc payment context to check
* @return true if the payment is sufficient, false if it is
* insufficient
*/
@@ -2375,37 +2889,70 @@ phase_execute_pay_transaction (struct PayContext *pc)
}
}
- /* Store signed output tokens in database. */
- for (size_t i = 0; i<pc->validate_tokens.output_tokens_len; i++)
- {
- struct SignedOutputToken *output = &pc->validate_tokens.output_tokens[i];
-
- enum GNUNET_DB_QueryStatus qs;
- qs = TMH_db->insert_issued_token (TMH_db->cls,
- &pc->check_contract.h_contract_terms,
- &output->h_issue,
- &output->sig);
+ {
+ const struct TALER_MERCHANT_ContractChoice *choice =
+ &pc->check_contract.contract_terms->details.v1
+ .choices[pc->parse_wallet_data.choice_index];
- if (0 >= qs)
+ for (size_t i = 0; i<pc->validate_tokens.output_tokens_len; i++)
{
- TMH_db->rollback (TMH_db->cls);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return; /* do it again */
- /* Always report on hard error as well to enable diagnostics */
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- pay_end (pc,
- TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "insert output token"));
- return;
+ switch (choice->outputs[i].type)
+ {
+ case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_INVALID:
+ /* Well, good luck getting here */
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "invalid output type"));
+ break;
+ case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
+ /* We skip outputs of donation receipts here, as they are handled in the
+ * phase_final_output_token_processing() callback from donau */
+ break;
+ case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
+ struct SignedOutputToken *output =
+ &pc->validate_tokens.output_tokens[i];
+
+ enum GNUNET_DB_QueryStatus qs;
+ qs = TMH_db->insert_issued_token (TMH_db->cls,
+ &pc->check_contract.h_contract_terms,
+ &output->h_issue,
+ &output->sig);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ TMH_db->rollback (TMH_db->cls);
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert output token"));
+ return;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ /* Serialization failure, retry */
+ TMH_db->rollback (TMH_db->cls);
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* UNIQUE constraint violation, meaning this token was already used. */
+ TMH_db->rollback (TMH_db->cls);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "duplicate output token"));
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ break;
+ }
}
}
- // FIXME: insert donau blinded inputs (into DB here!),
- // idempotency: if already exists, no problem!
-
TMH_notify_order_change (hc->instance,
TMH_OSF_CLAIMED | TMH_OSF_PAID,
pc->check_contract.contract_terms->timestamp,
@@ -2459,10 +3006,19 @@ phase_execute_pay_transaction (struct PayContext *pc)
return;
}
}
- // FIXME: if we have donation receipts, do NEW phase
- // DONAU interaction here, otherwise skip DONAU phase
- // and move to payment notification
- pc->phase = PP_PAYMENT_NOTIFICATION;
+
+ if (NULL != pc->parse_wallet_data.donau.donau_url)
+ {
+ pc->phase = PP_REQUEST_DONATION_RECEIPT;
+ }
+ else if (pc->validate_tokens.output_tokens_len > 0)
+ {
+ pc->phase = PP_FINAL_OUTPUT_TOKEN_PROCESSING;
+ }
+ else
+ {
+ pc->phase = PP_PAYMENT_NOTIFICATION;
+ }
}
@@ -2492,7 +3048,7 @@ find_valid_input_tokens (
struct TokenUseConfirmation *tuc = &pc->parse_pay.tokens[index + j];
const struct TALER_MERCHANT_ContractTokenFamilyKey *key = NULL;
- for (unsigned int i=0; i<family->keys_len; i++)
+ for (unsigned int i = 0; i<family->keys_len; i++)
{
const struct TALER_MERCHANT_ContractTokenFamilyKey *ki
= &family->keys[i];
@@ -2612,7 +3168,7 @@ test_tfk_mandatory (enum TALER_MERCHANTDB_TokenFamilyKind tfk)
/**
* Sign the tokens provided by the wallet for a particular @a key.
*
- * @param[in,out] payment we are processing
+ * @param[in,out] pc reference for payment we are processing
* @param key token family data
* @param priv private key to use to sign with
* @param mandatory true if the token must exist, if false
@@ -2725,6 +3281,240 @@ find_family (const struct PayContext *pc,
/**
+ * Handle contract output of type TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN.
+ * Looks up the token family, loads the matching private key,
+ * and signs the corresponding token envelopes from the wallet.
+ *
+ * @param pc context for the pay request
+ * @param output contract output we need to process
+ * @param output_index index of this output in the contract's outputs array
+ * @return #GNUNET_OK on success, #GNUNET_NO if an error was encountered
+ */
+static enum GNUNET_GenericReturnValue
+handle_output_token (struct PayContext *pc,
+ const struct TALER_MERCHANT_ContractOutput *output,
+ unsigned int output_index)
+{
+ const struct TALER_MERCHANT_ContractTokenFamily *family;
+ struct TALER_MERCHANT_ContractTokenFamilyKey *key;
+ struct TALER_MERCHANTDB_TokenFamilyKeyDetails details;
+ enum GNUNET_DB_QueryStatus qs;
+ bool mandatory;
+
+ /* Locate token family in the contract. This should never fail if
+ * the contract passed validation before insertion. */
+ family = find_family (pc,
+ output->details.token.token_family_slug);
+ if (NULL == family)
+ {
+ /* This "should never happen", so treat it as an internal error */
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "token family not found in order"));
+ return GNUNET_SYSERR;
+ }
+
+ /* Check the key_index field from the output. */
+ if (output->details.token.key_index >= family->keys_len)
+ {
+ /* Also "should never happen", contract was presumably validated on insert */
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "key index invalid for token family"));
+ return GNUNET_SYSERR;
+ }
+
+ /* Pick the correct key inside that family. */
+ key = &family->keys[output->details.token.key_index];
+
+ /* Fetch the private key from the DB for the merchant instance and
+ * this particular family/time interval. */
+ qs = TMH_db->lookup_token_family_key (
+ TMH_db->cls,
+ pc->hc->instance->settings.id,
+ family->slug,
+ pc->check_contract.contract_terms->timestamp,
+ pc->check_contract.contract_terms->pay_deadline,
+ &details);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Database error looking up token-family key for %s\n",
+ family->slug);
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL));
+ return GNUNET_NO;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_log (
+ GNUNET_ERROR_TYPE_ERROR,
+ "Token-family key for %s not found at [%llu,%llu]\n",
+ family->slug,
+ (unsigned long long)
+ pc->check_contract.contract_terms->timestamp.abs_time.abs_value_us,
+ (unsigned long long)
+ pc->check_contract.contract_terms->pay_deadline.abs_time.abs_value_us
+ );
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL));
+ return GNUNET_NO;
+
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ GNUNET_assert (NULL != details.priv.private_key);
+
+ /* Depending on the token family, decide if the token envelope
+ * is mandatory or optional. (Simplified logic here: adapt as needed.) */
+ mandatory = test_tfk_mandatory (details.token_family.kind);
+
+ /* Actually sign the number of token envelopes specified in 'count'.
+ * 'output_index' is the offset into the parse_wallet_data arrays. */
+ if (GNUNET_OK !=
+ sign_token_envelopes (pc,
+ key,
+ &details.priv,
+ mandatory,
+ output_index,
+ output->details.token.count))
+ {
+ /* sign_token_envelopes() already queued up an error via pay_end() */
+ GNUNET_break_op (0);
+ return GNUNET_NO;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Handle checks for contract output of type TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT.
+ * For now, this does nothing and simply returns #GNUNET_OK.
+ *
+ * @param pc context for the pay request
+ * @param output the contract output describing the donation receipt requirement
+ * @param output_index index of this output in the contract's outputs array
+ * @return #GNUNET_OK unconditionally (placeholder)
+ */
+static enum GNUNET_GenericReturnValue
+handle_output_donation_receipt (
+ struct PayContext *pc,
+ const struct TALER_MERCHANT_ContractOutput *output,
+ unsigned int output_index)
+{
+#ifndef HAVE_DONAU_DONAU_SERVICE_H
+ /* If you ended up here, I want to know how... - Bohdan */
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_NOT_IMPLEMENTED,
+ TALER_EC_MERCHANT_GENERIC_FEATURE_NOT_AVAILABLE,
+ "donau support disabled"));
+ return GNUNET_NO;
+#else
+ if (GNUNET_OK !=
+ DONAU_get_donation_amount_from_bkps (
+ pc->parse_wallet_data.donau_keys,
+ pc->parse_wallet_data.bkps,
+ pc->parse_wallet_data.num_bkps,
+ pc->parse_wallet_data.donau.donation_year,
+ &pc->parse_wallet_data.donation_amount) )
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "inconsistent bkps / donau keys"));
+ return GNUNET_NO;
+ }
+
+ if (GNUNET_OK !=
+ TALER_amount_cmp_currency (&pc->parse_wallet_data.donation_amount,
+ &output->details.donation_receipt.amount))
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_CURRENCY_MISMATCH,
+ output->details.donation_receipt.amount.currency));
+ return GNUNET_NO;
+ }
+
+ if (0 != TALER_amount_cmp (&pc->parse_wallet_data.donation_amount,
+ &output->details.donation_receipt.amount))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Wallet amount: %s\n",
+ TALER_amount2s (&pc->parse_wallet_data.donation_amount));
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Donation receipt amount: %s\n",
+ TALER_amount2s (&output->details.donation_receipt.amount));
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_DONATION_AMOUNT_MISMATCH,
+ "donation amount mismatch"));
+ return GNUNET_NO;
+ }
+ {
+ struct TALER_Amount receipts_to_date;
+ if (0 >
+ TALER_amount_add (&receipts_to_date,
+ &pc->parse_wallet_data.charity_receipts_to_date,
+ &pc->parse_wallet_data.donation_amount))
+ {
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_AMOUNT_OVERFLOW,
+ "adding donation amount"));
+ return GNUNET_NO;
+ }
+
+ if (1 ==
+ TALER_amount_cmp (&receipts_to_date,
+ &pc->parse_wallet_data.charity_max_per_year))
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_MERCHANT_POST_ORDERS_ID_PAY_DONATION_AMOUNT_MISMATCH,
+ "donation limit exceeded"));
+ return GNUNET_NO;
+ }
+ pc->parse_wallet_data.charity_receipts_to_date = receipts_to_date;
+ }
+ return GNUNET_OK;
+#endif /* HAVE_DONAU_DONAU_SERVICE_H */
+}
+
+
+/**
* Validate tokens and token envelopes. First, we check if all tokens listed
* in the 'inputs' array of the selected choice are present in the 'tokens'
* array of the request. Then, we validate the signatures of each provided
@@ -2798,92 +3588,33 @@ phase_validate_tokens (struct PayContext *pc)
for (unsigned int i = 0; i<selected->outputs_len; i++)
{
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_MERCHANTDB_TokenFamilyKeyDetails details;
const struct TALER_MERCHANT_ContractOutput *output
= &selected->outputs[i];
- const struct TALER_MERCHANT_ContractTokenFamily *family;
- struct TALER_MERCHANT_ContractTokenFamilyKey *key;
- // FIXME: check donau outputs are good choices
- // (allowed donau, total amount below max, correct year, ...)
- // change 'if' to switch...
- if (output->type != TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN)
+ switch (output->type)
{
- /* only validate outputs of type tokens (for now) */
+ case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
+ if (GNUNET_OK !=
+ handle_output_token (pc,
+ output,
+ i))
+ {
+ /* Error is already scheduled from handle_output_token. */
+ return;
+ }
+ break;
+ case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
+ if (GNUNET_OK !=
+ handle_output_donation_receipt (pc,
+ output,
+ i))
+ {
+ /* Error is already scheduled from handle_output_donation_receipt. */
+ return;
+ }
+ continue;
+ default:
continue;
- }
- // FIXME: move this into a function for the switch case on token...
- family = find_family (pc,
- output->details.token.token_family_slug);
- if (NULL == family)
- {
- /* this should never happen, since the choices and
- token families are validated on insert. */
- GNUNET_break (0);
- pay_end (pc,
- TALER_MHD_reply_with_error (
- pc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- "token family not found in order"));
- return;
- }
- if (output->details.token.key_index >= family->keys_len)
- {
- /* this should never happen, since the choices and
- token families are validated on insert. */
- GNUNET_break (0);
- pay_end (pc,
- TALER_MHD_reply_with_error (
- pc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- "key index invalid for token family"));
- return;
- }
- key = &family->keys[output->details.token.key_index];
- qs = TMH_db->lookup_token_family_key (
- TMH_db->cls,
- pc->hc->instance->settings.id,
- family->slug,
- pc->check_contract.contract_terms->timestamp,
- pc->check_contract.contract_terms->pay_deadline,
- &details);
- if (qs <= 0)
- {
- GNUNET_log (
- GNUNET_ERROR_TYPE_ERROR,
- "Did not find key for %s at [%llu,%llu]\n",
- family->slug,
- (unsigned long long) pc->check_contract.contract_terms->timestamp.
- abs_time.
- abs_value_us,
- (unsigned long long) pc->check_contract.contract_terms->pay_deadline
- .abs_time.
- abs_value_us);
- GNUNET_break (0);
- pay_end (pc,
- TALER_MHD_reply_with_error (
- pc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- NULL));
- return;
- }
-
- GNUNET_assert (NULL != details.priv.private_key);
- if (GNUNET_OK !=
- sign_token_envelopes (
- pc,
- key,
- &details.priv,
- test_tfk_mandatory (details.token_family.kind),
- i,
- output->details.token.count))
- {
- /* Error is already scheduled from sign_token_envelopes. */
- return;
}
}
}
@@ -2967,6 +3698,18 @@ deposit_paid_check (
}
+/**
+ * Function called with information about a token that was spent.
+ * FIXME: Replace this with a more specific function for this cb
+ *
+ * @param cls closure with `struct PayContext *`
+ * @param spent_token_serial "serial" of the spent token unused
+ * @param h_contract_terms hash of the contract terms unused
+ * @param h_issue_pub hash of the token issue public key unused
+ * @param use_pub public key of the token
+ * @param use_sig signature of the token
+ * @param issue_sig signature of the token issue
+ */
static void
input_tokens_paid_check (
void *cls,
@@ -2998,6 +3741,29 @@ input_tokens_paid_check (
/**
+ * Small helper function to append an output token signature from db
+ *
+ * @param cls closure with `struct PayContext *`
+ * @param h_issue hash of the token
+ * @param sig signature of the token
+ */
+static void
+append_output_token_sig (void *cls,
+ struct GNUNET_HashCode *h_issue,
+ struct GNUNET_CRYPTO_BlindedSignature *sig)
+{
+ struct PayContext *pc = cls;
+ struct SignedOutputToken out;
+
+ out.h_issue.hash = *h_issue;
+ out.sig.signature = sig;
+ GNUNET_array_append (pc->validate_tokens.output_tokens,
+ pc->validate_tokens.output_tokens_len,
+ out);
+}
+
+
+/**
* Handle case where contract was already paid. Either decides
* the payment is idempotent, or refunds the excess payment.
*
@@ -3066,6 +3832,30 @@ phase_contract_paid (struct PayContext *pc)
if (! tuc->found_in_db)
unmatched = true;
}
+
+ /* In this part we are fetching token_sigs related output */
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TMH_db->select_order_blinded_sigs (
+ TMH_db->cls,
+ pc->order_id,
+ &append_output_token_sig,
+ pc
+ );
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_order_blinded_sigs"));
+ return;
+ }
+ }
+
if (! unmatched)
{
/* Everything fine, idempotent request, generate response immediately */
@@ -3363,6 +4153,8 @@ static void
phase_parse_wallet_data (struct PayContext *pc)
{
const json_t *tokens_evs;
+ const json_t *donau_obj;
+
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_int16 ("choice_index",
@@ -3372,12 +4164,10 @@ phase_parse_wallet_data (struct PayContext *pc)
GNUNET_JSON_spec_array_const ("tokens_evs",
&tokens_evs),
NULL),
- // FIXME: extend spec for wallet to submit
- // - URL of selected donau
- // - year
- // - BUDIs with blinded donation receipts (donau-key-hash, blinded value)
- // + check in later phase (once we have the contract)
- // that the selected donau was offered and the BUDIs are below the allowed amount
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("donau",
+ &donau_obj),
+ NULL),
GNUNET_JSON_spec_end ()
};
@@ -3474,6 +4264,144 @@ phase_parse_wallet_data (struct PayContext *pc)
}
}
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+ if (NULL != donau_obj)
+ {
+ const char *donau_url_tmp;
+ const json_t *budikeypairs;
+ json_t *donau_keys_json;
+
+ /* Fetching and checking that all 3 are present in some way */
+ struct GNUNET_JSON_Specification dspec[] = {
+ GNUNET_JSON_spec_string ("url",
+ &donau_url_tmp),
+ GNUNET_JSON_spec_uint64 ("year",
+ &pc->parse_wallet_data.donau.donation_year),
+ GNUNET_JSON_spec_array_const ("budikeypairs",
+ &budikeypairs),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (pc->connection,
+ donau_obj,
+ dspec);
+ if (GNUNET_YES != res)
+ {
+ GNUNET_break_op (0);
+ pay_end (pc,
+ (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO);
+ return;
+ }
+
+ if (0 == json_array_size (budikeypairs))
+ {
+ GNUNET_break_op (0);
+ resume_pay_with_error (pc,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "Empty 'budikeypairs' array");
+ return;
+ }
+
+ /* Check if the needed data is present for the given donau URL */
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ qs = TMH_db->lookup_order_charity (
+ TMH_db->cls,
+ pc->hc->instance->settings.id,
+ donau_url_tmp,
+ &pc->parse_wallet_data.charity_id,
+ &pc->parse_wallet_data.charity_priv,
+ &pc->parse_wallet_data.charity_max_per_year,
+ &pc->parse_wallet_data.charity_receipts_to_date,
+ &donau_keys_json,
+ &pc->parse_wallet_data.donau_instance_serial);
+
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TMH_db->rollback (TMH_db->cls);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_order_charity"));
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ TMH_db->rollback (TMH_db->cls);
+ pay_end (pc,
+ TALER_MHD_reply_with_error (
+ pc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "No matching Donau charity found for the given URL"));
+ return;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ pc->parse_wallet_data.donau.donau_url =
+ GNUNET_strdup (donau_url_tmp);
+ break;
+ }
+ }
+
+ {
+ pc->parse_wallet_data.donau_keys =
+ DONAU_keys_from_json (donau_keys_json);
+ json_decref (donau_keys_json);
+ if (! pc->parse_wallet_data.donau_keys)
+ {
+ GNUNET_break_op (0);
+ resume_pay_with_error (pc,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "Invalid donau_keys");
+ return;
+ }
+ }
+
+ /* Stage to parse the budikeypairs from json to struct */
+ {
+ size_t num_bkps = json_array_size (budikeypairs);
+ struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkps =
+ GNUNET_new_array (num_bkps,
+ struct DONAU_BlindedUniqueDonorIdentifierKeyPair);
+
+ /* Change to json for each*/
+ for (size_t i = 0; i < num_bkps; i++)
+ {
+ const json_t *bkp_obj = json_array_get (budikeypairs,
+ i);
+ if (GNUNET_SYSERR == merchant_parse_json_bkp (&bkps[i],
+ bkp_obj))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (bkps);
+ resume_pay_with_error (pc,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "Failed to parse budikeypairs");
+ return;
+ }
+ }
+
+ pc->parse_wallet_data.num_bkps = num_bkps;
+ pc->parse_wallet_data.bkps = bkps;
+ }
+ }
+#else
+ /* Donau not compiled in: reject request if a donau object was given. */
+ if (NULL != donau_obj)
+ {
+ pay_end (pc,
+ TALER_MHD_reply_with_error (pc->connection,
+ MHD_HTTP_NOT_IMPLEMENTED,
+ TALER_EC_MERCHANT_GENERIC_FEATURE_NOT_AVAILABLE,
+ "donau support disabled"));
+ return;
+ }
+#endif /* HAVE_DONAU_DONAU_SERVICE_H */
+
TALER_json_hash (pc->parse_pay.wallet_data,
&pc->parse_wallet_data.h_wallet_data);
@@ -3511,6 +4439,23 @@ phase_parse_pay (struct PayContext *pc)
GNUNET_JSON_spec_end ()
};
+#if DEBUG
+ {
+ char *dump = json_dumps (pc->hc->request_body,
+ JSON_INDENT (2)
+ | JSON_ENCODE_ANY
+ | JSON_SORT_KEYS);
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "POST /orders/%s/pay – request body follows:\n%s\n",
+ pc->order_id,
+ dump);
+
+ free (dump);
+
+ }
+#endif /* DEBUG */
+
GNUNET_assert (PP_PARSE_PAY == pc->phase);
{
enum GNUNET_GenericReturnValue res;
@@ -3818,6 +4763,30 @@ pay_context_cleanup (void *cls)
pc_tail,
pc);
GNUNET_free (pc->check_contract.pos_key);
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+ if (NULL != pc->donau_receipt.donau_sigs_json)
+ {
+ json_decref (pc->donau_receipt.donau_sigs_json);
+ pc->donau_receipt.donau_sigs_json = NULL;
+ }
+ if (NULL != pc->donau_receipt.sigs)
+ {
+ GNUNET_free (pc->donau_receipt.sigs);
+ pc->donau_receipt.sigs = NULL;
+ pc->donau_receipt.num_sigs = 0;
+ }
+ if (NULL != pc->parse_wallet_data.bkps)
+ {
+ GNUNET_free (pc->parse_wallet_data.bkps);
+ pc->parse_wallet_data.bkps = NULL;
+ pc->parse_wallet_data.num_bkps = 0;
+ }
+ if (NULL != pc->parse_wallet_data.donau_keys)
+ {
+ DONAU_keys_decref (pc->parse_wallet_data.donau_keys);
+ pc->parse_wallet_data.donau_keys = NULL;
+ }
+#endif
GNUNET_free (pc);
}
@@ -3867,10 +4836,15 @@ TMH_post_orders_ID_pay (const struct TMH_RequestHandler *rh,
case PP_PAY_TRANSACTION:
phase_execute_pay_transaction (pc);
break;
+ case PP_REQUEST_DONATION_RECEIPT:
+ phase_request_donation_receipt (pc);
+ break;
+ case PP_FINAL_OUTPUT_TOKEN_PROCESSING:
+ phase_final_output_token_processing (pc);
+ break;
case PP_PAYMENT_NOTIFICATION:
phase_payment_notification (pc);
break;
- // FIXME: donau phase
case PP_SUCCESS_RESPONSE:
phase_success_response (pc);
break;
diff --git a/src/backend/taler-merchant-httpd_private-delete-donau-instance-ID.c b/src/backend/taler-merchant-httpd_private-delete-donau-instance-ID.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-delete-donau-instance-ID.c
+ * @brief implement DELETE /private/donau/$donau_serial_id
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+
+#include "platform.h"
+#include "taler-merchant-httpd_private-delete-donau-instance-ID.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+
+/**
+ * Handle a DELETE "/donau/$donau_serial_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_donau_instance_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;
+ uint64_t donau_serial_id;
+ char dummy;
+
+ GNUNET_assert (NULL != mi);
+
+ if (1 != sscanf (hc->infix,
+ "%lu%c",
+ &donau_serial_id,
+ &dummy))
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ hc->infix);
+ }
+
+ qs = TMH_db->delete_donau_instance (TMH_db->cls,
+ hc->instance->settings.id,
+ donau_serial_id);
+ 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_donau_instance");
+
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ "delete_donau_instance (soft)");
+
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_MERCHANT_GENERIC_INSTANCE_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;
+}
+\ No newline at end of file
diff --git a/src/backend/taler-merchant-httpd_private-delete-donau-instance-ID.h b/src/backend/taler-merchant-httpd_private-delete-donau-instance-ID.h
@@ -0,0 +1,44 @@
+/*
+ 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-donau-instance-ID.h
+ * @brief implement DELETE /private/donau/$charity_id/
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+
+#ifndef TALER_MERCHANT_HTTPD_PRIVATE_DELETE_DONAU_INSTANCE_ID_H
+#define TALER_MERCHANT_HTTPD_PRIVATE_DELETE_DONAU_INSTANCE_ID_H
+
+#include "taler-merchant-httpd.h"
+
+
+/**
+ * Handle a DELETE "/donau/$donau_serial_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_donau_instance_ID (
+ const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+/* end of taler-merchant-httpd_private-delete-donau-instance-ID.h */
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-donau-instances.c b/src/backend/taler-merchant-httpd_private-get-donau-instances.c
@@ -0,0 +1,121 @@
+/*
+ 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 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-donau-instances.c
+ * @brief implementation of GET /donau
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+#include "platform.h"
+#include <jansson.h>
+#include <taler/taler_json_lib.h>
+#include <taler/taler_dbevents.h>
+#include "taler_merchant_donau.h"
+#include "taler_merchant_service.h"
+#include "taler-merchant-httpd_private-get-donau-instances.h"
+
+
+/**
+ * Add details about a Donau instance to the JSON array.
+ *
+ * @param cls json array to which the Donau instance details will be added
+ * @param donau_instance_serial the serial number of the Donau instance
+ * @param donau_url the URL of the Donau instance
+ * @param charity_name the name of the charity
+ * @param charity_pub_key the public key of the charity
+ * @param charity_id the ID of the charity
+ * @param charity_max_per_year the maximum donation amount per year
+ * @param charity_receipts_to_date the total donations received so far this year
+ * @param current_year the current year being tracked for donations
+ * @param donau_keys_json JSON object with key information specific to the Donau instance
+ */
+static void
+add_donau_instance (void *cls,
+ uint64_t donau_instance_serial,
+ const char *donau_url,
+ const char *charity_name,
+ const struct DONAU_CharityPublicKeyP *charity_pub_key,
+ uint64_t charity_id,
+ const struct TALER_Amount *charity_max_per_year,
+ const struct TALER_Amount *charity_receipts_to_date,
+ int64_t current_year,
+ const json_t *donau_keys_json)
+{
+ json_t *json_instances = cls;
+
+ GNUNET_assert (
+ 0 == json_array_append_new (
+ json_instances,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 ("donau_instance_serial",
+ donau_instance_serial),
+ GNUNET_JSON_pack_string ("donau_url",
+ donau_url),
+ GNUNET_JSON_pack_string ("charity_name",
+ charity_name),
+ GNUNET_JSON_pack_data_auto ("charity_pub_key",
+ charity_pub_key),
+ GNUNET_JSON_pack_uint64 ("charity_id",
+ charity_id),
+ TALER_JSON_pack_amount ("charity_max_per_year",
+ charity_max_per_year),
+ TALER_JSON_pack_amount ("charity_receipts_to_date",
+ charity_receipts_to_date),
+ GNUNET_JSON_pack_int64 ("current_year",
+ current_year),
+ GNUNET_JSON_pack_object_incref ("donau_keys_json",
+ (json_t *) donau_keys_json)
+ )));
+}
+
+
+/**
+ * Handle a GET "/donau" 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_donau_instances (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ json_t *json_donau_instances = json_array ();
+ enum GNUNET_DB_QueryStatus qs;
+
+ TMH_db->preflight (TMH_db->cls);
+ qs = TMH_db->select_donau_instances (TMH_db->cls,
+ hc->instance->settings.id,
+ &add_donau_instance,
+ json_donau_instances);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ json_decref (json_donau_instances);
+ 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 (
+ "donau_instances",
+ json_donau_instances));
+}
+\ No newline at end of file
diff --git a/src/backend/taler-merchant-httpd_private-get-donau-instances.h b/src/backend/taler-merchant-httpd_private-get-donau-instances.h
@@ -0,0 +1,42 @@
+/*
+ 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 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-donau-instances.h
+ * @brief implementation of GET /donau
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+
+#ifndef TALER_MERCHANT_HTTPD_GET_DONAU_INSTANCES_H
+#define TALER_MERCHANT_HTTPD_GET_DONAU_INSTANCES_H
+
+#include <microhttpd.h>
+#include "taler-merchant-httpd.h"
+
+/**
+ * Handle a GET "/donau" 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_donau_instances (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-get-incoming.c b/src/backend/taler-merchant-httpd_private-get-incoming.c
@@ -33,7 +33,7 @@
* @param wtid wire transfer identifier
* @param payto_uri target account that received the wire transfer
* @param exchange_url base URL of the exchange that made the wire transfer
- * @param transfer_serial_id serial number identifying the transfer in the backend
+ * @param expected_transfer_serial_id serial number identifying the transfer in the backend
* @param execution_time when did the exchange make the transfer, #GNUNET_TIME_UNIT_FOREVER_ABS
* if it did not yet happen
* @param confirmed true if the merchant acknowledged the wire transfer reception
diff --git a/src/backend/taler-merchant-httpd_private-get-instances-ID-tokens.c b/src/backend/taler-merchant-httpd_private-get-instances-ID-tokens.c
@@ -26,8 +26,11 @@
* Add token details to our JSON array.
*
* @param cls a `json_t *` JSON array to build
- * @param product_serial serial (row) number of the product in the database
- * @param product_id ID of the product
+ * @param creation_time when the token was created
+ * @param expiration_time when the token will expire
+ * @param scope internal scope identifier for the token (mapped to string)
+ * @param description human-readable purpose or context of the token
+ * @param serial serial (row) number of the product in the database
*/
static void
add_token (void *cls,
diff --git a/src/backend/taler-merchant-httpd_private-get-statistics-amount-SLUG.c b/src/backend/taler-merchant-httpd_private-get-statistics-amount-SLUG.c
@@ -32,8 +32,8 @@
* @param bucket_start start time of the bucket
* @param bucket_end end time of the bucket
* @param bucket_range range of the bucket
- * @param cumulative_amounts_len the length of @a cumulative_amounts
- * @param cumulative_amounts the cumulative amounts array
+ * @param amounts_len the length of @a cumulative_amounts
+ * @param amounts the cumulative amounts array
*/
static void
amount_by_bucket (void *cls,
@@ -97,9 +97,9 @@ amount_by_bucket (void *cls,
*
* @param cls a `json_t *` JSON array to build
* @param description description of the statistic
- * @param interval_start start time of the bucket
- * @param cumulative_amounts_len the length of @a cumulative_amounts
- * @param cumulative_amounts the cumulative amounts array
+ * @param bucket_start start time of the bucket
+ * @param amounts_len the length of @a cumulative_amounts
+ * @param amounts the cumulative amounts array
*/
static void
amount_by_interval (void *cls,
diff --git a/src/backend/taler-merchant-httpd_private-get-statistics-counter-SLUG.c b/src/backend/taler-merchant-httpd_private-get-statistics-counter-SLUG.c
@@ -33,7 +33,7 @@
* @param bucket_start start time of the bucket
* @param bucket_end end time of the bucket
* @param bucket_range range of the bucket
- * @param cumulative_counter counter value
+ * @param cumulative_number counter value
*/
static void
counter_by_bucket (void *cls,
@@ -86,8 +86,8 @@ counter_by_bucket (void *cls,
*
* @param cls a `json_t *` JSON array to build
* @param description description of the statistic
- * @param interval_start start time of the interval
- * @param cumulative_counter counter value
+ * @param bucket_start start time of the interval
+ * @param cumulative_number counter value
*/
static void
counter_by_interval (void *cls,
diff --git a/src/backend/taler-merchant-httpd_private-get-transfers.c b/src/backend/taler-merchant-httpd_private-get-transfers.c
@@ -30,14 +30,14 @@
* Generate a response (array entry) based on the given arguments.
*
* @param cls closure with a `json_t *` array to build up the response
- * @param expected_credit_amount amount expected to be wired to the merchant (minus fees), NULL if unknown
+ * @param credit_amount amount expected to be wired to the merchant (minus fees), NULL if unknown
* @param wtid wire transfer identifier
* @param payto_uri target account that received the wire transfer
* @param exchange_url base URL of the exchange that made the wire transfer
* @param transfer_serial_id serial number identifying the transfer in the backend
* @param execution_time when did the exchange make the transfer, #GNUNET_TIME_UNIT_FOREVER_ABS
* if it did not yet happen
- * @param confirmed true if the merchant acknowledged the wire transfer reception
+ * @param expected true if the merchant acknowledged the wire transfer reception
*/
static void
transfer_cb (void *cls,
diff --git a/src/backend/taler-merchant-httpd_private-post-donau-instance.c b/src/backend/taler-merchant-httpd_private-post-donau-instance.c
@@ -0,0 +1,270 @@
+/*
+ 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 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-donau-instance.c
+ * @brief implementation of POST /donau
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+
+#include "platform.h"
+#include <jansson.h>
+#include "donau/donau_service.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_dbevents.h>
+#include "taler_merchant_service.h"
+#include "taler-merchant-httpd_private-post-donau-instance.h"
+
+/**
+ * Context for the POST /donau request handler.
+ */
+struct PostDonauCtx
+{
+ /**
+ * Stored in a DLL.
+ */
+ struct PostDonauCtx *next;
+
+ /**
+ * Stored in a DLL.
+ */
+ struct PostDonauCtx *prev;
+
+ /**
+ * Connection to the MHD server
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Context of the request handler.
+ */
+ struct TMH_HandlerContext *hc;
+
+ /**
+ * URL of the DONAU service
+ * to which the charity belongs.
+ */
+ const char *donau_url;
+
+ /**
+ * ID of the charity in the DONAU service.
+ */
+ uint64_t charity_id;
+
+ /**
+ * Data that comes back from Donau
+ */
+ struct DONAU_Charity charity;
+
+ /**
+ * Handle returned by DONAU_charities_get(); needed to cancel on
+ * connection abort, etc. */
+ struct DONAU_CharityGetHandle *get_handle;
+};
+
+/**
+ * Head of active pay context DLL.
+ */
+static struct PostDonauCtx *pdc_head;
+
+/**
+ * Tail of active pay context DLL.
+ */
+static struct PostDonauCtx *pdc_tail;
+
+/**
+ * Callback for DONAU_charities_get() to handle the response.
+ *
+ * @param cls closure with PostDonauCtx
+ * @param gcr response from Donau
+ */
+static void
+donau_charity_get_cb (void *cls,
+ const struct DONAU_GetCharityResponse *gcr)
+{
+ struct PostDonauCtx *pdc = cls;
+ pdc->get_handle = NULL;
+ if (NULL == pdc->connection)
+ return;
+
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Processing DONAU charity get response");
+
+ /* Anything but 200 => propagate Donau’s response. */
+ if (MHD_HTTP_OK != gcr->hr.http_status)
+ {
+ TALER_MHD_reply_with_error (pdc->connection,
+ gcr->hr.http_status,
+ gcr->hr.ec,
+ gcr->hr.hint);
+ MHD_resume_connection (cls);
+ TALER_MHD_daemon_trigger ();
+ return;
+ }
+
+ pdc->charity = gcr->details.ok.charity;
+
+ if (GNUNET_memcmp (&pdc->charity.charity_pub.eddsa_pub,
+ &pdc->hc->instance->merchant_pub.eddsa_pub))
+ {
+ TALER_MHD_reply_with_error (pdc->connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "Merchant public key of this instance and charity public key received "
+ "from DONAU for this charity_id do not match");
+ MHD_resume_connection (pdc->connection);
+ TALER_MHD_daemon_trigger ();
+ return;
+ }
+
+ enum GNUNET_DB_QueryStatus qs =
+ TMH_db->insert_donau_instance (TMH_db->cls,
+ pdc->donau_url,
+ &pdc->charity,
+ pdc->charity_id);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ TALER_MHD_reply_with_error (pdc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_donau_instance: Failed to insert Donau instance");
+ break;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ TALER_MHD_reply_with_error (pdc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ "insert_donau_instance: Database operation failed");
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ {
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = htons (sizeof (es)),
+ .type = htons (TALER_DBEVENT_MERCHANT_DONAU_KEYS)
+ };
+ TMH_db->event_notify (TMH_db->cls,
+ &es,
+ pdc->donau_url,
+ strlen (pdc->donau_url) + 1);
+ TALER_MHD_reply_static (pdc->connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ break;
+ }
+ }
+ MHD_resume_connection (pdc->connection);
+ TALER_MHD_daemon_trigger ();
+}
+
+
+/**
+ * Cleanup function for the PostDonauCtx.
+ *
+ * @param cls closure with PostDonauCtx
+ */
+static void
+post_donau_cleanup (void *cls)
+{
+ struct PostDonauCtx *pdc = cls;
+
+ if (pdc->get_handle)
+ DONAU_charity_get_cancel (pdc->get_handle);
+
+ GNUNET_CONTAINER_DLL_remove (pdc_head,
+ pdc_tail,
+ pdc);
+ GNUNET_free (pdc);
+}
+
+
+/**
+ * Handle a POST "/donau" 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_donau_instance (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc)
+{
+ const char *donau_url = NULL;
+ uint64_t charity_id = -1;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("donau_url",
+ &donau_url),
+ GNUNET_JSON_spec_uint64 ("charity_id",
+ &charity_id),
+ GNUNET_JSON_spec_end ()
+ };
+ if (GNUNET_OK != TALER_MHD_parse_json_data (connection,
+ hc->request_body,
+ spec))
+ {
+ return MHD_NO;
+ }
+ if (NULL == donau_url
+ || -1 == charity_id)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "Missing donau_url or charity_id");
+ }
+
+ struct PostDonauCtx *pdc = hc->ctx;
+ if (NULL == pdc)
+ {
+ pdc = GNUNET_new (struct PostDonauCtx);
+ pdc->connection = connection;
+ pdc->hc = hc;
+ pdc->donau_url = donau_url;
+ pdc->charity_id = charity_id;
+ hc->ctx = pdc;
+ hc->cc = &post_donau_cleanup;
+ GNUNET_CONTAINER_DLL_insert (pdc_head,
+ pdc_tail,
+ pdc);
+
+ pdc->get_handle =
+ DONAU_charity_get (TMH_curl_ctx,
+ donau_url,
+ charity_id,
+ NULL, /* bearer */
+ &donau_charity_get_cb,
+ pdc);
+
+ if (NULL == pdc->get_handle)
+ {
+ GNUNET_free (pdc);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_SERVICE_UNAVAILABLE,
+ TALER_EC_GENERIC_ALLOCATION_FAILURE,
+ "Failed to initiate Donau lookup");
+ }
+
+ MHD_suspend_connection (connection);
+ return MHD_YES;
+ }
+ return MHD_YES;
+}
diff --git a/src/backend/taler-merchant-httpd_private-post-donau-instance.h b/src/backend/taler-merchant-httpd_private-post-donau-instance.h
@@ -0,0 +1,41 @@
+/*
+ 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 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-donau-instance.h
+ * @brief implementation of POST /donau
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+
+#ifndef TALER_MERCHANT_HTTPD_POST_DONAU_INSTANCE_H
+#define TALER_MERCHANT_HTTPD_POST_DONAU_INSTANCE_H
+
+#include "taler-merchant-httpd.h"
+
+/**
+ * Handle a POST "/donau" 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_donau_instance (const struct TMH_RequestHandler *rh,
+ struct MHD_Connection *connection,
+ struct TMH_HandlerContext *hc);
+
+#endif
diff --git a/src/backend/taler-merchant-httpd_private-post-orders.c b/src/backend/taler-merchant-httpd_private-post-orders.c
@@ -2159,7 +2159,7 @@ add_output_token_family (struct OrderContext *oc,
* Build JSON array that represents all of the token families
* in the contract.
*
- * @param[in] v1-style order
+ * @param[in] oc v1-style order context
* @return JSON array with token families for the contract
*/
static json_t *
@@ -2191,7 +2191,7 @@ output_token_families (struct OrderContext *oc)
* Build JSON array that represents all of the contract choices
* in the contract.
*
- * @param[in] v1-style order
+ * @param[in] oc v1-style order context
* @return JSON array with token families for the contract
*/
static json_t *
@@ -2485,7 +2485,7 @@ phase_set_max_fee (struct OrderContext *oc)
*
* @param wmc wire method candidate to check
* @param brutto amount to check
- * @param true if the amount is OK, false if it is too high
+ * @return true if the amount is OK, false if it is too high
*/
static bool
check_limits (struct WireMethodCandidate *wmc,
@@ -2656,7 +2656,7 @@ resume_with_keys (struct OrderContext *oc)
* @a stefan_fee. Note that @a stefan_fee is updated to the maximum
* of the input and the computed fee.
*
- * @param[in,out] oc order context
+ * @param[in,out] keys exchange keys
* @param brutto some brutto amount the client is to pay
* @param[in,out] stefan_fee set to STEFAN fee to be paid by the merchant
*/
@@ -3324,6 +3324,58 @@ phase_merge_inventory (struct OrderContext *oc)
/* ***************** ORDER_PHASE_PARSE_CHOICES **************** */
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+/**
+ * Callback function that is called for each donau instance.
+ * It simply adds the provided donau_url to the json.
+ *
+ * @param cls closure with our `struct TALER_MERCHANT_ContractOutput *`
+ * @param donau_url the URL of the donau instance
+ */
+static void
+add_donau_url (void *cls,
+ const char *donau_url)
+{
+ struct TALER_MERCHANT_ContractOutput *output = cls;
+
+ GNUNET_array_append (output->details.donation_receipt.donau_urls,
+ output->details.donation_receipt.donau_urls_len,
+ GNUNET_strdup (donau_url));
+}
+
+
+/**
+ * Add the donau output to the contract output.
+ *
+ * @param oc order context
+ * @param output contract output to add donau URLs to
+ */
+static bool
+add_donau_output (struct OrderContext *oc,
+ struct TALER_MERCHANT_ContractOutput *output)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TMH_db->select_donau_instances_filtered (
+ TMH_db->cls,
+ output->details.donation_receipt.amount.currency,
+ &add_donau_url,
+ output);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ reply_with_error (oc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "donau url parsing db call");
+ return false;
+ }
+ return true;
+}
+
+
+#endif
+
/**
* Parse contract choices. Upon success, continue
* processing with merge_inventory().
@@ -3527,8 +3579,20 @@ phase_parse_choices (struct OrderContext *oc)
GNUNET_assert (0);
break;
case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_DONATION_RECEIPT:
- GNUNET_break (0); /* FIXME-#9059: not yet implemented! */
- break;
+ output.details.donation_receipt.amount = choice->amount;
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+ if (! add_donau_output (oc,
+ &output))
+ {
+ GNUNET_break (0);
+ return;
+ }
+#endif
+
+ GNUNET_array_append (choice->outputs,
+ choice->outputs_len,
+ output);
+ continue;
case TALER_MERCHANT_CONTRACT_OUTPUT_TYPE_TOKEN:
/* Ignore inputs tokens with 'count' field set to 0 */
if (0 == output.details.token.count)
diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am
@@ -39,6 +39,7 @@ sql_DATA = \
merchant-0020.sql \
merchant-0021.sql \
merchant-0022.sql \
+ merchant-0023.sql \
drop.sql
BUILT_SOURCES = \
@@ -198,6 +199,7 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \
pg_update_category.h pg_update_category.c \
pg_update_contract_terms.h pg_update_contract_terms.c \
pg_update_deposit_confirmation_status.h pg_update_deposit_confirmation_status.c \
+ pg_update_donau_instance_receipts_amount.h pg_update_donau_instance_receipts_amount.c \
pg_update_instance.h pg_update_instance.c \
pg_update_instance_auth.h pg_update_instance_auth.c \
pg_update_otp.h pg_update_otp.c \
@@ -214,6 +216,22 @@ libtaler_plugin_merchantdb_postgres_la_SOURCES = \
pg_lookup_statistics_amount_by_bucket.h pg_lookup_statistics_amount_by_bucket.c \
pg_lookup_statistics_amount_by_interval.h pg_lookup_statistics_amount_by_interval.c \
plugin_merchantdb_postgres.c
+
+if HAVE_DONAU
+libtaler_plugin_merchantdb_postgres_la_SOURCES += \
+ pg_lookup_donau_keys.h pg_lookup_donau_keys.c \
+ pg_lookup_order_charity.h pg_lookup_order_charity.c \
+ pg_upsert_donau_keys.h pg_upsert_donau_keys.c \
+ pg_insert_donau_instance.h pg_insert_donau_instance.c \
+ pg_insert_order_blinded_sigs.h pg_insert_order_blinded_sigs.c \
+ pg_select_donau_instance_by_serial.h pg_select_donau_instance_by_serial.c \
+ pg_select_donau_instances.h pg_select_donau_instances.c \
+ pg_select_donau_instances_filtered.h pg_select_donau_instances_filtered.c \
+ pg_select_order_blinded_sigs.h pg_select_order_blinded_sigs.c \
+ pg_delete_donau_instance.h pg_delete_donau_instance.c \
+ pg_update_donau_instance.h pg_update_donau_instance.c
+endif
+
libtaler_plugin_merchantdb_postgres_la_LIBADD = \
$(LTLIBINTL)
libtaler_plugin_merchantdb_postgres_la_LDFLAGS = \
@@ -229,6 +247,11 @@ libtaler_plugin_merchantdb_postgres_la_LDFLAGS = \
-lgnunetutil \
$(XLIB)
+if HAVE_DONAU
+libtaler_plugin_merchantdb_postgres_la_LDFLAGS += \
+ -ldonau
+endif
+
if HAVE_POSTGRESQL
if HAVE_GNUNETPQ
check_PROGRAMS = \
diff --git a/src/backenddb/merchant-0023.sql b/src/backenddb/merchant-0023.sql
@@ -0,0 +1,98 @@
+--
+-- 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/>
+--
+
+-- @file merchant-0023.sql
+-- @brief Create table to store donau related information
+-- @author Bohdan Potuzhnyi
+-- @author Vlada Svirsh
+
+BEGIN;
+
+-- Check patch versioning is in place.
+SELECT _v.register_patch('merchant-0023', NULL, NULL);
+
+SET search_path TO merchant;
+
+CREATE TABLE IF NOT EXISTS merchant_donau_keys
+(donau_keys_serial BIGINT GENERATED BY DEFAULT AS IDENTITY UNIQUE
+ ,donau_url TEXT PRIMARY KEY
+ ,keys_json TEXT NOT NULL
+ ,first_retry INT8 NOT NULL DEFAULT (0)
+);
+
+COMMENT ON TABLE merchant_donau_keys
+ IS 'Here we store the cached /keys response from Donau in JSON format';
+COMMENT ON COLUMN merchant_donau_keys.donau_keys_serial
+ IS 'Unique serial identifier for each cached key entry';
+COMMENT ON COLUMN merchant_donau_keys.donau_url
+ IS 'Base URL of Donau associated with these keys';
+COMMENT ON COLUMN merchant_donau_keys.keys_json
+ IS 'JSON string of the /keys as generated by Donau';
+COMMENT ON COLUMN merchant_donau_keys.first_retry
+ IS 'Absolute time when this merchant may retry to fetch the keys from this donau at the earliest';
+
+CREATE TABLE IF NOT EXISTS merchant_donau_instances
+(donau_instances_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
+ ,donau_url TEXT NOT NULL
+ ,charity_name TEXT NOT NULL
+ ,merchant_instance_serial INT8 NOT NULL
+ REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
+ ,charity_id BIGINT NOT NULL
+ ,charity_max_per_year taler_amount_currency NOT NULL
+ ,charity_receipts_to_date taler_amount_currency NOT NULL
+ ,current_year INT8 NOT NULL
+);
+
+COMMENT ON TABLE merchant_donau_instances
+ IS 'Here we store information about individual Donau instances, including details about associated charities and donation limits';
+COMMENT ON COLUMN merchant_donau_instances.donau_instances_serial
+ IS 'Unique serial identifier for each Donau instance';
+COMMENT ON COLUMN merchant_donau_instances.donau_url
+ IS 'The URL associated with the Donau system for this instance';
+COMMENT ON COLUMN merchant_donau_instances.merchant_instance_serial
+ IS 'The serial from merchant_instances whose public key is public key of the charity organization';
+COMMENT ON COLUMN merchant_donau_instances.charity_id
+ IS 'The unique identifier for the charity organization linked to this Donau instance';
+COMMENT ON COLUMN merchant_donau_instances.charity_max_per_year
+ IS 'Maximum allowable donation amount per year for the charity associated with this instance, stored in taler_amount_currency';
+COMMENT ON COLUMN merchant_donau_instances.charity_receipts_to_date
+ IS 'The total amount of donations received to date for this instance, stored in taler_amount_currency';
+COMMENT ON COLUMN merchant_donau_instances.current_year
+ IS 'The current year for tracking donations for this instance, stored as an 8-byte integer';
+
+CREATE TABLE IF NOT EXISTS merchant_order_token_blinded_sigs
+(order_token_bs_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
+ ,order_serial BIGINT NOT NULL
+ REFERENCES merchant_contract_terms (order_serial) ON DELETE CASCADE
+ ,token_index INT4 NOT NULL
+ ,token_blinded_signature BYTEA NOT NULL
+ ,token_hash BYTEA NOT NULL CHECK (LENGTH(token_hash)=64)
+ ,PRIMARY KEY (order_serial, token_index)
+);
+
+COMMENT ON TABLE merchant_order_token_blinded_sigs
+ IS 'Table linking merchant orders with Donau BUDIS information';
+COMMENT ON COLUMN merchant_order_token_blinded_sigs.token_index
+ IS 'offset of the given signature in the output token array';
+COMMENT ON COLUMN merchant_order_token_blinded_sigs.order_token_bs_serial
+ IS 'Unique serial identifier for token order linkage';
+COMMENT ON COLUMN merchant_order_token_blinded_sigs.order_serial
+ IS 'Foreign key linking to the corresponding merchant order';
+COMMENT ON COLUMN merchant_order_token_blinded_sigs.token_blinded_signature
+ IS 'Blinded signature of the token associated with the order';
+COMMENT ON COLUMN merchant_order_token_blinded_sigs.token_hash
+ IS 'Hash of the token';
+COMMIT;
diff --git a/src/backenddb/pg_delete_donau_instance.c b/src/backenddb/pg_delete_donau_instance.c
@@ -0,0 +1,53 @@
+/*
+ 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_donau_instance.c
+ * @brief Implementation of the delete_donau_instance function for Postgres
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_delete_donau_instance.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_donau_instance (
+ void *cls,
+ const char *id,
+ const uint64_t donau_serial_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&donau_serial_id),
+ GNUNET_PQ_query_param_string (id),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "delete_donau_instance",
+ "DELETE FROM merchant_donau_instances di"
+ " USING merchant_instances mi"
+ " WHERE di.merchant_instance_serial = mi.merchant_serial"
+ " AND di.donau_instances_serial = $1"
+ " AND mi.merchant_id = $2;");
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "delete_donau_instance",
+ params);
+}
+\ No newline at end of file
diff --git a/src/backenddb/pg_delete_donau_instance.h b/src/backenddb/pg_delete_donau_instance.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_donau_instance.h
+ * @brief implementation of the delete_donau_instance function for Postgres
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+#ifndef PG_DELETE_DONAU_INSTANCE_H
+#define PG_DELETE_DONAU_INSTANCE_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+/**
+ * Delete an existing Donau charity instance from the database.
+ *
+ * @param cls closure
+ * @param id merchant instance id
+ * @param charity_id unique identifier of the charity instance to be deleted
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_delete_donau_instance (
+ void *cls,
+ const char *id,
+ const uint64_t charity_id);
+
+#endif
diff --git a/src/backenddb/pg_insert_donau_instance.c b/src/backenddb/pg_insert_donau_instance.c
@@ -0,0 +1,97 @@
+/*
+ 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_donau_instance.c
+ * @brief Implementation of the insert_donau_instance function for Postgres
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "donau/donau_service.h"
+#include "pg_insert_donau_instance.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_donau_instance (
+ void *cls,
+ const char *donau_url,
+ const struct DONAU_Charity *charity,
+ const uint64_t charity_id)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (donau_url),
+ GNUNET_PQ_query_param_string (charity->name),
+ GNUNET_PQ_query_param_auto_from_type (&charity->charity_pub),
+ GNUNET_PQ_query_param_uint64 (&charity_id),
+ TALER_PQ_query_param_amount_with_currency (pg->conn,
+ &charity->max_per_year),
+ TALER_PQ_query_param_amount_with_currency (pg->conn,
+ &charity->receipts_to_date),
+ GNUNET_PQ_query_param_uint64 (&charity->current_year),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+
+ PREPARE (pg,
+ "insert_donau_instance",
+ "INSERT INTO merchant_donau_instances"
+ " (donau_url"
+ " ,charity_name"
+ " ,merchant_instance_serial"
+ " ,charity_id"
+ " ,charity_max_per_year"
+ " ,charity_receipts_to_date"
+ " ,current_year)"
+ "VALUES"
+ " ($1, $2,"
+ " (SELECT merchant_serial"
+ " FROM merchant_instances mi"
+ " WHERE mi.merchant_pub = $3),"
+ " $4, $5, $6, $7);");
+
+ PREPARE (pg,
+ "update_donau_instance",
+ "UPDATE merchant_donau_instances SET"
+ " charity_name = $2,"
+ " merchant_instance_serial = (SELECT merchant_serial"
+ " FROM merchant_instances mi"
+ " WHERE mi.merchant_pub = $3),"
+ " charity_max_per_year = $5,"
+ " charity_receipts_to_date = $6,"
+ " current_year = $7"
+ " WHERE"
+ " charity_id = $4"
+ " AND donau_url = $1;");
+
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_donau_instance",
+ params);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_donau_instance",
+ params);
+ }
+
+ return qs;
+}
diff --git a/src/backenddb/pg_insert_donau_instance.h b/src/backenddb/pg_insert_donau_instance.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_donau_instance.h
+ * @brief implementation of the insert_donau_instance function for Postgres
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+#ifndef PG_INSERT_DONAU_INSTANCE_H
+#define PG_INSERT_DONAU_INSTANCE_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+#include "donau/donau_service.h"
+
+/**
+ * Insert information about a Donau charity instance.
+ *
+ * @param cls closure
+ * @param donau_url URL of the Donau
+ * @param charity structure containing information about the charity
+ * @param charity_id unique identifier for the charity
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_donau_instance (
+ void *cls,
+ const char *donau_url,
+ const struct DONAU_Charity *charity,
+ const uint64_t charity_id);
+
+#endif
diff --git a/src/backenddb/pg_insert_login_token.h b/src/backenddb/pg_insert_login_token.h
@@ -35,6 +35,7 @@
* @param creation_time the current time
* @param expiration_time when does the token expire
* @param validity_scope scope of the token
+ * @param description human-readable description of the token
* @return database result code
*/
enum GNUNET_DB_QueryStatus
diff --git a/src/backenddb/pg_insert_order_blinded_sigs.c b/src/backenddb/pg_insert_order_blinded_sigs.c
@@ -0,0 +1,61 @@
+/*
+ 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/>
+ */
+/**
+ * @file backenddb/pg_insert_order_blinded_sigs.c
+ * @brief implementation of insert_order_blinded_sigs() for Postgres
+ * @author Bohdan Potuzhnyi
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_insert_order_blinded_sigs.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_order_blinded_sigs (
+ void *cls,
+ const char *order_id,
+ uint32_t i,
+ const struct GNUNET_HashCode *hash,
+ const struct GNUNET_CRYPTO_BlindedSignature *blind_sig)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (order_id),
+ GNUNET_PQ_query_param_uint32 (&i),
+ GNUNET_PQ_query_param_auto_from_type (hash),
+ GNUNET_PQ_query_param_blinded_sig (blind_sig),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "insert_blinded_sigs",
+ "INSERT INTO merchant_order_token_blinded_sigs"
+ " (order_serial"
+ " ,token_index"
+ " ,token_hash"
+ " ,token_blinded_signature"
+ ")"
+ " SELECT order_serial, $2, $3, $4"
+ " FROM merchant_contract_terms"
+ " WHERE order_id = $1");
+
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_blinded_sigs",
+ params);
+}
diff --git a/src/backenddb/pg_insert_order_blinded_sigs.h b/src/backenddb/pg_insert_order_blinded_sigs.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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_order_blinded_sigs.h
+ * @brief Implementation of the insert blinded sigs by order_id function for Postgres
+ * @author Bohdan Potuzhnyi
+ */
+#ifndef PG_INSERT_BLIND_SIGS_ORD_H
+#define PG_INSERT_BLIND_SIGS_ORD_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Store (or update) the Donau *blinded* signature JSON for an order.
+ *
+ * @param cls closure (our `struct PostgresClosure *`)
+ * @param order_id business-level order identifier
+ * @param i index of the @a blinded_sig in the output token array
+ * @param hash hash of the @a blinded_sig
+ * @param blind_sig blinded signature for the token outputs
+ * @return GNUNET_DB status code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_insert_order_blinded_sigs (
+ void *cls,
+ const char *order_id,
+ uint32_t i,
+ const struct GNUNET_HashCode *hash,
+ const struct GNUNET_CRYPTO_BlindedSignature *blind_sig);
+
+#endif
diff --git a/src/backenddb/pg_lookup_donau_keys.c b/src/backenddb/pg_lookup_donau_keys.c
@@ -0,0 +1,73 @@
+/*
+ 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_donau_keys.c
+ * @brief Implementation of the lookup_donau_keys function for Postgres
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "donau/donau_service.h"
+#include "pg_lookup_donau_keys.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_donau_keys (void *cls,
+ const char *donau_url,
+ struct GNUNET_TIME_Absolute *first_retry,
+ struct DONAU_Keys **keys)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (donau_url),
+ GNUNET_PQ_query_param_end
+ };
+ json_t *jkeys;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_absolute_time ("first_retry",
+ first_retry),
+ TALER_PQ_result_spec_json ("keys_json",
+ &jkeys),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_donau_keys",
+ "SELECT"
+ " first_retry"
+ ",keys_json"
+ " FROM merchant_donau_keys"
+ " WHERE donau_url=$1;");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_donau_keys",
+ params,
+ rs);
+ if (qs <= 0)
+ return qs;
+ *keys = DONAU_keys_from_json (jkeys);
+ json_decref (jkeys);
+ if (NULL == *keys)
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ return qs;
+}
+\ No newline at end of file
diff --git a/src/backenddb/pg_lookup_donau_keys.h b/src/backenddb/pg_lookup_donau_keys.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_lookup_donau_keys.h
+ * @brief implementation of the lookup_donau_keys function for Postgres
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+#ifndef PG_LOOKUP_DONAU_KEYS_H
+#define PG_LOOKUP_DONAU_KEYS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+#include "donau/donau_service.h"
+
+/**
+ * Retrieve donau's keys from the database.
+ *
+ * @param cls plugin closure
+ * @param donau_url base URL of the exchange
+ * @param first_retry if the query fails, this is set to the
+ * time when the next retry should be attempted
+ * @param[out] keys set to the keys of the exchange
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_donau_keys (void *cls,
+ const char *donau_url,
+ struct GNUNET_TIME_Absolute *first_retry,
+ struct DONAU_Keys **keys);
+
+
+#endif
diff --git a/src/backenddb/pg_lookup_order_charity.c b/src/backenddb/pg_lookup_order_charity.c
@@ -0,0 +1,95 @@
+/*
+ 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/>
+ */
+/**
+ * @file backenddb/pg_lookup_order_charity.c
+ * @brief Implementation for retrieving Donau charity_id and corresponding private key
+ * by Donau URL.
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "donau/donau_service.h"
+#include "pg_lookup_order_charity.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_order_charity (
+ void *cls,
+ const char *instance_id,
+ const char *donau_url,
+ uint64_t *charity_id,
+ struct DONAU_CharityPrivateKeyP *charity_priv,
+ struct TALER_Amount *charity_max_per_year,
+ struct TALER_Amount *charity_receipts_to_date,
+ json_t **donau_keys_json,
+ uint64_t *donau_instance_serial)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (donau_url),
+ GNUNET_PQ_query_param_end
+ };
+
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("charity_id",
+ charity_id),
+ GNUNET_PQ_result_spec_auto_from_type ("merchant_priv",
+ charity_priv),
+ TALER_PQ_result_spec_json ("keys_json",
+ donau_keys_json),
+ TALER_PQ_result_spec_amount_with_currency ("charity_max_per_year",
+ charity_max_per_year),
+ TALER_PQ_result_spec_amount_with_currency ("charity_receipts_to_date",
+ charity_receipts_to_date),
+ GNUNET_PQ_result_spec_uint64 ("donau_instances_serial",
+ donau_instance_serial),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "lookup_donau_charity",
+ "SELECT"
+ " di.donau_instances_serial"
+ " ,di.charity_id"
+ " ,k.merchant_priv"
+ " ,dk.keys_json"
+ " ,di.charity_max_per_year"
+ " ,di.charity_receipts_to_date"
+ " FROM merchant_donau_instances di"
+ " JOIN merchant_donau_keys dk"
+ " ON dk.donau_url = di.donau_url"
+ " JOIN merchant_instances mi"
+ " ON mi.merchant_serial = di.merchant_instance_serial"
+ " JOIN merchant_keys k"
+ " ON k.merchant_serial = mi.merchant_serial"
+ " WHERE mi.merchant_id = $1"
+ " AND di.donau_url = $2;");
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "lookup_donau_charity",
+ params,
+ rs);
+}
+\ No newline at end of file
diff --git a/src/backenddb/pg_lookup_order_charity.h b/src/backenddb/pg_lookup_order_charity.h
@@ -0,0 +1,65 @@
+/*
+ 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/>
+ */
+/**
+ * @file backenddb/pg_lookup_order_charity.h
+ * @brief Header for retrieving Donau charity_id and corresponding private key
+ * by Donau URL.
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+
+#ifndef PG_LOOKUP_ORDER_CHARITY_H
+#define PG_LOOKUP_ORDER_CHARITY_H
+
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "donau/donau_service.h"
+#include "pg_helper.h"
+
+
+/**
+ * Retrieve the charity’s ID and private key for a given Donau URL.
+ *
+ * @param cls plugin closure (the Postgres DB handle / struct PostgresClosure *)
+ * @param instance_id the instance ID of the merchant
+ * @param donau_url base URL of the Donau instance
+ * @param[out] charity_id set to the `charity_id` from `merchant_donau_instances`
+ * @param[out] charity_priv set to the private key (32 bytes)
+ * @param[out] charity_max_per_year set to the maximum amount that can be donated
+ * per year to this charity
+ * @param[out] charity_receipts_to_date set to the total amount of receipts
+ * issued to date
+ * @param[out] donau_keys_json set to the JSON object containing the Donau keys
+ * @param[out] donau_instance_serial set to the serial number of the Donau instance in DB
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_lookup_order_charity (
+ void *cls,
+ const char *instance_id,
+ const char *donau_url,
+ uint64_t *charity_id,
+ struct DONAU_CharityPrivateKeyP *charity_priv,
+ struct TALER_Amount *charity_max_per_year,
+ struct TALER_Amount *charity_receipts_to_date,
+ json_t **donau_keys_json,
+ uint64_t *donau_instance_serial);
+
+#endif /* PG_LOOKUP_ORDER_CHARITY_H */
+\ No newline at end of file
diff --git a/src/backenddb/pg_select_donau_instance_by_serial.c b/src/backenddb/pg_select_donau_instance_by_serial.c
@@ -0,0 +1,60 @@
+/*
+ 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_donau_instance_by_serial.c
+ * @brief Implementation of the select_donau_instance function for Postgres
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_select_donau_instance_by_serial.h"
+#include "taler_merchant_donau.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_donau_instance_by_serial (void *cls,
+ uint64_t serial,
+ char **donau_url,
+ uint64_t *charity_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&serial),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("donau_url", donau_url),
+ GNUNET_PQ_result_spec_uint64 ("charity_id", charity_id),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "select_donau_instance_by_serial",
+ "SELECT donau_url, charity_id"
+ " FROM merchant_donau_instances"
+ " WHERE donau_instances_serial = $1");
+
+ return GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "select_donau_instance_by_serial",
+ params,
+ rs);
+}
+\ No newline at end of file
diff --git a/src/backenddb/pg_select_donau_instance_by_serial.h b/src/backenddb/pg_select_donau_instance_by_serial.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_donau_instance_by_serial.h
+ * @brief implementation of the select_donau_instance function for Postgres
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+#ifndef PG_SELECT_DONAU_INSTANCE_SERIAL_H
+#define PG_SELECT_DONAU_INSTANCE_SERIAL_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+#include "taler_merchant_donau.h"
+#include "donau/donau_util.h"
+
+/**
+ * Select Donau instance from the database by serial.
+ *
+ * @param cls the closure for the database context
+ * @param serial the serial of the Donau instance to select
+ * @param[out] donau_url Donau URL
+ * @param[out] charity_id charity ID
+ * @return status of the PG
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_donau_instance_by_serial (void *cls,
+ uint64_t serial,
+ char **donau_url,
+ uint64_t *charity_id);
+
+#endif
diff --git a/src/backenddb/pg_select_donau_instances.c b/src/backenddb/pg_select_donau_instances.c
@@ -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/>
+ */
+/**
+ * @file backenddb/pg_select_donau_instances.c
+ * @brief Implementation of the select_donau_instance function for Postgres
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_select_donau_instances.h"
+#include "taler_merchant_donau.h"
+#include "pg_helper.h"
+
+/**
+ * Context for select_donau_instances().
+ */
+struct SelectDonauInstanceContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_DonauInstanceCallback cb;
+
+ /**
+ * Closure for @e 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 donau instances.
+ *
+ * @param[in, out] cls of type `struct SelectDonauInstanceContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+select_donau_instance_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct SelectDonauInstanceContext *sdc = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ uint64_t donau_instance_serial;
+ char *donau_url;
+ char *charity_name;
+ struct DONAU_CharityPublicKeyP charity_pub_key;
+ uint64_t charity_id;
+ struct TALER_Amount charity_max_per_year;
+ struct TALER_Amount charity_receipts_to_date;
+ int64_t current_year;
+ json_t *donau_keys_json;
+
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("donau_instances_serial",
+ &donau_instance_serial),
+ GNUNET_PQ_result_spec_string ("donau_url",
+ &donau_url),
+ GNUNET_PQ_result_spec_string ("charity_name",
+ &charity_name),
+ GNUNET_PQ_result_spec_auto_from_type ("charity_pub_key",
+ &charity_pub_key),
+ GNUNET_PQ_result_spec_uint64 ("charity_id",
+ &charity_id),
+ TALER_PQ_result_spec_amount_with_currency ("charity_max_per_year",
+ &charity_max_per_year),
+ TALER_PQ_result_spec_amount_with_currency ("charity_receipts_to_date",
+ &charity_receipts_to_date),
+ GNUNET_PQ_result_spec_int64 ("current_year",
+ ¤t_year),
+ TALER_PQ_result_spec_json ("keys_json",
+ &donau_keys_json),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ sdc->extract_failed = true;
+ return;
+ }
+ sdc->cb (sdc->cb_cls,
+ donau_instance_serial,
+ donau_url,
+ charity_name,
+ &charity_pub_key,
+ charity_id,
+ &charity_max_per_year,
+ &charity_receipts_to_date,
+ current_year,
+ donau_keys_json);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_donau_instances (void *cls,
+ const char *id,
+ TALER_MERCHANTDB_DonauInstanceCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct SelectDonauInstanceContext sdc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ /* Can be overwritten by the select_donau_instance_cb */
+ .extract_failed = false,
+ };
+
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (id),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "select_donau_instances",
+ "SELECT"
+ " di.donau_instances_serial"
+ ",di.donau_url"
+ ",di.charity_name"
+ ",mi.merchant_pub AS charity_pub_key"
+ ",di.charity_id"
+ ",di.charity_max_per_year"
+ ",di.charity_receipts_to_date"
+ ",di.current_year"
+ ",dk.keys_json"
+ " FROM merchant_donau_instances di"
+ " JOIN merchant_donau_keys dk ON di.donau_url = dk.donau_url"
+ " JOIN merchant_instances mi ON di.merchant_instance_serial = mi.merchant_serial"
+ " WHERE mi.merchant_id = $1");
+
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "select_donau_instances",
+ params,
+ &select_donau_instance_cb,
+ &sdc);
+
+ /* If there was an error inside select_donau_instance_cb, return a hard error. */
+ if (sdc.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+
+ return qs;
+}
+\ No newline at end of file
diff --git a/src/backenddb/pg_select_donau_instances.h b/src/backenddb/pg_select_donau_instances.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_select_donau_instances.h
+ * @brief implementation of the select_donau_instance function for Postgres
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+#ifndef PG_SELECT_DONAU_INSTANCE_H
+#define PG_SELECT_DONAU_INSTANCE_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+#include "taler_merchant_donau.h"
+#include "donau/donau_util.h"
+
+/**
+ * Select multiple Donau instances from the database.
+ *
+ * @param cls the closure for the database context
+ * @param id the ID of the merchant instance
+ * @param cb callback function to call with each result
+ * @param cb_cls closure for the callback
+ * @return status of the PG
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_donau_instances (
+ void *cls,
+ const char *id,
+ TALER_MERCHANTDB_DonauInstanceCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_select_donau_instances_filtered.c b/src/backenddb/pg_select_donau_instances_filtered.c
@@ -0,0 +1,132 @@
+/*
+ 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/>
+ */
+/**
+ * @file backenddb/pg_select_donau_instances_filtered.c
+ * @brief Implementation of the select_donau_instance function for Postgres
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_select_donau_instances_filtered.h"
+#include "taler_merchant_donau.h"
+#include "pg_helper.h"
+
+/**
+ * Context for select_donau_instances_filtered().
+ */
+struct SelectDonauInstanceContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_DonauInstanceFilteredCallback cb;
+
+ /**
+ * Closure for @e 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 donau instances.
+ *
+ * @param[in, out] cls of type `struct SelectDonauInstanceContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+select_donau_instance_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct SelectDonauInstanceContext *sdc = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ char *donau_url;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_string ("donau_url",
+ &donau_url),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ sdc->extract_failed = true;
+ return;
+ }
+ sdc->cb (sdc->cb_cls,
+ donau_url);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_donau_instances_filtered (
+ void *cls,
+ const char *currency,
+ TALER_MERCHANTDB_DonauInstanceFilteredCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct SelectDonauInstanceContext sdc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ /* Can be overwritten by the select_donau_instance_cb */
+ .extract_failed = false,
+ };
+
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (currency),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ PREPARE (pg,
+ "select_donau_instances_filtered",
+ "SELECT"
+ " donau_url"
+ " FROM merchant_donau_instances"
+ " WHERE (charity_max_per_year).curr = $1");
+
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "select_donau_instances_filtered",
+ params,
+ &select_donau_instance_cb,
+ &sdc);
+
+ /* If there was an error inside select_donau_instance_cb, return a hard error. */
+ if (sdc.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+
+ return qs;
+}
diff --git a/src/backenddb/pg_select_donau_instances_filtered.h b/src/backenddb/pg_select_donau_instances_filtered.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_select_donau_instances_filtered.h
+ * @brief implementation of the select_donau_instance function for Postgres
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+#ifndef PG_SELECT_DONAU_INSTANCE_FILTERED_H
+#define PG_SELECT_DONAU_INSTANCE_FILTERED_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+#include "taler_merchant_donau.h"
+#include "donau/donau_util.h"
+
+/**
+ * Select multiple Donau instances from the database.
+ *
+ * @param cls the closure for the database context
+ * @param currency currency to filter donau instances by
+ * @param cb callback function to call with each result
+ * @param cb_cls closure for the callback
+ * @return status of the PG
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_donau_instances_filtered (
+ void *cls,
+ const char *currency,
+ TALER_MERCHANTDB_DonauInstanceFilteredCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_select_order_blinded_sigs.c b/src/backenddb/pg_select_order_blinded_sigs.c
@@ -0,0 +1,127 @@
+/*
+ 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/>
+ */
+/**
+ * @file backenddb/pg_select_order_blinded_sigs.c
+ * @brief Implementation of the select blinded sigs by order_id function for Postgres
+ * @author Bohdan Potuzhnyi
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "pg_select_order_blinded_sigs.h"
+#include "pg_helper.h"
+
+/**
+ * @brief Context for the callback to handle the result of the blinded sigs selection.
+ */
+struct SelectBlindedSigsContext
+{
+ /**
+ * @brief Callback to be called with the result of the blinded sigs selection.
+ */
+ void *cb_cls;
+
+ /**
+ * @brief Callback to be called with the result of the blinded sigs selection.
+ */
+ TALER_MERCHANTDB_BlindedSigCallback cb;
+
+ /**
+ * @brief Result status of the query.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+};
+
+
+/**
+ * @brief Callback to handle the result of the blinded sigs selection.
+ *
+ * @param cls the closure containing the callback and its context
+ * @param result the result of the query
+ * @param num_results number of results in the result set
+ */
+static void
+restore_sig_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct SelectBlindedSigsContext *ctx = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ struct GNUNET_HashCode h_issue;
+ struct GNUNET_CRYPTO_BlindedSignature *sig;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("token_hash",
+ &h_issue),
+ GNUNET_PQ_result_spec_blinded_sig ("token_blinded_signature",
+ &sig),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ ctx->qs = GNUNET_DB_STATUS_HARD_ERROR;
+ return;
+ }
+ ctx->cb (ctx->cb_cls,
+ &h_issue,
+ sig);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_order_blinded_sigs (
+ void *cls,
+ const char *order_id,
+ TALER_MERCHANTDB_BlindedSigCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct SelectBlindedSigsContext ctx = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS
+ };
+
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (order_id),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "select_blinded_sigs",
+ "SELECT"
+ " motbs.token_blinded_signature"
+ " ,motbs.token_hash"
+ " FROM merchant_order_token_blinded_sigs AS motbs"
+ " JOIN merchant_contract_terms AS mct USING (order_serial)"
+ " WHERE mct.order_id = $1"
+ " ORDER BY motbs.token_index ASC");
+ return GNUNET_PQ_eval_prepared_multi_select (
+ pg->conn,
+ "select_blinded_sigs",
+ params,
+ &restore_sig_cb,
+ &ctx);
+}
diff --git a/src/backenddb/pg_select_order_blinded_sigs.h b/src/backenddb/pg_select_order_blinded_sigs.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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_order_blinded_sigs.h
+ * @brief Implementation of the select blinded sigs by order_id function for Postgres
+ * @author Bohdan Potuzhnyi
+ */
+#ifndef PG_SELECT_BLIND_SIGS_ORD_H
+#define PG_SELECT_BLIND_SIGS_ORD_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+
+
+/**
+ * Look up the Donau *blinded‐signature* blob we already stored for an
+ * order (identified by its human-readable `order_id`) and hand it back
+ * to the caller.
+ *
+ * @param cls closure (our `struct PostgresClosure *`)
+ * @param order_id business-level order identifier
+ * @param cb callback to be called with the blinded signature and hash
+ * @param cb_cls callback closure
+ * @return GNUNET_DB status code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_select_order_blinded_sigs (
+ void *cls,
+ const char *order_id,
+ TALER_MERCHANTDB_BlindedSigCallback cb,
+ void *cb_cls);
+
+#endif
diff --git a/src/backenddb/pg_update_donau_instance.c b/src/backenddb/pg_update_donau_instance.c
@@ -0,0 +1,70 @@
+/*
+ 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_donau_instance.c
+ * @brief Implementation of the update_donau_instance function for Postgres
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "donau/donau_service.h"
+#include "pg_update_donau_instance.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_donau_instance (
+ void *cls,
+ const char *donau_url,
+ const struct DONAU_Charity *charity,
+ const uint64_t charity_id)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (donau_url),
+ GNUNET_PQ_query_param_string (charity->name),
+ GNUNET_PQ_query_param_auto_from_type (&charity->charity_pub),
+ GNUNET_PQ_query_param_uint64 (&charity_id),
+ TALER_PQ_query_param_amount_with_currency (pg->conn,
+ &charity->max_per_year),
+ TALER_PQ_query_param_amount_with_currency (pg->conn,
+ &charity->receipts_to_date),
+ GNUNET_PQ_query_param_uint64 (&charity->current_year),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+
+ PREPARE (pg,
+ "update_existing_donau_instance",
+ "UPDATE merchant_donau_instances SET"
+ " charity_name = $2,"
+ " merchant_instance_serial = (SELECT merchant_serial"
+ " FROM merchant_instances mi"
+ " WHERE mi.merchant_pub = $3),"
+ " charity_max_per_year = $5,"
+ " charity_receipts_to_date = $6,"
+ " current_year = $7"
+ " WHERE"
+ " charity_id = $4"
+ " AND donau_url = $1;");
+
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_existing_donau_instance",
+ params);
+}
+\ No newline at end of file
diff --git a/src/backenddb/pg_update_donau_instance.h b/src/backenddb/pg_update_donau_instance.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_update_donau_instance.h
+ * @brief implementation of the update_donau_instance function for Postgres
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+#ifndef PG_UPDATE_DONAU_INSTANCE_H
+#define PG_UPDATE_DONAU_INSTANCE_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+#include "donau/donau_service.h"
+
+/**
+ * Update information about a Donau charity instance.
+ *
+ * @param cls closure
+ * @param donau_url URL of the Donau
+ * @param charity structure containing information about the charity
+ * @param charity_id unique identifier for the charity
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_donau_instance (
+ void *cls,
+ const char *donau_url,
+ const struct DONAU_Charity *charity,
+ const uint64_t charity_id);
+
+#endif
diff --git a/src/backenddb/pg_update_donau_instance_receipts_amount.c b/src/backenddb/pg_update_donau_instance_receipts_amount.c
@@ -0,0 +1,56 @@
+/*
+ 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_donau_instance_receipts_amount.c
+ * @brief Implementation of the update_donau_instance_receipts_amount
+ * function for Postgres
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "donau/donau_service.h"
+#include "pg_update_donau_instance_receipts_amount.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_donau_instance_receipts_amount (
+ void *cls,
+ uint64_t *donau_instances_serial,
+ const struct TALER_Amount *new_amount)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (donau_instances_serial),
+ TALER_PQ_query_param_amount_with_currency (pg->conn,
+ new_amount),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+
+ PREPARE (pg,
+ "update_donau_instance_receipts",
+ "UPDATE merchant_donau_instances "
+ "SET charity_receipts_to_date = $2 "
+ "WHERE donau_instances_serial = $1;");
+
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_donau_instance_receipts",
+ params);
+}
+\ No newline at end of file
diff --git a/src/backenddb/pg_update_donau_instance_receipts_amount.h b/src/backenddb/pg_update_donau_instance_receipts_amount.h
@@ -0,0 +1,44 @@
+/*
+ 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_donau_instance_receipts_amount.h
+ * @brief implementation of the update_donau_instance function for Postgres
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+#ifndef PG_UPDATE_DONAU_INSTANCE_RECEIPTS_AMOUNT_H
+#define PG_UPDATE_DONAU_INSTANCE_RECEIPTS_AMOUNT_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+#include "donau/donau_service.h"
+
+/**
+ * Update information about a Donau charity instance.
+ *
+ * @param cls closure
+ * @param donau_instances_serial serial of the Donau instance to update
+ * @param new_amount new amount to set for the Donau instance receipts to date
+ * @return transaction status code
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_update_donau_instance_receipts_amount (
+ void *cls,
+ uint64_t *donau_instances_serial,
+ const struct TALER_Amount *new_amount);
+
+#endif
diff --git a/src/backenddb/pg_upsert_donau_keys.c b/src/backenddb/pg_upsert_donau_keys.c
@@ -0,0 +1,77 @@
+/*
+ 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_upsert_donau_keys.c
+ * @brief Implementation of the upsert_donau_keys function for Postgres
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+#include "platform.h"
+#include <taler/taler_error_codes.h>
+#include <taler/taler_dbevents.h>
+#include <taler/taler_pq_lib.h>
+#include "donau/donau_service.h"
+#include "pg_upsert_donau_keys.h"
+#include "pg_helper.h"
+
+enum GNUNET_DB_QueryStatus
+TMH_PG_upsert_donau_keys (
+ void *cls,
+ const struct DONAU_Keys *keys,
+ struct GNUNET_TIME_Absolute first_retry)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ struct PostgresClosure *pg = cls;
+ json_t *jkeys = DONAU_keys_to_json (keys);
+
+ if (NULL == jkeys)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ TALER_PQ_query_param_json (jkeys),
+ GNUNET_PQ_query_param_absolute_time (&first_retry),
+ GNUNET_PQ_query_param_string (keys->donau_url),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ PREPARE (pg,
+ "insert_donau_keys",
+ "INSERT INTO merchant_donau_keys"
+ "(keys_json"
+ ",first_retry"
+ ",donau_url"
+ ") VALUES ($1, $2, $3);");
+ PREPARE (pg,
+ "update_donau_keys",
+ "UPDATE merchant_donau_keys SET"
+ " keys_json=$1,"
+ " first_retry=$2"
+ " WHERE"
+ " donau_url=$3;");
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_donau_keys",
+ params);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_donau_keys",
+ params);
+ }
+
+ json_decref (jkeys);
+ return qs;
+}
+\ No newline at end of file
diff --git a/src/backenddb/pg_upsert_donau_keys.h b/src/backenddb/pg_upsert_donau_keys.h
@@ -0,0 +1,44 @@
+/*
+ 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_upsert_donau_keys.h
+ * @brief implementation of the upsert_donau_keys function for Postgres
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+#ifndef PG_UPSERT_DONAU_KEYS_H
+#define PG_UPSERT_DONAU_KEYS_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_json_lib.h>
+#include "taler_merchantdb_plugin.h"
+#include "donau/donau_service.h"
+
+/**
+ * Function to insert or update Donau keys in the merchant_donau_keys table.
+ *
+ * @param cls closure
+ * @param keys the DONAU_Keys object
+ * @param first_retry time when we make retry to update keys
+ * @return GNUNET_DB_QueryStatus transaction status
+ */
+enum GNUNET_DB_QueryStatus
+TMH_PG_upsert_donau_keys (void *cls,
+ const struct DONAU_Keys *keys,
+ struct GNUNET_TIME_Absolute first_retry);
+
+
+#endif
diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c
@@ -88,8 +88,10 @@
#include "pg_lookup_order_summary.h"
#include "pg_lookup_orders.h"
#include "pg_insert_order.h"
+#include "pg_insert_order_blinded_sigs.h"
#include "pg_unlock_inventory.h"
#include "pg_insert_order_lock.h"
+#include "pg_select_order_blinded_sigs.h"
#include "pg_lookup_contract_terms3.h"
#include "pg_lookup_contract_terms2.h"
#include "pg_lookup_contract_terms.h"
@@ -160,7 +162,20 @@
#include "pg_lookup_statistics_amount_by_interval.h"
#include "pg_lookup_statistics_counter_by_bucket.h"
#include "pg_lookup_statistics_counter_by_interval.h"
-
+#include "pg_update_donau_instance_receipts_amount.h"
+
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+#include "donau/donau_service.h"
+#include "pg_insert_donau_instance.h"
+#include "pg_select_donau_instance_by_serial.h"
+#include "pg_select_donau_instances.h"
+#include "pg_select_donau_instances_filtered.h"
+#include "pg_delete_donau_instance.h"
+#include "pg_lookup_donau_keys.h"
+#include "pg_lookup_order_charity.h"
+#include "pg_upsert_donau_keys.h"
+#include "pg_update_donau_instance.h"
+#endif
/**
* How often do we re-try if we run into a DB serialization error?
@@ -472,10 +487,14 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
= &TMH_PG_lookup_orders;
plugin->insert_order
= &TMH_PG_insert_order;
+ plugin->insert_order_blinded_sigs
+ = &TMH_PG_insert_order_blinded_sigs;
plugin->unlock_inventory
= &TMH_PG_unlock_inventory;
plugin->insert_order_lock
= &TMH_PG_insert_order_lock;
+ plugin->select_order_blinded_sigs
+ = &TMH_PG_select_order_blinded_sigs;
plugin->lookup_contract_terms
= &TMH_PG_lookup_contract_terms;
plugin->lookup_contract_terms2
@@ -634,6 +653,8 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
= &TMH_PG_lookup_token_family_key;
plugin->update_deposit_confirmation_status
= &TMH_PG_update_deposit_confirmation_status;
+ plugin->update_donau_instance_receipts_amount
+ = &TMH_PG_update_donau_instance_receipts_amount;
plugin->insert_spent_token
= &TMH_PG_insert_spent_token;
plugin->insert_issued_token
@@ -650,7 +671,26 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
= &TMH_PG_lookup_statistics_amount_by_interval;
plugin->gc
= &TMH_PG_gc;
-
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+ plugin->insert_donau_instance
+ = &TMH_PG_insert_donau_instance;
+ plugin->select_donau_instance_by_serial
+ = &TMH_PG_select_donau_instance_by_serial;
+ plugin->select_donau_instances
+ = &TMH_PG_select_donau_instances;
+ plugin->select_donau_instances_filtered
+ = &TMH_PG_select_donau_instances_filtered;
+ plugin->delete_donau_instance
+ = &TMH_PG_delete_donau_instance;
+ plugin->lookup_donau_keys
+ = &TMH_PG_lookup_donau_keys;
+ plugin->lookup_order_charity
+ = &TMH_PG_lookup_order_charity;
+ plugin->upsert_donau_keys
+ = &TMH_PG_upsert_donau_keys;
+ plugin->update_donau_instance
+ = &TMH_PG_update_donau_instance;
+#endif
return plugin;
}
diff --git a/src/include/Makefile.am b/src/include/Makefile.am
@@ -10,4 +10,10 @@ talerinclude_HEADERS = \
taler_merchantdb_plugin.h \
taler_merchant_util.h \
taler_merchant_service.h \
+ taler_merchant_pay_service.h \
taler_merchant_testing_lib.h
+
+if HAVE_DONAU
+talerinclude_HEADERS += \
+ taler_merchant_donau.h
+endif
diff --git a/src/include/cutted_from_service b/src/include/cutted_from_service
@@ -0,0 +1,155 @@
+///* PART FROM WHICH IDEAS FOR NEW IMPLEMENTATION HAVE BEEN DEFINED*/
+//
+///* application *before* including this header should do: */
+//#define TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE struct PayState
+//
+//
+//#ifndef TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE
+//#define TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE void
+//#endif
+//
+//struct TALER_MERCHANT_OrderPayHandle *
+//TALER_MERCHANT_order_pay_create (
+// struct GNUNET_CURL_Context *ctx,
+// TALER_MERCHANT_OrderPayCallback pay_cb,
+// TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE *pay_cb_cls)
+//{
+// ph->body = json_object (); // json_t *
+//}
+//
+//
+//struct TALER_MERCHANT_OrderPayOption
+//{
+// enum TALER_MERCHANT_OrderPayOptionType
+// {
+// TALER_MERCHANT_OrderPayOptionType_END = 0,
+// TALER_MERCHANT_OrderPayOptionType_MERCHANT_URL,
+// TALER_MERCHANT_OrderPayOptionType_SESSION_ID,
+// TALER_MERCHANT_OrderPayOptionType_H_CONTRACT,
+// TALER_MERCHANT_OrderPayOptionType_CHOICE_INDEX,
+// TALER_MERCHANT_OrderPayOptionType_AMOUNT,
+// TALER_MERCHANT_OrderPayOptionType_MAX_FEE,
+// TALER_MERCHANT_OrderPayOptionType_MERCHANT_PUB,
+// TALER_MERCHANT_OrderPayOptionType_TIMESTAMP,
+// TALER_MERCHANT_OrderPayOptionType_REFUND_DEADLINE,
+// TALER_MERCHANT_OrderPayOptionType_PAY_DEADLINE,
+// TALER_MERCHANT_OrderPayOptionType_H_WIRE,
+// TALER_MERCHANT_OrderPayOptionType_ORDER_ID,
+// TALER_MERCHANT_OrderPayOptionType_COINS,
+// TALER_MERCHANT_OrderPayOptionType_INPUT_TOKENS,
+// TALER_MERCHANT_OrderPayOptionType_OUTPUT_TOKENS
+// } ot;
+//
+// union
+// {
+// const char *merchant_url;
+// const char *session_id;
+// const struct TALER_PrivateContractHashP *h_contract;
+// int choice_index;
+// struct TALER_Amount amount;
+// struct TALER_Amount max_fee;
+// struct TALER_MerchantPublicKeyP merchant_pub;
+// struct GNUNET_TIME_Timestamp timestamp;
+// struct GNUNET_TIME_Timestamp refund_deadline;
+// struct GNUNET_TIME_Timestamp pay_deadline;
+// struct TALER_MerchantWireHashP h_wire;
+// const char *order_id;
+// struct
+// {
+// unsigned int num_coins;
+// const struct TALER_MERCHANT_PayCoin *coins;
+// } coins;
+// struct
+// {
+// unsigned int num_tokens;
+// const struct TALER_MERCHANT_UseToken *tokens;
+// } input_tokens;
+// struct
+// {
+// unsigned int num_output_tokens,
+// const struct TALER_MERCHANT_OutputToken *output_tokens;
+// } output_tokens;
+// } details;
+//};
+//
+///*FIXME: THIS PARTS NEEDS TO BE DEFINED CLEARLY and ideally in new file with previous struct*/
+//enum TALER_MERCHANT_OrderPayOptionErrorCode
+//TALER_MERCHANT_order_pay_set_options (
+// struct TALER_MERCHANT_OrderPayHandle *ph,
+// struct TALER_MERCHANT_OrderPayOption options[],
+// size_t max_options);
+//
+//{
+// for (i-- not 0 - terminateor && i < max_options; i++)
+// switch (options[i].type)
+// case ...
+// GNUNET_assert (0 ==
+// json_object_set_new (ph->body,
+// "field",
+// json_string ("foo")));
+// return OK;
+// case ...
+// GNUNET_assert (0 ==
+// json_object_set_new (ph->body,
+// "field",
+// json_bool (true)));
+//
+// return OK;
+// case ...
+// GNUNET_assert (0 ==
+// json_object_set_new (ph->body,
+// "wire_hash",
+// GNUNET_JSON_PACK (
+// GNUNET_JSON_pack_data_auto (NULL,
+// &options[i
+// ].details.
+// h_wire))))
+// return OK;
+//}
+//
+//}
+//return ERROR_UNKNOWN_OPTION;
+//}
+//
+//#define TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE() \
+// (const struct TALER_MERCHANT_OrderPayOption) { \
+// .ot = TALER_MERCHANT_OrderPayOptionType_END \
+// }
+//
+//
+//#define TALER_MERCHANT_ORDER_PAY_OPTION_SESSION_ID(session_id) \
+// (const struct TALER_MERCHANT_OrderPayOption) { \
+// .ot = TALER_MERCHANT_OrderPayOptionType_SESSION_ID, \
+// .details.session_id = session_id \
+// }
+//
+//#define TALER_MERCHANT_ORDER_PAY_SET_OPTIONS(ph,...) \
+// MHD_NOWARN_COMPOUND_LITERALS_ \
+// TALER_MERCHANT_order_pay_set_options ( \
+// daemon, \
+// ((const struct TALER_MERCHANT_OrderPayHandle[]) \
+// {__VA_ARGS__, TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE ()}), \
+// MHD_OPTIONS_ARRAY_MAX_SIZE) \
+//
+///* END OF THE PART THAT NEEDS TO BE EXTENDED */
+//
+//enum TALER_MERCHANT_OrderPayOptionErrorCode
+//TALER_MERCHANT_order_pay_start (struct TALER_MERCHANT_OrderPayHandle *ph);
+
+//#if APP /*SAMPLE OF USAGE OF PREVIOUS DEFINITIONS*/
+//{
+// struct TALER_MERCHANT_OrderPayHandle *ph;
+//
+// ph = TALER_MERCHANT_order_pay_create (struct GNUNET_CURL_Context *ctx,
+// TALER_MERCHANT_OrderPayCallback pay_cb,
+// pay_cb_cls);
+//
+// GNUNET_assert (OK ==
+// TALER_MERCHANT_ORDER_PAY_SET_OPTIONS (
+// ph,
+// TALER_MERCHANT_ORDER_PAY_OPTION_SESSION_ID ("session")));
+// pay_start (ph);
+//}
+//#endif
+
+/* END OF LIST OF IDEAS */
+\ No newline at end of file
diff --git a/src/include/taler_merchant_donau.h b/src/include/taler_merchant_donau.h
@@ -0,0 +1,396 @@
+/*
+ This file is part of TALER
+ 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 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along with
+ TALER; see the file COPYING.LIB. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler_merchant_donau.h
+ * @brief Structs to interact with the donau.
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+#ifndef _TALER_MERCHANT_DONAU_H
+#define _TALER_MERCHANT_DONAU_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_error_codes.h>
+#include <taler/taler_exchange_service.h>
+#include "taler_merchant_service.h"
+#include <donau/donau_service.h>
+#include <donau/donau_util.h>
+#include <donau/donaudb_plugin.h>
+
+/**
+ * Structure to hold charity details for the Donau service.
+ */
+struct TALER_MERCHANT_DONAU_Charity
+{
+ /**
+ * name of the charity
+ */
+ const char *name;
+
+ /**
+ * charity url
+ */
+ const char *charity_url;
+
+ /**
+ * public key of the charity
+ */
+ struct DONAU_CharityPublicKeyP charity_pub;
+
+ /**
+ * Max donation amount for this charitiy and @e current_year.
+ */
+ struct TALER_Amount max_per_year;
+
+ /**
+ * Current amount of donation receipts for @e current_year.
+ */
+ struct TALER_Amount receipts_to_date;
+
+ /**
+ * current year
+ */
+ uint64_t current_year;
+
+ /**
+ * The unique identifier of the charity.
+ */
+ uint64_t charity_id;
+};
+
+
+/**
+ * Structure to hold Donau instance details from the database.
+ */
+struct TALER_MERCHANTDB_DonauInstance
+{
+ /**
+ * Donau instance serial
+ */
+ uint64_t donau_instance_serial;
+
+ /**
+ * The URL for the Donau instance.
+ */
+ char *donau_url;
+
+ /**
+ * The name of the charity associated with the Donau instance.
+ */
+ char *charity_name;
+
+ /**
+ * Pointer to the public key of the charity, used for cryptographic operations.
+ * This is represented as an EDDSA public key structure.
+ */
+ struct DONAU_CharityPublicKeyP *charity_pub_key;
+
+ /**
+ * A unique identifier for the charity in the Donau instance.
+ */
+ uint64_t charity_id;
+
+ /**
+ * The maximum allowable amount for donations to this charity in the current year.
+ * This is tracked for regulatory or internal business constraints.
+ */
+ struct TALER_Amount charity_max_per_year;
+
+ /**
+ * The total amount of donations received by the charity in the current year.
+ * This field helps track progress toward the yearly donation limit.
+ */
+ struct TALER_Amount charity_receipts_to_date;
+
+ /**
+ * The current year being tracked for donations.
+ * This is used to differentiate donation data between years.
+ */
+ int64_t current_year;
+
+ /**
+ * A JSON object containing key information specific to the Donau instance,
+ * such as cryptographic keys or other relevant details.
+ */
+ json_t *donau_keys_json;
+};
+
+/**
+ * Callback function typically used by `select_donau_instances` to handle
+ * the details of each Donau instance retrieved from the database.
+ *
+ * @param cls Closure to pass additional context or data to the callback function.
+ * @param donau_instance_serial Serial number of the Donau instance in the merchant database.
+ * @param donau_url The URL of the Donau instance.
+ * @param charity_name The name of the charity associated with the Donau instance.
+ * @param charity_pub_key Pointer to the charity's public key used for cryptographic operations.
+ * @param charity_id The unique identifier for the charity within the Donau instance.
+ * @param charity_max_per_year Maximum allowed donations to the charity for the current year.
+ * @param charity_receipts_to_date Total donations received by the charity so far in the current year.
+ * @param current_year The year for which the donation data is being tracked.
+ * @param donau_keys_json JSON object containing additional key-related information for the Donau instance.
+ */
+typedef void
+(*TALER_MERCHANTDB_DonauInstanceCallback)(
+ void *cls,
+ uint64_t donau_instance_serial,
+ const char *donau_url,
+ const char *charity_name,
+ const struct DONAU_CharityPublicKeyP *charity_pub_key,
+ uint64_t charity_id,
+ const struct TALER_Amount *charity_max_per_year,
+ const struct TALER_Amount *charity_receipts_to_date,
+ int64_t current_year,
+ const json_t *donau_keys_json
+ );
+
+/**
+ * Callback function typically used by `select_donau_instances` to handle
+ * the details of each Donau instance retrieved from the database.
+ *
+ * @param cls Closure to pass additional context or data to the callback function.
+ * @param donau_url The URL of the Donau instance.
+ */
+typedef void
+(*TALER_MERCHANTDB_DonauInstanceFilteredCallback)(
+ void *cls,
+ const char *donau_url
+ );
+
+/**
+ * SPECIFICATION FOR REQUESTS /donau
+ */
+
+/**
+ * Handle for a GET /donau operation.
+ */
+struct TALER_MERCHANT_DonauInstanceGetHandle;
+
+/**
+ * Individual Donau instance details.
+ */
+struct TALER_MERCHANT_DonauInstanceEntry
+{
+ /**
+ * Serial number of the Donau instance in merchant database.
+ */
+ uint64_t donau_instance_serial;
+
+ /**
+ * The URL of the Donau service.
+ */
+ const char *donau_url;
+
+ /**
+ * Name of the charity associated with the Donau instance.
+ */
+ const char *charity_name;
+
+ /**
+ * Public key of the charity.
+ */
+ struct DONAU_CharityPublicKeyP charity_pub_key;
+
+ /**
+ * ID of the charity on Donau.
+ */
+ uint64_t charity_id;
+
+ /**
+ * Maximum donation amount per year allowed for the charity.
+ */
+ struct TALER_Amount charity_max_per_year;
+
+ /**
+ * Total donations received by the charity in the current year.
+ */
+ struct TALER_Amount charity_receipts_to_date;
+
+ /**
+ * The current year being tracked for donations.
+ */
+ int64_t current_year;
+
+ /**
+ * Additional key information related to the Donau instance.
+ */
+ struct DONAU_Keys donau_keys;
+};
+
+/**
+ * Response for a GET /donau request.
+ */
+struct TALER_MERCHANT_DonauInstanceGetResponse
+{
+ /**
+ * HTTP response details.
+ */
+ struct TALER_MERCHANT_HttpResponse hr;
+
+ /**
+ * Details depending on the HTTP status.
+ */
+ union
+ {
+ /**
+ * Details for #MHD_HTTP_OK.
+ */
+ struct
+ {
+ /**
+ * Length of the @e donau_instances array.
+ */
+ unsigned int donau_instances_length;
+
+ /**
+ * Details about the Donau instance.
+ */
+ struct TALER_MERCHANT_DonauInstanceEntry *donau_instances;
+ } ok;
+ } details;
+};
+
+/**
+ * Function called with the result of the GET /donau operation.
+ *
+ * @param cls closure
+ * @param dir response details
+ */
+typedef void
+(*TALER_MERCHANT_DonauInstanceGetCallback)(
+ void *cls,
+ const struct TALER_MERCHANT_DonauInstanceGetResponse *dir);
+
+/**
+ * Initiate the GET /donau request.
+ *
+ * @param ctx CURL context
+ * @param backend_url base URL for the backend
+ * @param cb callback function to handle the response
+ * @param cb_cls closure for the callback function
+ * @return the handle for the operation, or NULL on error
+ */
+struct TALER_MERCHANT_DonauInstanceGetHandle *
+TALER_MERCHANT_donau_instances_get (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ TALER_MERCHANT_DonauInstanceGetCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Cancel the GET /donau instances operation.
+ *
+ * @param dgh request to cancel.
+ */
+void
+TALER_MERCHANT_donau_instances_get_cancel (
+ struct TALER_MERCHANT_DonauInstanceGetHandle *dgh);
+
+
+/**
+ * Handle for a POST /donau operation.
+ */
+struct TALER_MERCHANT_DonauInstancePostHandle;
+
+
+/**
+ * Function called with the result of the POST /donau operation.
+ *
+ * @param cls closure
+ * @param hr HTTP response data
+ */
+typedef void
+(*TALER_MERCHANT_DonauInstancePostCallback)(
+ void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr);
+
+/**
+ * Post a new Donau instance.
+ * Sends a POST request to the backend to register a new Donau instance
+ * with the provided charity details.
+ *
+ * @param ctx the context
+ * @param backend_url the backend URL for the operation
+ * @param charity the details of the Donau charity to be posted
+ * @param auth_token the authentication token for the operation
+ * @param cb function to call with the operation result
+ * @param cb_cls closure for @a cb
+ * @return the instance handle, or NULL upon error
+ */
+struct TALER_MERCHANT_DonauInstancePostHandle *
+TALER_MERCHANT_donau_instances_post (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const struct TALER_MERCHANT_DONAU_Charity *charity,
+ const char *auth_token,
+ TALER_MERCHANT_DonauInstancePostCallback cb,
+ void *cb_cls);
+
+/**
+ * Cancel the POST /donau instances operation.
+ * This must not be called after the callback has been invoked.
+ *
+ * @param dph request to cancel
+ */
+void
+TALER_MERCHANT_donau_instances_post_cancel (
+ struct TALER_MERCHANT_DonauInstancePostHandle *dph);
+
+/**
+ * Handle for a DELETE /donau/$charity_id operation.
+ */
+struct TALER_MERCHANT_DonauInstanceDeleteHandle;
+
+/**
+ * Callback for DELETE /donau/$charity_id operation.
+ *
+ * @param cls Closure to pass context.
+ * @param hr HTTP response data.
+ */
+typedef void
+(*TALER_MERCHANT_DonauInstanceDeleteCallback)(
+ void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr);
+
+/**
+ * Initiates the DELETE /donau/$charity_id operation.
+ *
+ * @param ctx CURL context
+ * @param backend_url Base URL for the backend
+ * @param charity_id The ID of the charity to delete
+ * @param cb Callback function to handle the response
+ * @param cb_cls Closure for @a cb
+ * @return the handle for the operation, or NULL on error
+ */
+struct TALER_MERCHANT_DonauInstanceDeleteHandle *
+TALER_MERCHANT_donau_instance_delete (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ uint64_t charity_id,
+ TALER_MERCHANT_DonauInstanceDeleteCallback cb,
+ void *cb_cls);
+
+/**
+ * Cancel the DELETE /donau/$charity_id operation.
+ *
+ * @param ddh Handle for the operation to cancel.
+ */
+void
+TALER_MERCHANT_donau_instance_delete_cancel (
+ struct TALER_MERCHANT_DonauInstanceDeleteHandle *ddh);
+
+#endif
+\ No newline at end of file
diff --git a/src/include/taler_merchant_pay_service.h b/src/include/taler_merchant_pay_service.h
@@ -0,0 +1,612 @@
+/*
+ This file is part of TALER
+ 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 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along with
+ TALER; see the file COPYING.LIB. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler_merchant_pay_service.h
+ * @brief Payment‑specific interface extracted from taler_merchant_service.h
+ * **Only** declarations and helper‑macros live here – the actual
+ * implementation must be provided in the accompanying *.c file.
+ * This header can be included by both merchant front‑ends and wallets
+ * that want to create or proxy /orders/$ID/pay requests.
+ */
+#ifndef TALER_MERCHANT_PAY_SERVICE_H
+#define TALER_MERCHANT_PAY_SERVICE_H
+
+#include <taler/taler_util.h>
+#include <taler/taler_signatures.h>
+#include <taler/taler_error_codes.h>
+#include <taler/taler_exchange_service.h>
+#include "taler_merchant_service.h"
+#include <gnunet/gnunet_time_lib.h>
+
+
+#ifndef TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE
+#define TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE void
+#endif
+
+/**
+ * Opaque handle returned by the payment helper APIs declared below.
+ */
+struct TALER_MERCHANT_OrderPayHandle;
+
+/**
+ * Which option is being supplied to #TALER_MERCHANT_order_pay_set_options().
+ */
+enum TALER_MERCHANT_OrderPayOptionType
+{
+ TALER_MERCHANT_OrderPayOptionType_END = 0,
+ TALER_MERCHANT_OrderPayOptionType_MERCHANT_URL,
+ TALER_MERCHANT_OrderPayOptionType_SESSION_ID,
+ TALER_MERCHANT_OrderPayOptionType_H_CONTRACT,
+ TALER_MERCHANT_OrderPayOptionType_CHOICE_INDEX,
+ TALER_MERCHANT_OrderPayOptionType_AMOUNT,
+ TALER_MERCHANT_OrderPayOptionType_MAX_FEE,
+ TALER_MERCHANT_OrderPayOptionType_MERCHANT_PUB,
+ TALER_MERCHANT_OrderPayOptionType_TIMESTAMP,
+ TALER_MERCHANT_OrderPayOptionType_REFUND_DEADLINE,
+ TALER_MERCHANT_OrderPayOptionType_PAY_DEADLINE,
+ TALER_MERCHANT_OrderPayOptionType_H_WIRE,
+ TALER_MERCHANT_OrderPayOptionType_ORDER_ID,
+ TALER_MERCHANT_OrderPayOptionType_COINS,
+ TALER_MERCHANT_OrderPayOptionType_INPUT_TOKENS,
+ TALER_MERCHANT_OrderPayOptionType_OUTPUT_TOKENS,
+ TALER_MERCHANT_OrderPayOptionType_WALLET_DATA, // Used privately so no #define is present for it
+ TALER_MERCHANT_OrderPayOptionType_DONAU_URL,
+ TALER_MERCHANT_OrderPayOptionType_DONAU_YEAR,
+ TALER_MERCHANT_OrderPayOptionType_DONAU_BUDIS,
+ /*New objects go in here*/
+ TALER_MERCHANT_OrderPayOptionType_LENGTH
+};
+
+/**
+ * Container describing *one* option for a payment request.
+ *
+ * Applications should typically not use this struct directly,
+ * only through respective TALER_MERCHANT_ORDER_PAY_OPTION_-macros
+ */
+struct TALER_MERCHANT_OrderPayOption
+{
+ /**
+ * Type of the option being supplied.
+ */
+ enum TALER_MERCHANT_OrderPayOptionType ot;
+
+ union
+ {
+ const char *merchant_url;
+ const char *session_id;
+ const struct TALER_PrivateContractHashP *h_contract;
+ int choice_index;
+ struct TALER_Amount amount;
+ struct TALER_Amount max_fee;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ struct GNUNET_TIME_Timestamp timestamp;
+ struct GNUNET_TIME_Timestamp refund_deadline;
+ struct GNUNET_TIME_Timestamp pay_deadline;
+ struct TALER_MerchantWireHashP h_wire;
+ const char *order_id;
+ struct
+ {
+ unsigned int num_coins;
+ const struct TALER_MERCHANT_PayCoin *coins;
+ } coins;
+ struct
+ {
+ unsigned int num_tokens;
+ const struct TALER_MERCHANT_UseToken *tokens;
+ } input_tokens;
+ struct
+ {
+ unsigned int num_output_tokens;
+ const struct TALER_MERCHANT_OutputToken *output_tokens;
+ } output_tokens;
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+ const char *donau_url;
+ uint64_t donau_year;
+ json_t *donau_budis_json;
+#endif
+ } details;
+};
+
+
+/**
+ * Status codes returned from #TALER_MERCHANT_order_pay_set_options() and
+ * #TALER_MERCHANT_order_pay_start().
+ */
+enum TALER_MERCHANT_OrderPayErrorCode
+{
+ TALER_MERCHANT_OPOEC_OK = 0, /**< everything fine */
+ TALER_MERCHANT_OPOEC_UNKNOWN_OPTION, /**< unrecognised option kind */
+ TALER_MERCHANT_OPOEC_DUPLICATE_OPTION, /**< option given more than once */
+ TALER_MERCHANT_OPOEC_INVALID_VALUE, /**< semantic/format error in value */
+ TALER_MERCHANT_OPOEC_MISSING_MANDATORY, /**< required field missing at start */
+ TALER_MERCHANT_OPOEC_URL_FAILURE, /**< failed to build request URL */
+ TALER_MERCHANT_OPOEC_CURL_FAILURE /**< failed to init/schedule CURL */
+};
+
+
+/**
+ * Terminate the list of payment options.
+ *
+ * @par Example
+ * \code
+ * struct TALER_MERCHANT_OrderPayOption opts[] = {
+ * TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE()
+ * };
+ * \endcode
+ */
+#define TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE() \
+ (struct TALER_MERCHANT_OrderPayOption){ \
+ .ot = TALER_MERCHANT_OrderPayOptionType_END \
+ }
+
+
+/**
+ * Specify the merchant’s URL for the payment request.
+ *
+ * @param _url NULL-terminated string holding the merchant endpoint.
+ *
+ * @par Example
+ * \code
+ * struct TALER_MERCHANT_OrderPayOption opts[] = {
+ * TALER_MERCHANT_ORDER_PAY_OPTION_MERCHANT_URL("https://shop.example/pay"),
+ * TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE()
+ * };
+ * \endcode
+ */
+#define TALER_MERCHANT_ORDER_PAY_OPTION_MERCHANT_URL(_url) \
+ (struct TALER_MERCHANT_OrderPayOption){ \
+ .ot = TALER_MERCHANT_OrderPayOptionType_MERCHANT_URL, \
+ .details.merchant_url = (_url) \
+ }
+
+
+/**
+ * Supply the session identifier for this payment.
+ *
+ * @param _sid session ID string.
+ *
+ * @par Example
+ * \code
+ * struct TALER_MERCHANT_OrderPayOption opts[] = {
+ * TALER_MERCHANT_ORDER_PAY_OPTION_SESSION_ID("ABC123"),
+ * TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE()
+ * };
+ * \endcode
+ */
+#define TALER_MERCHANT_ORDER_PAY_OPTION_SESSION_ID(_sid) \
+ (struct TALER_MERCHANT_OrderPayOption){ \
+ .ot = TALER_MERCHANT_OrderPayOptionType_SESSION_ID, \
+ .details.session_id = (_sid) \
+ }
+
+
+/**
+ * Supply a private contract hash for the order.
+ *
+ * @param _h pointer to a TALER_PrivateContractHashP.
+ *
+ * @par Example
+ * \code
+ * struct TALER_PrivateContractHashP *h = …;
+ * struct TALER_MERCHANT_OrderPayOption opts[] = {
+ * TALER_MERCHANT_ORDER_PAY_OPTION_H_CONTRACT(h),
+ * TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE()
+ * };
+ * \endcode
+ */
+#define TALER_MERCHANT_ORDER_PAY_OPTION_H_CONTRACT(_h) \
+ (struct TALER_MERCHANT_OrderPayOption){ \
+ .ot = TALER_MERCHANT_OrderPayOptionType_H_CONTRACT, \
+ .details.h_contract = (_h) \
+ }
+
+
+/**
+ * Supply which choice index the customer(wallet) selected.
+ * from contract terms.
+ *
+ * @param _idx choice index.
+ *
+ * @par Example
+ * \code
+ * struct TALER_MERCHANT_OrderPayOption opts[] = {
+ * TALER_MERCHANT_ORDER_PAY_OPTION_CHOICE_INDEX(2),
+ * TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE()
+ * };
+ * \endcode
+ */
+#define TALER_MERCHANT_ORDER_PAY_OPTION_CHOICE_INDEX(_idx) \
+ (struct TALER_MERCHANT_OrderPayOption){ \
+ .ot = TALER_MERCHANT_OrderPayOptionType_CHOICE_INDEX, \
+ .details.choice_index = (_idx) \
+ }
+
+
+/**
+ * Specify the amount to be paid.
+ *
+ * @param _amt pointer to a TALER_Amount struct.
+ *
+ * @par Example
+ * \code
+ * struct TALER_Amount amt = ...;
+ * struct TALER_MERCHANT_OrderPayOption opts[] = {
+ * TALER_MERCHANT_ORDER_PAY_OPTION_AMOUNT(&amt),
+ * TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE()
+ * };
+ * \endcode
+ */
+#define TALER_MERCHANT_ORDER_PAY_OPTION_AMOUNT(_amt) \
+ (struct TALER_MERCHANT_OrderPayOption){ \
+ .ot = TALER_MERCHANT_OrderPayOptionType_AMOUNT, \
+ .details.amount = *(_amt) \
+ }
+
+
+/**
+ * Set the maximum acceptable fee.
+ *
+ * @param _fee pointer to a TALER_Amount struct for the fee cap.
+ *
+ * @par Example
+ * \code
+ * struct TALER_Amount fee = ...;
+ * struct TALER_MERCHANT_OrderPayOption opts[] = {
+ * TALER_MERCHANT_ORDER_PAY_OPTION_MAX_FEE(&fee),
+ * TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE()
+ * };
+ * \endcode
+ */
+#define TALER_MERCHANT_ORDER_PAY_OPTION_MAX_FEE(_fee) \
+ (struct TALER_MERCHANT_OrderPayOption){ \
+ .ot = TALER_MERCHANT_OrderPayOptionType_MAX_FEE, \
+ .details.max_fee = *(_fee) \
+ }
+
+
+/**
+ * Provide the merchant’s public key.
+ *
+ * @param _mpub pointer to a TALER_MerchantPublicKeyP.
+ *
+ * @par Example
+ * \code
+ * struct TALER_MerchantPublicKeyP *mp = …;
+ * struct TALER_MERCHANT_OrderPayOption opts[] = {
+ * TALER_MERCHANT_ORDER_PAY_OPTION_MERCHANT_PUB(mp),
+ * TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE()
+ * };
+ * \endcode
+ */
+#define TALER_MERCHANT_ORDER_PAY_OPTION_MERCHANT_PUB(_mpub) \
+ (struct TALER_MERCHANT_OrderPayOption){ \
+ .ot = TALER_MERCHANT_OrderPayOptionType_MERCHANT_PUB, \
+ .details.merchant_pub = *(_mpub) \
+ }
+
+
+/**
+ * Stamp the request with a specific time.
+ *
+ * @param _ts GNUNET_TIME_Timestamp value.
+ *
+ * @par Example
+ * \code
+ * struct GNUNET_TIME_Timestamp now = …;
+ * struct TALER_MERCHANT_OrderPayOption opts[] = {
+ * TALER_MERCHANT_ORDER_PAY_OPTION_TIMESTAMP(now),
+ * TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE()
+ * };
+ * \endcode
+ */
+#define TALER_MERCHANT_ORDER_PAY_OPTION_TIMESTAMP(_ts) \
+ (struct TALER_MERCHANT_OrderPayOption){ \
+ .ot = TALER_MERCHANT_OrderPayOptionType_TIMESTAMP, \
+ .details.timestamp = (_ts) \
+ }
+
+
+/**
+ * Set a deadline by which refunds may be issued.
+ *
+ * @param _ts GNUNET_TIME_Timestamp for the refund deadline.
+ *
+ * @par Example
+ * \code
+ * struct GNUNET_TIME_Timestamp rd = …;
+ * struct TALER_MERCHANT_OrderPayOption opts[] = {
+ * TALER_MERCHANT_ORDER_PAY_OPTION_REFUND_DEADLINE(rd),
+ * TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE()
+ * };
+ * \endcode
+ */
+#define TALER_MERCHANT_ORDER_PAY_OPTION_REFUND_DEADLINE(_ts) \
+ (struct TALER_MERCHANT_OrderPayOption){ \
+ .ot = TALER_MERCHANT_OrderPayOptionType_REFUND_DEADLINE, \
+ .details.refund_deadline = (_ts) \
+ }
+
+
+/**
+ * Set a deadline by which payment must be completed.
+ *
+ * @param _ts GNUNET_TIME_Timestamp for the payment deadline.
+ *
+ * @par Example
+ * \code
+ * struct GNUNET_TIME_Timestamp pd = …;
+ * struct TALER_MERCHANT_OrderPayOption opts[] = {
+ * TALER_MERCHANT_ORDER_PAY_OPTION_PAY_DEADLINE(pd),
+ * TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE()
+ * };
+ * \endcode
+ */
+#define TALER_MERCHANT_ORDER_PAY_OPTION_PAY_DEADLINE(_ts) \
+ (struct TALER_MERCHANT_OrderPayOption){ \
+ .ot = TALER_MERCHANT_OrderPayOptionType_PAY_DEADLINE, \
+ .details.pay_deadline = (_ts) \
+ }
+
+
+/**
+ * Supply the merchant wire transaction hash.
+ *
+ * @param _hwire pointer to a TALER_MerchantWireHashP.
+ *
+ * @par Example
+ * \code
+ * struct TALER_MerchantWireHashP *wh = …;
+ * struct TALER_MERCHANT_OrderPayOption opts[] = {
+ * TALER_MERCHANT_ORDER_PAY_OPTION_H_WIRE(wh),
+ * TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE()
+ * };
+ * \endcode
+ */
+#define TALER_MERCHANT_ORDER_PAY_OPTION_H_WIRE(_hwire) \
+ (struct TALER_MERCHANT_OrderPayOption){ \
+ .ot = TALER_MERCHANT_OrderPayOptionType_H_WIRE, \
+ .details.h_wire = *(_hwire) \
+ }
+
+
+/**
+ * Provide the unique order identifier.
+ *
+ * @param _oid NULL-terminated string of the order ID.
+ *
+ * @par Example
+ * \code
+ * struct TALER_MERCHANT_OrderPayOption opts[] = {
+ * TALER_MERCHANT_ORDER_PAY_OPTION_ORDER_ID("order-42"),
+ * TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE()
+ * };
+ * \endcode
+ */
+#define TALER_MERCHANT_ORDER_PAY_OPTION_ORDER_ID(_oid) \
+ (struct TALER_MERCHANT_OrderPayOption){ \
+ .ot = TALER_MERCHANT_OrderPayOptionType_ORDER_ID, \
+ .details.order_id = (_oid) \
+ }
+
+
+/**
+ * Include a list of pay coins.
+ *
+ * @param _num number of coins in the array
+ * @param _coins pointer to array of TALER_MERCHANT_PayCoin.
+ *
+ * @par Example
+ * \code
+ * const struct TALER_MERCHANT_PayCoin coins[2] = { … };
+ * struct TALER_MERCHANT_OrderPayOption opts[] = {
+ * TALER_MERCHANT_ORDER_PAY_OPTION_COINS(2, coins),
+ * TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE()
+ * };
+ * \endcode
+ */
+#define TALER_MERCHANT_ORDER_PAY_OPTION_COINS(_num,_coins) \
+ (struct TALER_MERCHANT_OrderPayOption){ \
+ .ot = TALER_MERCHANT_OrderPayOptionType_COINS, \
+ .details.coins = { .num_coins = (_num), \
+ .coins = (_coins) } \
+ }
+
+
+/**
+ * Include a list of input tokens.
+ *
+ * @param _num number of tokens
+ * @param _tokens pointer to array of TALER_MERCHANT_UseToken.
+ *
+ * @par Example
+ * \code
+ * const struct TALER_MERCHANT_UseToken toks[1] = { … };
+ * struct TALER_MERCHANT_OrderPayOption opts[] = {
+ * TALER_MERCHANT_ORDER_PAY_OPTION_INPUT_TOKENS(1, toks),
+ * TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE()
+ * };
+ * \endcode
+ */
+
+#define TALER_MERCHANT_ORDER_PAY_OPTION_INPUT_TOKENS(_num,_tokens) \
+ (struct TALER_MERCHANT_OrderPayOption){ \
+ .ot = TALER_MERCHANT_OrderPayOptionType_INPUT_TOKENS, \
+ .details.input_tokens = { .num_tokens = (_num), \
+ .tokens = (_tokens) } \
+ }
+
+
+/**
+ * Include a list of output tokens.
+ *
+ * @param _num number of output tokens
+ * @param _otokens pointer to array of TALER_MERCHANT_OutputToken.
+ *
+ * @par Example
+ * \code
+ * const struct TALER_MERCHANT_OutputToken ots[3] = { … };
+ * struct TALER_MERCHANT_OrderPayOption opts[] = {
+ * TALER_MERCHANT_ORDER_PAY_OPTION_OUTPUT_TOKENS(3, ots),
+ * TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE()
+ * };
+ * \endcode
+ */
+#define TALER_MERCHANT_ORDER_PAY_OPTION_OUTPUT_TOKENS(_num,_otokens) \
+ (struct TALER_MERCHANT_OrderPayOption){ \
+ .ot = TALER_MERCHANT_OrderPayOptionType_OUTPUT_TOKENS, \
+ .details.output_tokens = { .num_output_tokens = (_num), \
+ .output_tokens = (_otokens) } \
+ }
+
+
+/**
+ * Supply the Donau service URL.
+ *
+ * @param _u NULL-terminated string of the Donau endpoint.
+ *
+ * @par Example
+ * \code
+ * struct TALER_MERCHANT_OrderPayOption opts[] = {
+ * TALER_MERCHANT_ORDER_PAY_OPTION_DONAU_URL("https://donau.example"),
+ * TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE()
+ * };
+ * \endcode
+ */
+#define TALER_MERCHANT_ORDER_PAY_OPTION_DONAU_URL(_u) \
+ (struct TALER_MERCHANT_OrderPayOption){ \
+ .ot = TALER_MERCHANT_OrderPayOptionType_DONAU_URL, \
+ .details.donau_url = (_u) \
+ }
+
+
+/**
+ * Specify the Donau “year” parameter.
+ *
+ * @param _y 64-bit unsigned integer for the Donau year.
+ *
+ * @par Example
+ * \code
+ * struct TALER_MERCHANT_OrderPayOption opts[] = {
+ * TALER_MERCHANT_ORDER_PAY_OPTION_DONAU_YEAR(2025),
+ * TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE()
+ * };
+ * \endcode
+ */
+#define TALER_MERCHANT_ORDER_PAY_OPTION_DONAU_YEAR(_y) \
+ (struct TALER_MERCHANT_OrderPayOption){ \
+ .ot = TALER_MERCHANT_OrderPayOptionType_DONAU_YEAR, \
+ .details.donau_year = (_y) \
+ }
+
+
+/**
+ * Supply the Donau “budis” JSON structure.
+ *
+ * @param _js pointer to a json_t* holding the budis data.
+ *
+ * @par Example
+ * \code
+ * json_t *budis = json_pack("{s:i}", "quota", 100);
+ * struct TALER_MERCHANT_OrderPayOption opts[] = {
+ * TALER_MERCHANT_ORDER_PAY_OPTION_DONAU_BUDIS(budis),
+ * TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE()
+ * };
+ * \endcode
+ */
+#define TALER_MERCHANT_ORDER_PAY_OPTION_DONAU_BUDIS(_js) \
+ (struct TALER_MERCHANT_OrderPayOption){ \
+ .ot = TALER_MERCHANT_OrderPayOptionType_DONAU_BUDIS, \
+ .details.donau_budis_json = (_js) \
+ }
+
+
+/**
+ * Helper to call TALER_MERCHANT_order_pay_set_options()
+ * with a compound‐literal array terminated by TERMINATE().
+ *
+ * @param ph the payment handle
+ * @param ... a comma-separated list of `_OPTION_*()` macros
+ *
+ * @par Example
+ * \code
+ * TALER_MERCHANT_ORDER_PAY_SET_OPTIONS(handle,
+ * TALER_MERCHANT_ORDER_PAY_OPTION_MERCHANT_URL("https://…"),
+ * TALER_MERCHANT_ORDER_PAY_OPTION_AMOUNT(&amt),
+ * TALER_MERCHANT_ORDER_PAY_OPTION_MAX_FEE(&fee)
+ * );
+ * \endcode
+ */
+#define TALER_MERCHANT_ORDER_PAY_SET_OPTIONS(ph,...) \
+ MHD_NOWARN_COMPOUND_LITERALS_ \
+ TALER_MERCHANT_order_pay_set_options ( \
+ daemon, \
+ ((const struct TALER_MERCHANT_OrderPayHandle[]) \
+ {__VA_ARGS__, TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE ()}), \
+ MHD_OPTIONS_ARRAY_MAX_SIZE) \
+
+
+/**
+ * @brief Create and initialize a new payment handle.
+ *
+ * @param ctx GNUNET CURL context used for HTTP operations
+ * @param pay_cb callback to invoke when the payment completes or fails
+ * @param pay_cb_cls closure data passed back to @a pay_cb
+ * @return pointer to a newly allocated handle, or NULL on error
+ */
+struct TALER_MERCHANT_OrderPayHandle *
+TALER_MERCHANT_order_pay_create (struct GNUNET_CURL_Context *ctx,
+ TALER_MERCHANT_OrderPayCallback pay_cb,
+ TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE
+ *pay_cb_cls);
+
+
+/**
+ * @brief Configure payment options on a handle.
+ *
+ * @param ph payment handle to configure
+ * @param options NULL-terminated array of options (use
+ * TALER_MERCHANT_ORDER_PAY_OPTION_* macros)
+ * @param max_options maximum number of options in the @a options array
+ * @return #TALER_MERCHANT_OPOEC_OK on success;
+ * error code otherwise
+ */
+enum TALER_MERCHANT_OrderPayErrorCode
+TALER_MERCHANT_order_pay_set_options (struct TALER_MERCHANT_OrderPayHandle *ph,
+ const struct TALER_MERCHANT_OrderPayOption
+ options[],
+ size_t max_options);
+
+
+/**
+ * @brief Start processing the payment request.
+ *
+ * @param ph fully configured payment handle
+ * @return #TALER_MERCHANT_OPOEC_OK on successful dispatch;
+ * error code on validation or dispatch failure
+ */
+enum TALER_MERCHANT_OrderPayErrorCode
+TALER_MERCHANT_order_pay_start (struct TALER_MERCHANT_OrderPayHandle *ph);
+
+
+/**
+ * @brief Cancel an in-flight or pending payment.
+ *
+ * @param ph payment handle to cancel and free
+ */
+void
+TALER_MERCHANT_order_pay_cancel1 (struct TALER_MERCHANT_OrderPayHandle *ph);
+
+#endif /* TALER_MERCHANT_PAY_SERVICE_H */
+\ No newline at end of file
diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h
@@ -783,7 +783,7 @@ TALER_MERCHANT_instance_token_post (
* the callback was invoked. Afterwards, the authentication may or
* may not have been updated.
*
- * @param iaph request to cancel.
+ * @param itph request to cancel.
*/
void
TALER_MERCHANT_instance_token_post_cancel (
@@ -799,7 +799,7 @@ struct TALER_MERCHANT_InstanceTokenDeleteHandle;
* Function called with the result of the DELETE /instances/$ID/private/token operation.
*
* @param cls closure
- * @param par response data
+ * @param hr response data
*/
typedef void
(*TALER_MERCHANT_InstanceTokenDeleteCallback)(
@@ -831,7 +831,7 @@ TALER_MERCHANT_instance_token_delete (
* Cancel DELETE token request. Must not be called by clients after
* the callback was invoked.
*
- * @param pah request to cancel.
+ * @param tdh request to cancel.
*/
void
TALER_MERCHANT_instance_token_delete_cancel (
@@ -3675,7 +3675,6 @@ TALER_MERCHANT_order_pay (
TALER_MERCHANT_OrderPayCallback pay_cb,
void *pay_cb_cls);
-
/**
* Cancel a POST /orders/$ID/pay request. Note that if you cancel a request
* like this, you have no assurance that the request has not yet been
@@ -6019,7 +6018,7 @@ typedef void
* @param ctx the context
* @param backend_url HTTP base URL for the backend
* @param slug short, url-safe identifier for the statistic
- * @param stype the type of statistic to get, see #TALER_MERCHANT_StatisticType
+ * @param stype the type of statistic to get, see #TALER_MERCHANT_StatisticsType
* @param cb function to call with the statistic information
* @param cb_cls closure for @a cb
* @return the request handle; NULL upon error
@@ -6178,7 +6177,7 @@ typedef void
* @param ctx the context
* @param backend_url HTTP base URL for the backend
* @param slug short, url-safe identifier for the statistic
- * @param stype the type of statistic to get, see #TALER_MERCHANT_StatisticType
+ * @param stype the type of statistic to get, see #TALER_MERCHANT_StatisticsType
* @param cb function to call with the statistic information
* @param cb_cls closure for @a cb
* @return the request handle; NULL upon error
diff --git a/src/include/taler_merchant_testing_lib.h b/src/include/taler_merchant_testing_lib.h
@@ -30,6 +30,9 @@
#include <gnunet/gnunet_time_lib.h>
#include <taler/taler_testing_lib.h>
#include "taler_merchant_service.h"
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+#include "donau/donau_service.h"
+#endif // HAVE_DONAU_DONAU_SERVICE_H
/* ********************* Helper functions ********************* */
@@ -148,7 +151,7 @@ TALER_TESTING_cmd_merchant_post_instance_token (const char *label,
* Set the access token gotten through another CMD
*
* @param label command label.
- * @param other_cmd the other command exposing the bearer_token trait.
+ * @param cmd_job_label the other command exposing the bearer_token trait.
* @return the command.
*/
struct TALER_TESTING_Command
@@ -664,6 +667,8 @@ TALER_TESTING_cmd_merchant_post_orders3 (
* the proposal request.
* @param http_status expected HTTP status.
* @param token_family_slug slug of the token family to use
+ * @param choice_description description of the choice
+ * @param choice_description_i18n json of description translations
* @param num_inputs number of input tokens.
* @param num_outputs number of output tokens.
* @param order_id the name of the order to add.
@@ -691,6 +696,32 @@ TALER_TESTING_cmd_merchant_post_orders_choices (
/**
+ * Create an order with a choices array with output choice for donau.
+ *
+ * @param label command label
+ * @param cfg configuration to use
+ * @param merchant_url base URL of the merchant serving
+ * the proposal request.
+ * @param http_status expected HTTP status.
+ * @param order_id the name of the order to add.
+ * @param refund_deadline the deadline for refunds on this order.
+ * @param pay_deadline the deadline for payment on this order.
+ * @param amount the amount this order is for.
+ * @return the command
+ */
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_orders_donau (
+ const char *label,
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *order_id,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ struct GNUNET_TIME_Timestamp pay_deadline,
+ const char *amount);
+
+/**
* Define a "GET /orders" CMD.
*
* @param label command label.
@@ -1071,6 +1102,47 @@ TALER_TESTING_cmd_merchant_pay_order_choices (
int choice_index,
const char *input_reference);
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+
+/**
+ * Make a "pay" test command for an order with choices.
+ *
+ * @param label command label.
+ * @param merchant_url merchant base url
+ * @param http_status expected HTTP response code.
+ * @param proposal_reference the proposal whose payment status
+ * is going to be checked.
+ * @param coin_reference reference to any command which is able
+ * to provide coins to use for paying.
+ * @param amount_with_fee amount to pay, including the deposit
+ * fee
+ * @param amount_without_fee amount to pay, no fees included.
+ * @param amount_donation amount for which the BKPS will be received.
+ * @param session_id the session id to use for the payment (can be NULL).
+ * @param choice_index index of the selected choice for the payment.
+ * @param year year of the donation
+ * @param donor_tax_id tax ID of the donor
+ * @param salt salt for the donation
+ * @return the command
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_pay_order_donau (
+ const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *proposal_reference,
+ const char *coin_reference,
+ const char *amount_with_fee,
+ const char *amount_without_fee,
+ const char *amount_donation,
+ const char *session_id,
+ int choice_index,
+ const char *charity_reference,
+ uint64_t year,
+ const char *donor_tax_id,
+ const char *salt);
+
+#endif
/**
* Make an "order paid" test command.
@@ -1843,6 +1915,95 @@ TALER_TESTING_cmd_checkserver2 (const char *label,
const char *expected_header,
const char *expected_body);
+
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+/**
+ * This function is the similar to the TALER_TESSTING_cmd_charity_post from DONAU
+ * with only difference that it re-uses the merchant public key, from the merchant test-suite.
+ *
+ * @param label command label
+ * @param name name of the charity
+ * @param url url of the charity
+ * @param max_per_year maximum amount of donations per year
+ * @param receipts_to_date amount of receipts to date
+ * @param current_year year
+ * @param bearer
+ * @param merchant_reference reference to fetch the merchant public key
+ * @param expected_response_code expected HTTP response code
+ * @return
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_charity_post_merchant (const char *label,
+ const char *name,
+ const char *url,
+ const char *max_per_year,
+ const char *receipts_to_date,
+ uint64_t current_year,
+ const struct DONAU_BearerToken *bearer,
+ const char *merchant_reference,
+ unsigned int expected_response_code);
+
+#endif /* HAVE_DONAU_DONAU_SERVICE_H */
+
+/**
+ * Define a "GET /donau" CMD.
+ *
+ * @param label command label.
+ * @param url base URL of the Donau we are looking for instances of.
+ * @param instance_count number of instances we expect to be returned.
+ * @param expected_http_status expected HTTP response code.
+ * @param ... NULL-terminated list of labels (const char *) of
+ * donau instances (commands) we expect to be returned in the list
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_donau_instances (const char *label,
+ const char *url,
+ unsigned int instance_count,
+ unsigned int
+ expected_http_status,
+ ...);
+
+
+/**
+ * Define a "POST /donau" CMD.
+ *
+ * @param label command label.
+ * @param url base URL of the Donau service serving the
+ * GET /donau_instances request.
+ * @param merchant_reference string reference to get the testing traits
+ * @param expected_http_status expected HTTP response code.
+ * @param ... NULL-terminated list of labels (const char *) of
+ * donau instances (commands) we expect to be returned in the list
+ * (assuming @a expected_http_status is #MHD_HTTP_OK)
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_donau_instance (const char *label,
+ const char *url,
+ const char *merchant_reference,
+ unsigned int
+ expected_http_status,
+ ...);
+
+
+/**
+ * Define a "DELETE /donau/$charity_id" CMD.
+ *
+ * @param label command label.
+ * @param url base URL of the Donau service serving the
+ * DELETE /donau/$charity_id request.
+ * @param charity_id the ID of the charity to delete.
+ * @param expected_http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_delete_donau_instance (const char *label,
+ const char *url,
+ uint64_t charity_id,
+ unsigned int
+ expected_http_status);
+
/**
* This function is used to check the statistics counter API
*
diff --git a/src/include/taler_merchant_util.h b/src/include/taler_merchant_util.h
@@ -213,12 +213,6 @@ struct TALER_MERCHANT_ContractOutput
struct TALER_Amount amount;
/**
- * TODO: Check if this block is possible to operate without default placeholder...
- * Is the donation receipt required?
- */
- bool receipt_required;
-
- /**
* Base URLs of the donation authorities that will issue the tax receipt.
*/
const char **donau_urls;
@@ -785,7 +779,7 @@ TALER_MERCHANT_json_from_token_family (
* @param families_len length of @a families array
* @param[out] family matching token family; NULL if no result
* @param[out] key key of matching token family; NULL if no result
- * @return #GNUNET_ERR if no matching family found; #GNUNET_OK otherwise
+ * @return #GNUNET_SYSERR if no matching family found; #GNUNET_OK otherwise
*/
enum GNUNET_GenericReturnValue
TALER_MERCHANT_find_token_family_key (
diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h
@@ -30,6 +30,11 @@
#include <taler/taler_exchange_service.h>
#include <jansson.h>
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+#include <donau/donau_service.h>
+#include "taler_merchant_donau.h"
+#endif
+
/**
* Handle to interact with the database.
*/
@@ -748,6 +753,19 @@ typedef void
/**
+ * Typically called by `select_order_blinded_sigs`
+ *
+ * @param cls closure
+ * @param hash hash of the token
+ * @param blinded_sig blinded signature for the token
+ */
+typedef void
+(*TALER_MERCHANTDB_BlindedSigCallback)(
+ void *cls,
+ struct GNUNET_HashCode *hash,
+ struct GNUNET_CRYPTO_BlindedSignature *blinded_sig);
+
+/**
* Function called with information about a coin that was deposited.
*
* @param cls closure
@@ -1704,6 +1722,7 @@ struct TALER_MERCHANTDB_Plugin
const struct TALER_MERCHANTDB_InstanceAuthSettings *ias,
bool validation_needed);
+
/**
* Insert information about an instance's account into our database.
*
@@ -2366,6 +2385,24 @@ struct TALER_MERCHANTDB_Plugin
/**
+ * Insert blinded signatures for an order.
+ *
+ * @param cls closure
+ * @param order_id order ID to insert blinded signatures for
+ * @param i index of the blinded signature in the output array
+ * @param hash hash of the token
+ * @param blinded_sigs JSON array of blinded signatures
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_order_blinded_sigs)(
+ void *cls,
+ const char *order_id,
+ uint32_t i,
+ const struct GNUNET_HashCode *hash,
+ const struct GNUNET_CRYPTO_BlindedSignature *blind_sig);
+
+
+ /**
* Release an inventory lock by UUID. Releases ALL stocks locked under
* the given UUID.
*
@@ -2401,6 +2438,22 @@ struct TALER_MERCHANTDB_Plugin
/**
+ * Select blinded signatures for an order.
+ *
+ * @param cls closure
+ * @param order_id order ID to select blinded signatures for
+ * @param cb callback to call for each blinded signature found
+ * @param cb_cls closure for @a cb
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_order_blinded_sigs)(
+ void *cls,
+ const char *order_id,
+ TALER_MERCHANTDB_BlindedSigCallback cb,
+ void *cb_cls);
+
+
+ /**
* Retrieve contract terms given its @a order_id
*
* @param cls closure
@@ -4098,6 +4151,168 @@ struct TALER_MERCHANTDB_Plugin
/**
+ * Update the amount of receipts for a Donau instance.
+ *
+ * @param cls closure
+ * @param donau_instances_serial serial number of the Donau instance
+ * @param new_amount new receipts_to_date amount
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*update_donau_instance_receipts_amount)(
+ void *cls,
+ uint64_t *donau_instances_serial,
+ const struct TALER_Amount *new_amount
+ );
+
+
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+ /**
+ * Upsert Donau keys into the database.
+ *
+ * @param cls closure
+ * @param keys Donau keys to insert or update
+ */
+ enum GNUNET_DB_QueryStatus
+ (*upsert_donau_keys)(
+ void *cls,
+ const struct DONAU_Keys *keys,
+ struct GNUNET_TIME_Absolute first_retry
+ );
+
+ /**
+ * Lookup Donau keys by URL.
+ *
+ * @param cls closure
+ * @param donau_url URL of the Donau instance
+ * @param[out] keys set to the Donau keys on success
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_donau_keys)(
+ void *cls,
+ const char *donau_url,
+ struct GNUNET_TIME_Absolute *first_retry,
+ struct DONAU_Keys **keys
+ );
+
+ /**
+ * Lookup a Donau instance by its instance ID and URL.
+ *
+ * @param cls closure
+ * @param instance_id instance ID of the Donau instance
+ * @param donau_url URL of the Donau instance
+ * @param charity_id set to the charity ID of the Donau instance
+ * @param charity_priv set to the private key of the charity
+ * @param charity_max_per_year set to the maximum amount
+ * the charity can receive per year
+ * @param charity_receipts_to_date set to the total amount
+ * the charity has received to date
+ * @param donau_keys_json set to the JSON representation of the
+ * Donau keys
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_order_charity)(
+ void *cls,
+ const char *instance_id,
+ const char *donau_url,
+ uint64_t *charity_id,
+ struct DONAU_CharityPrivateKeyP *charity_priv,
+ struct TALER_Amount *charity_max_per_year,
+ struct TALER_Amount *charity_receipts_to_date,
+ json_t **donau_keys_json,
+ uint64_t *donau_instance_serial
+ );
+
+ /**
+ * Insert information about a Donau instance.
+ *
+ * @param cls closure
+ * @param donau_url URL of the Donau instance
+ * @param charity details of the charity
+ * @param charity_id charity ID of the Donau instance
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_donau_instance)(
+ void *cls,
+ const char *donau_url,
+ const struct DONAU_Charity *charity,
+ const uint64_t charity_id
+ );
+
+ /**
+ * Select donau instance by serial number.
+ *
+ * @param cls closure
+ * @param serial serial number of the Donau instance in DB
+ * @param[out] donau_url set to the URL of the Donau instance
+ * @param[out] charity_id set to the charity ID of the Donau instance
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_donau_instance_by_serial)(
+ void *cls,
+ uint64_t serial,
+ char **donau_url,
+ uint64_t *charity_id);
+
+ /**
+ * Select all Donau instances.
+ *
+ * @param cls closure
+ * @param cb function to call on all Donau instances found
+ * @param cb_cls closure for @a cb
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_donau_instances)(
+ void *cls,
+ const char *id,
+ TALER_MERCHANTDB_DonauInstanceCallback cb,
+ void *cb_cls);
+
+ /**
+ * Select all Donau instances, but only the donau_url
+ * and charity_max_per_year.
+ *
+ * @param cls closure
+ * @param cb function to call on all Donau instances found
+ * @param cb_cls closure for @a cb
+ */
+ enum GNUNET_DB_QueryStatus
+ (*select_donau_instances_filtered)(
+ void *cls,
+ const char *currency,
+ TALER_MERCHANTDB_DonauInstanceFilteredCallback cb,
+ void *cb_cls);
+
+ /**
+ * Delete information about a Donau instance.
+ *
+ * @param cls closure
+ * @param charity_id charity ID of the Donau instance to delete
+ */
+ enum GNUNET_DB_QueryStatus
+ (*delete_donau_instance)(
+ void *cls,
+ const char *id,
+ const uint64_t charity_id
+ );
+
+ /**
+ * Update information about a Donau instance.
+ *
+ * @param cls closure
+ * @param donau_url URL of the Donau instance
+ * @param charity details of the charity
+ * @param charity_id charity ID of the Donau instance
+ */
+ enum GNUNET_DB_QueryStatus
+ (*update_donau_instance)(
+ void *cls,
+ const char *donau_url,
+ const struct DONAU_Charity *charity,
+ const uint64_t charity_id
+ );
+#endif
+ /**
* Lookup amount statistics for instance and slug by bucket.
*
* @param cls closure
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
@@ -70,7 +70,8 @@ libtalermerchant_la_SOURCES = \
merchant_api_post_webhooks.c \
merchant_api_wallet_get_order.c \
merchant_api_wallet_get_template.c \
- merchant_api_wallet_post_order_refund.c
+ merchant_api_wallet_post_order_refund.c \
+ taler_merchant_pay_service.c
libtalermerchant_la_LIBADD = \
-ltalerexchange \
@@ -85,6 +86,16 @@ libtalermerchant_la_LIBADD = \
-lcurl \
$(XLIB)
+if HAVE_DONAU
+ libtalermerchant_la_SOURCES += \
+ merchant_api_get_donau_instance.c \
+ merchant_api_post_donau_instance.c \
+ merchant_api_delete_donau_instance.c
+
+ libtalermerchant_la_LIBADD += \
+ -ldonau
+endif
+
check_PROGRAMS = \
test_merchant_api_common
diff --git a/src/lib/merchant_api_delete_donau_instance.c b/src/lib/merchant_api_delete_donau_instance.c
@@ -0,0 +1,198 @@
+/*
+ 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 Lesser General Public License as published by the Free Software
+ Foundation; either version 2.1, 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along with
+ TALER; see the file COPYING.LGPL. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * @file merchant_api_delete_donau_instance.c
+ * @brief Implementation of the DELETE /donau/$charity_id request of the merchant's HTTP API
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_service.h"
+#include "merchant_api_curl_defaults.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+/* DONAU RELATED IMPORTS */
+#include "taler_merchant_donau.h"
+#include <donau/donau_service.h>
+
+
+/**
+ * Handle for a DELETE /donau/$charity_id operation.
+ */
+struct TALER_MERCHANT_DonauInstanceDeleteHandle
+{
+ /**
+ * The URL for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_DonauInstanceDeleteCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+/**
+ * Function called when we're done processing the
+ * HTTP DELETE /donau/$charity_id request.
+ *
+ * @param cls the struct TALER_MERCHANT_DonauInstanceDeleteHandle
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_delete_donau_instance_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_DonauInstanceDeleteHandle *ddh = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_HttpResponse hr = {
+ .http_status = (unsigned int) response_code,
+ .reply = json
+ };
+
+ ddh->job = NULL;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Got /donau/$charity_id response with status code %u\n",
+ (unsigned int) response_code);
+
+ switch (response_code)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ case MHD_HTTP_UNAUTHORIZED:
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ default:
+ /* Unexpected response */
+ hr.ec = TALER_JSON_get_error_code (json);
+ hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d for DELETE /donau/$charity_id\n",
+ (unsigned int) response_code,
+ (int) hr.ec);
+ break;
+ }
+ ddh->cb (ddh->cb_cls,
+ &hr);
+ TALER_MERCHANT_donau_instance_delete_cancel (ddh);
+}
+
+
+/**
+ * Initiates the DELETE /donau/$charity_id operation.
+ *
+ * @param ctx CURL context
+ * @param backend_url Base URL for the backend
+ * @param charity_id The ID of the charity to delete
+ * @param cb Callback function to handle the response
+ * @param cb_cls Closure for @a cb
+ * @return the handle for the operation, or NULL on error
+ */
+struct TALER_MERCHANT_DonauInstanceDeleteHandle *
+TALER_MERCHANT_donau_instance_delete (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ uint64_t charity_id,
+ TALER_MERCHANT_DonauInstanceDeleteCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_DonauInstanceDeleteHandle *ddh;
+ char *charity_id_str;
+ CURL *eh;
+
+ GNUNET_asprintf (&charity_id_str,
+ "private/donau/%ld",
+ charity_id);
+ if (NULL == charity_id_str)
+ return NULL;
+
+ ddh = GNUNET_new (struct TALER_MERCHANT_DonauInstanceDeleteHandle);
+ ddh->ctx = ctx;
+ ddh->cb = cb;
+ ddh->cb_cls = cb_cls;
+
+ ddh->url = TALER_url_join (backend_url,
+ charity_id_str,
+ NULL);
+ GNUNET_free (charity_id_str);
+
+ if (NULL == ddh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (ddh);
+ return NULL;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n",
+ ddh->url);
+
+ eh = TALER_MERCHANT_curl_easy_get_ (ddh->url);
+ GNUNET_assert (CURLE_OK == curl_easy_setopt (eh,
+ CURLOPT_CUSTOMREQUEST,
+ "DELETE"));
+ ddh->job = GNUNET_CURL_job_add (ctx,
+ eh,
+ &handle_delete_donau_instance_finished,
+ ddh);
+
+ return ddh;
+}
+
+
+/**
+ * Cancel the DELETE /donau/$charity_id operation.
+ *
+ * @param ddh Handle for the operation to cancel.
+ */
+void
+TALER_MERCHANT_donau_instance_delete_cancel (
+ struct TALER_MERCHANT_DonauInstanceDeleteHandle *ddh)
+{
+ if (NULL != ddh->job)
+ GNUNET_CURL_job_cancel (ddh->job);
+
+ GNUNET_free (ddh->url);
+ GNUNET_free (ddh);
+}
+\ No newline at end of file
diff --git a/src/lib/merchant_api_get_donau_instance.c b/src/lib/merchant_api_get_donau_instance.c
@@ -0,0 +1,286 @@
+/*
+ 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 Lesser General Public License as published by the Free Software
+ Foundation; either version 2.1, 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along with
+ TALER; see the file COPYING.LGPL. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file merchant_api_get_donau_instance.c
+ * @brief Implementation of the GET /donau request of the merchant's HTTP API
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_service.h"
+#include "merchant_api_curl_defaults.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+/* DONAU RELATED IMPORTS */
+#include "taler_merchant_donau.h"
+#include <donau/donau_service.h>
+
+/**
+ * Handle for a GET /donau operation.
+ */
+struct TALER_MERCHANT_DonauInstanceGetHandle
+{
+ /**
+ * The URL for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the request.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_DonauInstanceGetCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+};
+
+/**
+ * Parse Donau instance information from @a ia.
+ *
+ * @param ia JSON array (or NULL!) with Donau instance data
+ * @param igr response to fill
+ * @param dgh operation handle
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+parse_donau_instances (const json_t *ia,
+ struct TALER_MERCHANT_DonauInstanceGetResponse *igr,
+ struct TALER_MERCHANT_DonauInstanceGetHandle *dgh)
+{
+ unsigned int instances_len = (unsigned int) json_array_size (ia);
+ struct TALER_MERCHANT_DonauInstanceEntry
+ instances[GNUNET_NZL (instances_len)];
+ size_t index;
+ json_t *value;
+
+ if ((json_array_size (ia) != (size_t) instances_len))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ json_array_foreach (ia,
+ index,
+ value)
+ {
+ struct TALER_MERCHANT_DonauInstanceEntry *instance = &instances[index];
+ const json_t *donau_keys_json;
+ struct DONAU_Keys *donau_keys_ptr;
+
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint64 ("donau_instance_serial",
+ &instance->donau_instance_serial),
+ GNUNET_JSON_spec_string ("donau_url",
+ &instance->donau_url),
+ GNUNET_JSON_spec_string ("charity_name",
+ &instance->charity_name),
+ GNUNET_JSON_spec_fixed_auto ("charity_pub_key",
+ &instance->charity_pub_key),
+ GNUNET_JSON_spec_uint64 ("charity_id",
+ &instance->charity_id),
+ TALER_JSON_spec_amount_any ("charity_max_per_year",
+ &instance->charity_max_per_year),
+ TALER_JSON_spec_amount_any ("charity_receipts_to_date",
+ &instance->charity_receipts_to_date),
+ GNUNET_JSON_spec_int64 ("current_year",
+ &instance->current_year),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK != GNUNET_JSON_parse (value,
+ spec,
+ NULL,
+ NULL))
+ {
+ GNUNET_break_op (0);
+ return GNUNET_SYSERR;
+ }
+
+ /* Parse the Donau keys */
+ donau_keys_json = json_object_get (value,
+ "donau_keys_json");
+ if (NULL == donau_keys_json)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse donau keys JSON\n");
+ return GNUNET_SYSERR;
+ }
+ donau_keys_ptr = DONAU_keys_from_json (donau_keys_json);
+ if (NULL == donau_keys_ptr)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to convert donau keys from JSON\n");
+ return GNUNET_SYSERR;
+ }
+
+ instance->donau_keys = *donau_keys_ptr;
+ }
+
+ igr->details.ok.donau_instances_length = instances_len;
+ igr->details.ok.donau_instances = instances;
+ dgh->cb (dgh->cb_cls, igr);
+ dgh->cb = NULL;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /donau request.
+ *
+ * @param cls the `struct TALER_MERCHANT_DonauInstanceGetHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_get_donau_instances_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_DonauInstanceGetHandle *dgh = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_DonauInstanceGetResponse igr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ dgh->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Got /donau response with status code %u\n",
+ (unsigned int) response_code);
+
+ switch (response_code)
+ {
+ case MHD_HTTP_OK:
+ {
+ const json_t *donau_instances;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("donau_instances", &donau_instances),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK != GNUNET_JSON_parse (json, spec, NULL, NULL))
+ {
+ igr.hr.http_status = 0;
+ igr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+
+ if (GNUNET_OK == parse_donau_instances (donau_instances, &igr, dgh))
+ {
+ TALER_MERCHANT_donau_instances_get_cancel (dgh);
+ return;
+ }
+
+ igr.hr.http_status = 0;
+ igr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ }
+
+ case MHD_HTTP_UNAUTHORIZED:
+ case MHD_HTTP_NOT_FOUND:
+ default:
+ igr.hr.ec = TALER_JSON_get_error_code (json);
+ igr.hr.hint = TALER_JSON_get_error_hint (json);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d\n",
+ (unsigned int) response_code,
+ (int) igr.hr.ec);
+ break;
+ }
+
+ dgh->cb (dgh->cb_cls,
+ &igr);
+ TALER_MERCHANT_donau_instances_get_cancel (dgh);
+}
+
+
+/**
+ * Initiate the GET /donau request.
+ *
+ * @param ctx CURL context
+ * @param backend_url base URL for the backend
+ * @param cb callback function to handle the response
+ * @param cb_cls closure for the callback function
+ * @return the handle for the operation, or NULL on error
+ */
+struct TALER_MERCHANT_DonauInstanceGetHandle *
+TALER_MERCHANT_donau_instances_get (struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ TALER_MERCHANT_DonauInstanceGetCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_DonauInstanceGetHandle *dgh;
+ CURL *eh;
+
+ dgh = GNUNET_new (struct TALER_MERCHANT_DonauInstanceGetHandle);
+ dgh->ctx = ctx;
+ dgh->cb = cb;
+ dgh->cb_cls = cb_cls;
+
+ dgh->url = TALER_url_join (backend_url, "private/donau", NULL);
+ if (NULL == dgh->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ GNUNET_free (dgh);
+ return NULL;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Requesting URL '%s'\n", dgh->url);
+
+ eh = TALER_MERCHANT_curl_easy_get_ (dgh->url);
+ dgh->job = GNUNET_CURL_job_add (ctx, eh, &handle_get_donau_instances_finished,
+ dgh);
+
+ return dgh;
+}
+
+
+/**
+ * Cancel the GET /donau instances operation.
+ *
+ * @param dgh request to cancel.
+ */
+void
+TALER_MERCHANT_donau_instances_get_cancel (
+ struct TALER_MERCHANT_DonauInstanceGetHandle *dgh)
+{
+ if (NULL != dgh->job)
+ GNUNET_CURL_job_cancel (dgh->job);
+
+ GNUNET_free (dgh->url);
+ GNUNET_free (dgh);
+}
diff --git a/src/lib/merchant_api_get_statistics.c b/src/lib/merchant_api_get_statistics.c
@@ -107,8 +107,10 @@ struct TALER_MERCHANT_StatisticsCounterGetHandle
*
* @param json overall JSON reply
* @param jbuckets JSON array (or NULL!) with bucket data
+ * @param buckets_description human-readable description for the buckets
* @param jintervals JSON array (or NULL!) with bucket data
- * @param scgh operation handle
+ * @param intervals_description human-readable description for the intervals
+ * @param sgh operation handle
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
@@ -373,7 +375,9 @@ handle_get_statistics_amount_finished (void *cls,
*
* @param json overall JSON reply
* @param jbuckets JSON array (or NULL!) with bucket data
+ * @param buckets_description human-readable description for the buckets
* @param jintervals JSON array (or NULL!) with bucket data
+ * @param intervals_description human-readable description for the intervals
* @param scgh operation handle
* @return #GNUNET_OK on success
*/
diff --git a/src/lib/merchant_api_post_donau_instance.c b/src/lib/merchant_api_post_donau_instance.c
@@ -0,0 +1,237 @@
+/*
+ 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 Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1,
+ 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General
+ Public License along with TALER; see the file COPYING.LGPL.
+ If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file merchant_api_post_donau_instance.c
+ * @brief Implementation of the POST /donau request
+ * of the merchant's HTTP API
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <jansson.h>
+#include <microhttpd.h> /* just for HTTP status codes */
+#include <gnunet/gnunet_util_lib.h>
+#include "taler_merchant_service.h"
+#include "merchant_api_curl_defaults.h"
+#include "merchant_api_common.h"
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+#include <taler/taler_curl_lib.h>
+#include <taler/taler_kyclogic_lib.h>
+/* DONAU RELATED IMPORTS */
+#include "taler_merchant_donau.h"
+#include <donau/donau_service.h>
+
+
+/**
+ * Handle for a POST /donau operation.
+ */
+struct TALER_MERCHANT_DonauInstancePostHandle
+{
+ /**
+ * URL for this request.
+ */
+ char *url;
+
+ /**
+ * Handle for the CURL job.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Function to call with the result.
+ */
+ TALER_MERCHANT_DonauInstancePostCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Reference to the execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Minor context that holds body and headers.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+};
+
+
+/**
+ * Function called when the POST /donau operation is finished.
+ *
+ * @param cls the `struct TALER_MERCHANT_DonauInstancePostHandle`
+ * @param response_code HTTP response code, 0 on error
+ * @param response response body, NULL if not in JSON
+ */
+static void
+handle_post_donau_finished (void *cls,
+ long response_code,
+ const void *response)
+{
+ struct TALER_MERCHANT_DonauInstancePostHandle *dph = cls;
+ const json_t *json = response;
+ struct TALER_MERCHANT_HttpResponse hr = {
+ .http_status = (unsigned int) response_code,
+ .reply = json
+ };
+
+ dph->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "POST /donau completed with response code %u\n",
+ (unsigned int) response_code);
+
+ switch (response_code)
+ {
+ case 0:
+ hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ default:
+ TALER_MERCHANT_parse_error_details_ (json,
+ response_code,
+ &hr);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d\n",
+ (unsigned int) response_code,
+ (int) hr.ec);
+ break;
+ }
+
+ dph->cb (dph->cb_cls,
+ &hr);
+ TALER_MERCHANT_donau_instances_post_cancel (dph);
+}
+
+
+struct TALER_MERCHANT_DonauInstancePostHandle *
+TALER_MERCHANT_donau_instances_post (
+ struct GNUNET_CURL_Context *ctx,
+ const char *backend_url,
+ const struct TALER_MERCHANT_DONAU_Charity *charity,
+ const char *auth_token,
+ TALER_MERCHANT_DonauInstancePostCallback cb,
+ void *cb_cls)
+{
+ struct TALER_MERCHANT_DonauInstancePostHandle *dph;
+ json_t *req_obj;
+ json_t *auth_obj;
+
+ if (NULL != auth_token)
+ {
+ if (0 != strncasecmp (RFC_8959_PREFIX,
+ auth_token,
+ strlen (RFC_8959_PREFIX)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Authentication token must start with `%s'\n",
+ RFC_8959_PREFIX);
+ return NULL;
+ }
+ auth_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("method",
+ "token"),
+ GNUNET_JSON_pack_string ("token",
+ auth_token));
+ }
+ else
+ {
+ auth_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("method",
+ "external"));
+ }
+ if (NULL == auth_obj)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
+ req_obj = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("donau_url",
+ charity->charity_url),
+ GNUNET_JSON_pack_uint64 ("charity_id",
+ charity->charity_id)
+ );
+
+ dph = GNUNET_new (struct TALER_MERCHANT_DonauInstancePostHandle);
+ dph->ctx = ctx;
+ dph->cb = cb;
+ dph->cb_cls = cb_cls;
+ dph->url = TALER_url_join (backend_url,
+ "private/donau",
+ NULL);
+
+ if (NULL == dph->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to construct request URL\n");
+ json_decref (req_obj);
+ GNUNET_free (dph);
+ return NULL;
+ }
+ {
+ CURL *eh;
+
+ eh = TALER_MERCHANT_curl_easy_get_ (dph->url);
+ if (GNUNET_OK != TALER_curl_easy_post (&dph->post_ctx,
+ eh,
+ req_obj))
+ {
+ GNUNET_break (0);
+ curl_easy_cleanup (eh);
+ json_decref (req_obj);
+ GNUNET_free (dph);
+ return NULL;
+ }
+
+ json_decref (req_obj);
+
+ dph->job = GNUNET_CURL_job_add2 (ctx,
+ eh,
+ dph->post_ctx.headers,
+ &handle_post_donau_finished,
+ dph);
+ }
+ return dph;
+}
+
+
+void
+TALER_MERCHANT_donau_instances_post_cancel (
+ struct TALER_MERCHANT_DonauInstancePostHandle *dph)
+{
+ if (NULL != dph->job)
+ {
+ GNUNET_CURL_job_cancel (dph->job);
+ dph->job = NULL;
+ }
+ TALER_curl_easy_post_finished (&dph->post_ctx);
+ GNUNET_free (dph->url);
+ GNUNET_free (dph);
+}
+
+
+/* end of merchant_api_post_donau_instance.c */
+\ No newline at end of file
diff --git a/src/lib/taler_merchant_pay_service.c b/src/lib/taler_merchant_pay_service.c
@@ -0,0 +1,1060 @@
+/*
+ This file is part of TALER
+ 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 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along with
+ TALER; see the file COPYING.LIB. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler_merchant_pay_service.c
+ * @brief Implementation of the the ideology
+ * from the pay_service as copy of
+ * merchant_api_post_order_pay.c
+ * @author Bohdan Potuzhnyi
+ */
+#include "platform.h"
+#include <curl/curl.h>
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_curl_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_pay_service.h"
+#include "merchant_api_common.h"
+#include "merchant_api_curl_defaults.h"
+#include <stdio.h>
+#include <taler/taler_json_lib.h>
+#include <taler/taler_signatures.h>
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_curl_lib.h>
+
+/**
+ * @brief A Pay Handle
+ */
+struct TALER_MERCHANT_OrderPayHandle
+{
+ /**
+ * Reference to the GNUNET CURL execution context.
+ */
+ struct GNUNET_CURL_Context *ctx;
+
+ /**
+ * Callback to invoke with the payment result ("pay" mode).
+ */
+ TALER_MERCHANT_OrderPayCallback cb;
+
+ /**
+ * Closure data for @a cb.
+ */
+ TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE *cb_cls;
+
+ /* Mandatory scalars: */
+
+ /**
+ * Base URL of the merchant service.
+ */
+ char *merchant_url;
+
+ /**
+ * Identifier of the order being paid.
+ */
+ char *order_id;
+
+ /**
+ * Session identifier for this payment attempt.
+ */
+ char *session_id;
+
+ /**
+ * Timestamp when the payment request was created.
+ */
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ /**
+ * Deadline after which refunds are no longer allowed.
+ */
+ struct GNUNET_TIME_Timestamp refund_deadline;
+
+ /**
+ * Wire hash for communicating payment details.
+ */
+ struct TALER_MerchantWireHashP h_wire;
+
+ /**
+ * Indicates whether @a h_wire has been set.
+ */
+ bool has_h_wire;
+
+ /* Wallet mode fields: */
+
+ /**
+ * Indicates whether a contract hash was provided.
+ */
+ bool has_h_contract;
+
+ /**
+ * Hash of the private contract terms (wallet mode only).
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * Indicates whether the merchant public key was provided.
+ */
+ bool has_merchant_pub;
+
+ /**
+ * Merchant’s public key for verifying signatures (wallet mode).
+ */
+ struct TALER_MerchantPublicKeyP merchant_pub;
+
+ /**
+ * Indicates whether a choice index was provided.
+ */
+ bool has_choice_index;
+
+ /**
+ * Selected index of the contract choice (for token operations).
+ */
+ int choice_index;
+
+ /**
+ * Legacy: pointer to the amount structure for strcmp checks.
+ */
+ const struct TALER_Amount *amount;
+
+ /**
+ * Legacy: pointer to the maximum fee structure for strcmp checks.
+ */
+ const struct TALER_Amount *max_fee;
+
+ /* Raw arrays as passed in via set_options(): */
+
+ /**
+ * Coins used for payment.
+ */
+ struct
+ {
+ /**
+ * Number of coins provided.
+ */
+ unsigned int num_coins;
+ /**
+ * Array of coins to spend.
+ */
+ const struct TALER_MERCHANT_PayCoin *coins;
+ } coins;
+
+ /**
+ * Input tokens to use (wallet mode).
+ */
+ struct
+ {
+ /**
+ * Number of tokens provided.
+ */
+ unsigned int num_tokens;
+ /**
+ * Array of tokens to redeem.
+ */
+ const struct TALER_MERCHANT_UseToken *tokens;
+ } input_tokens;
+
+ /**
+ * Output tokens expected from the merchant.
+ */
+ struct
+ {
+ /**
+ * Number of output tokens expected.
+ */
+ unsigned int num_output_tokens;
+ /**
+ * Array of expected output tokens.
+ */
+ const struct TALER_MERCHANT_OutputToken *output_tokens;
+ } output_tokens;
+
+ /**
+ * JSON array of token envelope events (from Donau).
+ */
+ json_t *tokens_evs;
+
+ /* Computed once both choice_index and tokens_evs are available: */
+
+ /**
+ * JSON object containing wallet-specific data payload.
+ */
+ json_t *wallet_data;
+
+ /**
+ * Hash code of @a wallet_data for integrity checks.
+ */
+ struct GNUNET_HashCode wallet_data_hash;
+
+ /**
+ * JSON body being constructed for the HTTP POST.
+ */
+ json_t *body;
+
+ /* Final URL and CURL plumbing: */
+
+ /**
+ * Fully formed URL for the POST /order/$ID/pay request.
+ */
+ char *url;
+
+ /**
+ * CURL post context managing headers and body.
+ */
+ struct TALER_CURL_PostContext post_ctx;
+
+ /**
+ * Handle for the asynchronous CURL job.
+ */
+ struct GNUNET_CURL_Job *job;
+
+ /**
+ * Flags indicating which payment options have been set.
+ */
+ bool field_seen[TALER_MERCHANT_OrderPayOptionType_LENGTH];
+
+ /**
+ * True if operating in wallet mode (using tokens/contracts).
+ */
+ bool am_wallet;
+
+ /**
+ * Raw JSON data of `donau` for `wallet_data`.
+ */
+ json_t *donau_data;
+};
+
+/**
+ * Parse blindly signed output tokens from response.
+ *
+ * @param token_sigs the JSON array with the token signatures. Can be NULL.
+ * @param tokens where to store the parsed tokens.
+ * @param num_tokens where to store the length of the @a tokens array.
+ */
+static enum GNUNET_GenericReturnValue
+parse_tokens (const json_t *token_sigs,
+ struct TALER_MERCHANT_OutputToken **tokens,
+ unsigned int *num_tokens)
+{
+ GNUNET_array_grow (*tokens,
+ *num_tokens,
+ json_array_size (token_sigs));
+
+ for (unsigned int i = 0; i<(*num_tokens); i++)
+ {
+ struct TALER_MERCHANT_OutputToken *token = &(*tokens)[i];
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_blinded_token_issue_sig ("blind_sig",
+ &token->blinded_sig),
+ GNUNET_JSON_spec_end ()
+ };
+ const json_t *jtoken
+ = json_array_get (token_sigs,
+ i);
+
+ if (NULL == jtoken)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (jtoken,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+
+ return GNUNET_YES;
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP /pay request.
+ *
+ * @param cls the `struct TALER_MERCHANT_Pay`
+ * @param response_code HTTP response code, 0 on error
+ * @param resp response body, NULL if not in JSON
+ */
+static void
+handle_finished (void *cls,
+ long response_code,
+ const void *resp)
+{
+ struct TALER_MERCHANT_OrderPayHandle *oph = cls;
+ const json_t *json = resp;
+ struct TALER_MERCHANT_PayResponse pr = {
+ .hr.http_status = (unsigned int) response_code,
+ .hr.reply = json
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Received /pay response with status code %u\n",
+ (unsigned int) response_code);
+
+ json_dumpf (json,
+ stderr,
+ JSON_INDENT (2));
+
+ oph->job = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "/pay completed with response code %u\n",
+ (unsigned int) response_code);
+ switch (response_code)
+ {
+ case 0:
+ pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ break;
+ case MHD_HTTP_OK:
+ if (oph->am_wallet)
+ {
+ const json_t *token_sigs = NULL;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("sig",
+ &pr.details.ok.merchant_sig),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("pos_confirmation",
+ &pr.details.ok.pos_confirmation),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("token_sigs",
+ &token_sigs),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (json,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ pr.hr.http_status = 0;
+ pr.hr.hint = "sig field missing in response";
+ break;
+ }
+
+ if (GNUNET_OK !=
+ parse_tokens (token_sigs,
+ &pr.details.ok.tokens,
+ &pr.details.ok.num_tokens))
+ {
+ GNUNET_break_op (0);
+ pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ pr.hr.http_status = 0;
+ pr.hr.hint = "failed to parse token_sigs field in response";
+ break;
+ }
+
+ if (GNUNET_OK !=
+ TALER_merchant_pay_verify (&oph->h_contract_terms,
+ &oph->merchant_pub,
+ &pr.details.ok.merchant_sig))
+ {
+ GNUNET_break_op (0);
+ pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ pr.hr.http_status = 0;
+ pr.hr.hint = "signature invalid";
+ }
+ }
+ break;
+ /* Tolerating Not Acceptable because sometimes
+ * - especially in tests - we might want to POST
+ * coins one at a time. */
+ case MHD_HTTP_NOT_ACCEPTABLE:
+ pr.hr.ec = TALER_JSON_get_error_code (json);
+ pr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ pr.hr.ec = TALER_JSON_get_error_code (json);
+ pr.hr.hint = TALER_JSON_get_error_hint (json);
+ /* This should never happen, either us
+ * or the merchant is buggy (or API version conflict);
+ * just pass JSON reply to the application */
+ break;
+ case MHD_HTTP_PAYMENT_REQUIRED:
+ /* was originally paid, but then refunded */
+ pr.hr.ec = TALER_JSON_get_error_code (json);
+ pr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_FORBIDDEN:
+ pr.hr.ec = TALER_JSON_get_error_code (json);
+ pr.hr.hint = TALER_JSON_get_error_hint (json);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ pr.hr.ec = TALER_JSON_get_error_code (json);
+ pr.hr.hint = TALER_JSON_get_error_hint (json);
+ /* Nothing really to verify, this should never
+ happen, we should pass the JSON reply to the
+ application */
+ break;
+ case MHD_HTTP_REQUEST_TIMEOUT:
+ pr.hr.ec = TALER_JSON_get_error_code (json);
+ pr.hr.hint = TALER_JSON_get_error_hint (json);
+ /* The merchant couldn't generate a timely response, likely because
+ it itself waited too long on the exchange.
+ Pass on to application. */
+ break;
+ case MHD_HTTP_CONFLICT:
+ TALER_MERCHANT_parse_error_details_ (json,
+ MHD_HTTP_CONFLICT,
+ &pr.hr);
+ break;
+ case MHD_HTTP_GONE:
+ TALER_MERCHANT_parse_error_details_ (json,
+ response_code,
+ &pr.hr);
+ /* The merchant says we are too late, the offer has expired or some
+ denomination key of a coin involved has expired.
+ Might be a disagreement in timestamps? Still, pass on to application. */
+ break;
+ case MHD_HTTP_PRECONDITION_FAILED:
+ TALER_MERCHANT_parse_error_details_ (json,
+ response_code,
+ &pr.hr);
+ /* Nothing really to verify, the merchant is blaming us for failing to
+ satisfy some constraint (likely it does not like our exchange because
+ of some disagreement on the PKI). We should pass the JSON reply to the
+ application */
+ break;
+ case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
+ {
+ json_t *ebus = json_object_get (json,
+ "exchange_base_urls");
+ if (NULL == ebus)
+ {
+ GNUNET_break_op (0);
+ pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ pr.hr.http_status = 0;
+ pr.hr.hint = "failed to parse exchange_base_urls field in response";
+ break;
+ }
+ {
+ size_t alen = json_array_size (ebus);
+ const char *ebua[GNUNET_NZL (alen)];
+ size_t idx;
+ json_t *jebu;
+ bool ok = true;
+
+ GNUNET_assert (alen <= UINT_MAX);
+ json_array_foreach (ebus, idx, jebu)
+ {
+ ebua[idx] = json_string_value (jebu);
+ if (NULL == ebua[idx])
+ {
+ GNUNET_break_op (0);
+ pr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ pr.hr.http_status = 0;
+ pr.hr.hint = "non-string value in exchange_base_urls in response";
+ ok = false;
+ break;
+ }
+ }
+ if (! ok)
+ break;
+ pr.details.unavailable_for_legal_reasons.num_exchanges
+ = (unsigned int) alen;
+ pr.details.unavailable_for_legal_reasons.exchanges
+ = ebua;
+ oph->cb (oph->cb_cls,
+ &pr);
+ TALER_MERCHANT_order_pay_cancel1 (oph);
+ return;
+ }
+ }
+ break;
+ case MHD_HTTP_INTERNAL_SERVER_ERROR:
+ TALER_MERCHANT_parse_error_details_ (json,
+ response_code,
+ &pr.hr);
+ /* Server had an internal issue; we should retry,
+ but this API leaves this to the application */
+ break;
+ case MHD_HTTP_BAD_GATEWAY:
+ /* Nothing really to verify, the merchant is blaming the exchange.
+ We should pass the JSON reply to the application */
+ TALER_MERCHANT_parse_error_details_ (json,
+ response_code,
+ &pr.hr);
+ break;
+ case MHD_HTTP_SERVICE_UNAVAILABLE:
+ TALER_MERCHANT_parse_error_details_ (json,
+ response_code,
+ &pr.hr);
+ /* Exchange couldn't respond properly; the retry is
+ left to the application */
+ break;
+ case MHD_HTTP_GATEWAY_TIMEOUT:
+ TALER_MERCHANT_parse_error_details_ (json,
+ response_code,
+ &pr.hr);
+ /* Exchange couldn't respond in a timely fashion;
+ the retry is left to the application */
+ break;
+ default:
+ TALER_MERCHANT_parse_error_details_ (json,
+ response_code,
+ &pr.hr);
+ /* unexpected response code */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u/%d\n",
+ (unsigned int) response_code,
+ (int) pr.hr.ec);
+ GNUNET_break_op (0);
+ break;
+ }
+ oph->cb (oph->cb_cls,
+ &pr);
+
+ if (pr.details.ok.tokens)
+ {
+ GNUNET_free (pr.details.ok.tokens);
+ pr.details.ok.tokens = NULL;
+ pr.details.ok.num_tokens = 0;
+ }
+
+ TALER_MERCHANT_order_pay_cancel1 (oph);
+}
+
+
+/**
+ * @brief Create and initialize a new payment handle
+ *
+ * Allocates a TALER_MERCHANT_OrderPayHandle, sets up its context and
+ * prepares an empty JSON body for the /orders/$ID/pay request.
+ */
+struct TALER_MERCHANT_OrderPayHandle *
+TALER_MERCHANT_order_pay_create (struct GNUNET_CURL_Context *ctx,
+ TALER_MERCHANT_OrderPayCallback cb,
+ TALER_MERCHANT_ORDER_PAY_CALLBACK_CLOSURE_TYPE
+ *cb_cls)
+{
+ struct TALER_MERCHANT_OrderPayHandle *ph =
+ GNUNET_new (struct TALER_MERCHANT_OrderPayHandle);
+ ph->ctx = ctx;
+ ph->cb = cb;
+ ph->cb_cls = cb_cls;
+ ph->body = json_object ();
+ GNUNET_assert (ph->body);
+ return ph;
+}
+
+
+/**
+ * @brief Cancel and free a payment handle
+ *
+ * Aborts any in-flight CURL job, releases all JSON objects and internal
+ * buffers, and frees the handle structure itself.
+ */
+void
+TALER_MERCHANT_order_pay_cancel1 (struct TALER_MERCHANT_OrderPayHandle *ph)
+{
+ if (ph->job)
+ GNUNET_CURL_job_cancel (ph->job);
+ ph->job = NULL;
+
+ TALER_curl_easy_post_finished (&ph->post_ctx);
+
+ if (ph->body)
+ json_decref (ph->body);
+ ph->body = NULL;
+
+ if (ph->wallet_data)
+ json_decref (ph->wallet_data);
+ ph->wallet_data = NULL;
+
+ if (ph->tokens_evs)
+ json_decref (ph->tokens_evs);
+ ph->tokens_evs = NULL;
+
+ if (ph->donau_data)
+ json_decref (ph->donau_data);
+
+ GNUNET_free (ph->url);
+ GNUNET_free (ph->merchant_url);
+ GNUNET_free (ph->session_id);
+ GNUNET_free (ph->order_id);
+ GNUNET_free (ph);
+}
+
+
+/**
+ * @brief Store a JSON snippet under a payment option key
+ *
+ * Ensures that an option of type @a ot has not already been set,
+ * then merges @a snippet into the handle's JSON @c body. Marks the
+ * field_seen flag and frees @a snippet.
+ *
+ * @param ph payment handle receiving the snippet
+ * @param ot option type under which to store @a snippet
+ * @param snippet JSON object representing the option payload
+ * @return #TALER_MERCHANT_OPOEC_OK if stored; appropriate error code otherwise
+ */
+static enum TALER_MERCHANT_OrderPayErrorCode
+store_json_option (struct TALER_MERCHANT_OrderPayHandle *ph,
+ enum TALER_MERCHANT_OrderPayOptionType ot,
+ json_t *snippet)
+{
+ if (ph->field_seen[ot])
+ {
+ json_decref (snippet);
+ return TALER_MERCHANT_OPOEC_DUPLICATE_OPTION;
+ }
+ ph->field_seen[ot] = true;
+ GNUNET_assert (0 == json_object_update (ph->body,
+ snippet));
+ json_decref (snippet);
+ return TALER_MERCHANT_OPOEC_OK;
+}
+
+
+/**
+ * @brief Apply user-supplied options to a payment handle
+ *
+ * Iterates through a NULL-terminated array of #TALER_MERCHANT_OrderPayOption
+ * entries, validates and stores each into @a ph. Handles JSON packing and
+ * internal state updates for coins, tokens, deadlines, Donau data, etc.
+ */
+enum TALER_MERCHANT_OrderPayErrorCode
+TALER_MERCHANT_order_pay_set_options (struct TALER_MERCHANT_OrderPayHandle *ph,
+ const struct TALER_MERCHANT_OrderPayOption
+ options[],
+ size_t max_options)
+{
+ for (size_t i = 0; i < max_options
+ && options[i].ot != TALER_MERCHANT_OrderPayOptionType_END; i++)
+ {
+ const struct TALER_MERCHANT_OrderPayOption *o = &options[i];
+ switch (o->ot)
+ {
+ case TALER_MERCHANT_OrderPayOptionType_MERCHANT_URL:
+ ph->merchant_url = GNUNET_strdup (o->details.merchant_url);
+ break;
+
+ case TALER_MERCHANT_OrderPayOptionType_SESSION_ID:
+ ph->session_id = GNUNET_strdup (o->details.session_id);
+ /* add straight into JSON body */
+ {
+ json_t *js = GNUNET_JSON_PACK (GNUNET_JSON_pack_string ("session_id",
+ o->details.
+ session_id));
+ enum TALER_MERCHANT_OrderPayErrorCode ec =
+ store_json_option (ph,
+ o->ot,
+ js);
+ if (TALER_MERCHANT_OPOEC_OK != ec)
+ return ec;
+ break;
+ }
+
+ case TALER_MERCHANT_OrderPayOptionType_ORDER_ID:
+ {
+ ph->order_id = GNUNET_strdup (o->details.order_id);
+ break;
+ }
+
+ case TALER_MERCHANT_OrderPayOptionType_H_CONTRACT:
+ {
+ ph->h_contract_terms = *o->details.h_contract;
+ ph->has_h_contract = true;
+
+ break;
+ }
+
+ case TALER_MERCHANT_OrderPayOptionType_CHOICE_INDEX:
+ ph->choice_index = o->details.choice_index;
+ ph->has_choice_index = true;
+ break;
+
+ case TALER_MERCHANT_OrderPayOptionType_AMOUNT:
+ {
+ ph->amount = &o->details.amount;
+ break;
+ }
+
+ case TALER_MERCHANT_OrderPayOptionType_MAX_FEE:
+ {
+ ph->max_fee = &o->details.max_fee;
+ break;
+ }
+
+ case TALER_MERCHANT_OrderPayOptionType_MERCHANT_PUB:
+ {
+ ph->merchant_pub = o->details.merchant_pub;
+ ph->has_merchant_pub = true;
+
+ break;
+ }
+
+ case TALER_MERCHANT_OrderPayOptionType_TIMESTAMP:
+ {
+ ph->timestamp = o->details.timestamp;
+ break;
+ }
+
+ case TALER_MERCHANT_OrderPayOptionType_REFUND_DEADLINE:
+ {
+ ph->refund_deadline = o->details.refund_deadline;
+ break;
+ }
+
+ case TALER_MERCHANT_OrderPayOptionType_PAY_DEADLINE:
+ {
+ /* FIXME: This one comes from the merchant_api_post_order_pay
+ no idea do we still need it or not? */
+ break;
+ }
+
+ case TALER_MERCHANT_OrderPayOptionType_H_WIRE:
+ {
+ ph->h_wire = o->details.h_wire;
+ ph->has_h_wire = true;
+
+ break;
+ }
+
+ case TALER_MERCHANT_OrderPayOptionType_COINS:
+ /* stash for later signing */
+ GNUNET_assert (o->details.coins.num_coins >= 0);
+ ph->coins.num_coins = o->details.coins.num_coins;
+ ph->coins.coins = o->details.coins.coins;
+ break;
+
+ case TALER_MERCHANT_OrderPayOptionType_INPUT_TOKENS:
+ /* stash for later signing */
+ ph->input_tokens.num_tokens = o->details.input_tokens.num_tokens;
+ ph->input_tokens.tokens = o->details.input_tokens.tokens;
+ break;
+
+ case TALER_MERCHANT_OrderPayOptionType_OUTPUT_TOKENS:
+ /* store JSON array directly, *and* stash for hash */
+ ph->output_tokens.num_output_tokens =
+ o->details.output_tokens.num_output_tokens;
+ ph->output_tokens.output_tokens = o->details.output_tokens.output_tokens;
+ {
+ /* build and store tokens_evs */
+ json_t *arr = json_array ();
+ for (unsigned j = 0; j < ph->output_tokens.num_output_tokens; j++)
+ {
+ const struct TALER_MERCHANT_OutputToken *otk =
+ &ph->output_tokens.output_tokens[j];
+ json_t *je = GNUNET_JSON_PACK (TALER_JSON_pack_token_envelope (NULL,
+ &otk->
+ envelope));
+ json_array_append_new (arr, je);
+ }
+
+ ph->tokens_evs = arr;
+
+ ph->field_seen[o->ot] = true;
+ }
+ break;
+
+ case TALER_MERCHANT_OrderPayOptionType_DONAU_URL:
+ {
+ if (ph->donau_data == NULL)
+ ph->donau_data = json_object ();
+ GNUNET_assert (0 == json_object_set_new (
+ ph->donau_data,
+ "url",
+ json_string (o->details.donau_url)));
+ break;
+ }
+
+ case TALER_MERCHANT_OrderPayOptionType_DONAU_YEAR:
+ {
+ if (ph->donau_data == NULL)
+ ph->donau_data = json_object ();
+ GNUNET_assert (0 == json_object_set_new (
+ ph->donau_data,
+ "year",
+ json_integer ((json_int_t) o->details.donau_year)));
+ break;
+ }
+
+ case TALER_MERCHANT_OrderPayOptionType_DONAU_BUDIS:
+ {
+ if (ph->donau_data == NULL)
+ ph->donau_data = json_object ();
+ GNUNET_assert (0 == json_object_set_new (
+ ph->donau_data,
+ "budikeypairs",
+ json_incref (o->details.donau_budis_json)));
+ break;
+ }
+
+ default:
+ return TALER_MERCHANT_OPOEC_UNKNOWN_OPTION;
+ }
+ }
+ return TALER_MERCHANT_OPOEC_OK;
+}
+
+
+/**
+ * @brief Dispatch the /orders/$ID/pay request
+ *
+ * Validates that all mandatory parameters (merchant_url, order_id, coins)
+ * have been set, builds the final JSON payload, constructs the URL,
+ * and issues an asynchronous HTTP POST. The payment handle's callback
+ * will receive completion notifications.
+ */
+enum TALER_MERCHANT_OrderPayErrorCode
+TALER_MERCHANT_order_pay_start (struct TALER_MERCHANT_OrderPayHandle *ph)
+{
+ /* all the old mandatory checks */
+ if (! ph->merchant_url || ! ph->order_id)
+ return TALER_MERCHANT_OPOEC_MISSING_MANDATORY;
+ if (! (ph->coins.num_coins >= 0) )
+ return TALER_MERCHANT_OPOEC_MISSING_MANDATORY;
+
+ if (GNUNET_YES !=
+ TALER_amount_cmp_currency (ph->amount,
+ ph->max_fee))
+ {
+ return TALER_MERCHANT_OPOEC_INVALID_VALUE;
+ }
+
+ /* build wallet_data hash for signing coins & tokens */
+ if (ph->has_choice_index)
+ {
+ /* base fields */
+ json_t *wd = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_int64 ("choice_index",
+ ph->choice_index),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_incref ("tokens_evs",
+ ph->tokens_evs))
+ );
+
+ /* Putting prepared donau_data into the wallet_data */
+ if (ph->donau_data)
+ GNUNET_assert (0 == json_object_set_new (
+ wd,
+ "donau",
+ json_incref (ph->donau_data)));
+
+ ph->wallet_data = wd;
+
+ TALER_json_hash (ph->wallet_data,
+ &ph->wallet_data_hash);
+
+ store_json_option (ph,
+ TALER_MERCHANT_OrderPayOptionType_WALLET_DATA,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_object_incref ("wallet_data",
+ ph->wallet_data)));
+ }
+
+ /* sign coins AND build the “coins” JSON in one pass */
+ {
+ json_t *arr = json_array ();
+ struct TALER_Amount total_fee, total_amount;
+
+ for (unsigned i = 0; i < ph->coins.num_coins; i++)
+ {
+ const struct TALER_MERCHANT_PayCoin *c = &ph->coins.coins[i];
+ struct TALER_MERCHANT_PaidCoin pc;
+ json_t *je;
+
+ /* sign */
+ struct TALER_Amount fee;
+ struct TALER_DenominationHashP h_denom_pub;
+
+ TALER_denom_pub_hash (&c->denom_pub,
+ &h_denom_pub);
+ if (0 > TALER_amount_subtract (&fee,
+ &c->amount_with_fee,
+ &c->amount_without_fee))
+ return TALER_MERCHANT_OPOEC_INVALID_VALUE;
+
+
+ TALER_wallet_deposit_sign (&c->amount_with_fee,
+ &fee,
+ &ph->h_wire,
+ &ph->h_contract_terms,
+ (NULL != ph->wallet_data)
+ ? &ph->wallet_data_hash
+ : NULL,
+ c->h_age_commitment,
+ NULL,
+ &h_denom_pub,
+ ph->timestamp,
+ &ph->merchant_pub,
+ ph->refund_deadline,
+ &c->coin_priv,
+ &pc.coin_sig);
+
+ pc.denom_pub = c->denom_pub;
+ pc.denom_sig = c->denom_sig;
+ pc.denom_value = c->denom_value;
+ pc.amount_with_fee = c->amount_with_fee;
+ pc.amount_without_fee = c->amount_without_fee;
+ pc.exchange_url = c->exchange_url;
+ GNUNET_CRYPTO_eddsa_key_get_public (&c->coin_priv.eddsa_priv,
+ &pc.coin_pub.eddsa_pub);
+
+ /* JSON */
+ je = GNUNET_JSON_PACK (TALER_JSON_pack_amount ("contribution",
+ &pc.amount_with_fee),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &pc.coin_pub),
+ GNUNET_JSON_pack_string ("exchange_url",
+ pc.exchange_url),
+ GNUNET_JSON_pack_data_auto ("h_denom",
+ &h_denom_pub),
+ TALER_JSON_pack_denom_sig ("ub_sig",
+ &pc.denom_sig),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &pc.coin_sig));
+ json_array_append_new (arr,
+ je);
+
+ /* optional totals if you need them later
+ (kept here because they existed in the legacy code) */
+ if (0 == i)
+ {
+ total_fee = fee;
+ total_amount = pc.amount_with_fee;
+ }
+ else
+ {
+ if ( (0 >
+ TALER_amount_add (&total_fee,
+ &total_fee,
+ &fee)) ||
+ (0 >
+ TALER_amount_add (&total_amount,
+ &total_amount,
+ &pc.amount_with_fee)) )
+ {
+ return TALER_MERCHANT_OPOEC_INVALID_VALUE;
+ }
+ }
+ }
+
+ /* Putting coins to the body*/
+ {
+ enum TALER_MERCHANT_OrderPayErrorCode ec =
+ store_json_option (ph,
+ TALER_MERCHANT_OrderPayOptionType_COINS,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("coins",
+ arr)
+ ));
+ if (TALER_MERCHANT_OPOEC_OK != ec)
+ {
+ return ec;
+ }
+ }
+ }
+
+ /* sign & pack input_tokens into used_tokens array in body */
+ if (ph->input_tokens.num_tokens > 0)
+ {
+ struct TALER_MERCHANT_UsedToken ut[ph->input_tokens.num_tokens];
+ json_t *arr = json_array ();
+ for (unsigned i = 0; i < ph->input_tokens.num_tokens; i++)
+ {
+ json_t *je;
+ const struct TALER_MERCHANT_UseToken *in = &ph->input_tokens.tokens[i];
+ struct TALER_MERCHANT_UsedToken *t = &ut[i];
+
+ TALER_wallet_token_use_sign (&ph->h_contract_terms,
+ &ph->wallet_data_hash,
+ &in->token_priv,
+ &t->token_sig);
+
+ t->ub_sig = in->ub_sig;
+ t->issue_pub = in->issue_pub;
+
+ GNUNET_CRYPTO_eddsa_key_get_public (&in->token_priv.private_key,
+ &t->token_pub.public_key);
+
+ je = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("token_sig",
+ &t->token_sig),
+ TALER_JSON_pack_token_issue_sig ("ub_sig",
+ &t->ub_sig),
+ GNUNET_JSON_pack_data_auto ("h_issue",
+ &t->issue_pub.public_key->pub_key_hash),
+ GNUNET_JSON_pack_data_auto ("token_pub",
+ &t->token_pub)
+ );
+ json_array_append_new (arr,
+ je);
+ }
+
+ store_json_option (ph,
+ TALER_MERCHANT_OrderPayOptionType_INPUT_TOKENS,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("tokens",
+ arr)
+ )
+ );
+ }
+
+
+ /* post the request */
+ {
+ char *path;
+ CURL *eh;
+ GNUNET_asprintf (&path,
+ "orders/%s/pay",
+ ph->order_id);
+ ph->url = TALER_url_join (ph->merchant_url,
+ path,
+ NULL);
+ GNUNET_free (path);
+
+ if (NULL == ph->url)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Could not construct request URL.\n");
+ json_decref (ph->body);
+ GNUNET_free (ph);
+ return TALER_MERCHANT_OPOEC_URL_FAILURE;
+ }
+
+ eh = TALER_MERCHANT_curl_easy_get_ (ph->url);
+ if (GNUNET_OK !=
+ TALER_curl_easy_post (&ph->post_ctx,
+ eh,
+ ph->body))
+ {
+ GNUNET_break (0);
+ curl_easy_cleanup (eh);
+ GNUNET_free (ph->url);
+ GNUNET_free (ph);
+ return TALER_MERCHANT_OPOEC_CURL_FAILURE;
+ }
+
+ ph->job = GNUNET_CURL_job_add2 (ph->ctx,
+ eh,
+ ph->post_ctx.headers,
+ &handle_finished,
+ ph);
+
+ ph->am_wallet = true;
+ return TALER_MERCHANT_OPOEC_OK;
+ }
+}
diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am
@@ -87,6 +87,14 @@ libtalermerchanttesting_la_SOURCES = \
testing_api_helpers.c \
testing_api_traits.c
+if HAVE_DONAU
+libtalermerchanttesting_la_SOURCES += \
+ testing_api_cmd_post_donau_charity_merchant.c \
+ testing_api_cmd_post_donau_instances.c \
+ testing_api_cmd_get_donau_instances.c \
+ testing_api_cmd_delete_donau_instances.c
+endif
+
libtalermerchanttesting_la_LIBADD = \
$(top_srcdir)/src/lib/libtalermerchant.la \
-ltalerbank \
@@ -102,6 +110,11 @@ libtalermerchanttesting_la_LIBADD = \
-ltalertesting \
$(XLIB)
+if HAVE_DONAU
+libtalermerchanttesting_la_LIBADD += \
+ -ldonautesting
+endif
+
if HAVE_TALERFAKEBANK
check_PROGRAMS = \
@@ -187,6 +200,11 @@ test_merchant_api_cs_LDADD = \
-ljansson \
$(XLIB)
+if HAVE_DONAU
+test_merchant_api_cs_LDADD += \
+ -ldonautesting
+endif
+
test_merchant_api_rsa_SOURCES = \
test_merchant_api.c
test_merchant_api_rsa_LDADD = \
@@ -227,6 +245,11 @@ test_reconciliation_rsa_LDADD = \
-ljansson \
$(XLIB)
+if HAVE_DONAU
+test_merchant_api_rsa_LDADD += \
+ -ldonautesting
+endif
+
test_kyc_api_SOURCES = \
test_kyc_api.c
test_kyc_api_LDADD = \
diff --git a/src/testing/test_merchant_api-rsa.conf b/src/testing/test_merchant_api-rsa.conf
@@ -48,3 +48,71 @@ fee_refresh = EUR:0.03
fee_refund = EUR:0.01
rsa_keysize = 1024
CIPHER = RSA
+
+
+# Sections starting with "doco_" specify which denominations
+# the donau should support (and their respective fee structure)
+[doco_eur_ct_1]
+value = EUR:0.01
+duration_withdraw = 1 year
+anchor_round = 1 year
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.00
+fee_deposit = EUR:0.00
+fee_refresh = EUR:0.01
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 1024
+
+[doco_eur_ct_10]
+value = EUR:0.10
+duration_withdraw = 1 year
+anchor_round = 1 year
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 1024
+
+[doco_eur_1]
+value = EUR:1
+duration_withdraw = 1 year
+anchor_round = 1 year
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 1024
+
+[doco_eur_5]
+value = EUR:5
+duration_withdraw = 1 year
+anchor_round = 1 year
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 1024
+
+[doco_eur_10]
+value = EUR:10
+duration_withdraw = 1 year
+anchor_round = 1 year
+duration_spend = 2 years
+duration_legal = 3 years
+fee_withdraw = EUR:0.01
+fee_deposit = EUR:0.01
+fee_refresh = EUR:0.03
+fee_refund = EUR:0.01
+CIPHER = RSA
+rsa_keysize = 1024
diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c
@@ -38,6 +38,10 @@
#include <taler/taler_error_codes.h>
#include "taler_merchant_testing_lib.h"
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+#include <donau/donau_testing_lib.h>
+#endif
+
/**
* The 'poll-orders-conclude-1' and other 'conclude'
@@ -1694,13 +1698,13 @@ run (void *cls,
* Move money to the exchange's bank account.
*/
cmd_transfer_to_exchange ("create-reserve-tokens",
- "EUR:10.02"),
+ "EUR:20.03"),
/**
* Make a reserve exist, according to the previous transfer.
*/
cmd_exec_wirewatch ("wirewatch-1"),
TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-tokens",
- "EUR:10.02",
+ "EUR:20.03",
payer_payto,
exchange_payto,
"create-reserve-tokens"),
@@ -1714,6 +1718,11 @@ run (void *cls,
"EUR:5",
0,
MHD_HTTP_OK),
+ TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-3",
+ "create-reserve-tokens",
+ "EUR:5",
+ 0,
+ MHD_HTTP_OK),
TALER_TESTING_cmd_merchant_post_tokenfamilies (
"create-upcoming-tokenfamily",
merchant_url,
@@ -1850,12 +1859,94 @@ run (void *cls,
TALER_TESTING_cmd_end ()
};
+ #ifdef HAVE_DONAU_DONAU_SERVICE_H
+ const struct DONAU_BearerToken bearer = {
+ .token = NULL
+ };
+
+ struct TALER_TESTING_Command donau[] = {
+ TALER_TESTING_cmd_set_var (
+ "donau",
+ TALER_TESTING_cmd_get_donau ("get-donau",
+ cred.cfg,
+ true)),
+ TALER_TESTING_cmd_charity_post_merchant ("post-charity",
+ "example",
+ "example.com",
+ "EUR:50", // max_per_year
+ "EUR:0", // receipts_to_date
+ 2025, // current year
+ &bearer,
+ "create-another-order-with-input-and-output",
+ // reusing the merchant_reference for merchant_pub
+ MHD_HTTP_CREATED),
+ TALER_TESTING_cmd_merchant_post_donau_instance (
+ "post-donau-instance",
+ merchant_url,
+ "create-another-order-with-input-and-output", // reusing the merchant_reference
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_sleep (
+ "In this time donaukeyupdate must fetch the keys from the donau",
+ 1),
+ TALER_TESTING_cmd_merchant_get_donau_instances (
+ "get-donau-instance",
+ merchant_url,
+ 1,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_merchant_post_orders_donau (
+ "create-donau-order",
+ cred.cfg,
+ merchant_url,
+ MHD_HTTP_OK,
+ "donau",
+ GNUNET_TIME_UNIT_ZERO_TS,
+ GNUNET_TIME_UNIT_FOREVER_TS,
+ "EUR:1"),
+ TALER_TESTING_cmd_merchant_pay_order_donau (
+ "pay-donau-order",
+ merchant_url,
+ MHD_HTTP_OK,
+ "create-donau-order",
+ "withdraw-coin-3",
+ "EUR:1", /* full amount */
+ "EUR:0.99", /* amount without fees */
+ "EUR:1", /* donation amount */
+ NULL,
+ 0,
+ "post-charity",
+ 2025,
+ "7560001010000",
+ "1234"
+ ),
+ TALER_TESTING_cmd_merchant_delete_donau_instance (
+ "delete-donau-instance",
+ merchant_url,
+ 1,
+ MHD_HTTP_NO_CONTENT),
+ TALER_TESTING_cmd_merchant_get_donau_instances (
+ "get-donau-instances-after-delete",
+ merchant_url,
+ 0,
+ MHD_HTTP_OK),
+ TALER_TESTING_cmd_end ()
+ };
+ #endif
+
struct TALER_TESTING_Command commands[] = {
/* general setup */
TALER_TESTING_cmd_run_fakebank (
"run-fakebank",
cred.cfg,
"exchange-account-exchange"),
+ #ifdef HAVE_DONAU_DONAU_SERVICE_H
+ TALER_TESTING_cmd_system_start (
+ "start-taler",
+ config_file,
+ "-emaDZ",
+ "-u", "exchange-account-exchange",
+ "-r", "merchant-exchange-test",
+ NULL),
+ #else
TALER_TESTING_cmd_system_start (
"start-taler",
config_file,
@@ -1863,6 +1954,7 @@ run (void *cls,
"-u", "exchange-account-exchange",
"-r", "merchant-exchange-test",
NULL),
+ #endif
TALER_TESTING_cmd_get_exchange (
"get-exchange",
cred.cfg,
@@ -2175,6 +2267,12 @@ run (void *cls,
repurchase),
TALER_TESTING_cmd_batch ("tokens",
tokens),
+
+ #ifdef HAVE_DONAU_DONAU_SERVICE_H
+ TALER_TESTING_cmd_batch ("donau",
+ donau),
+ #endif
+
TALER_TESTING_cmd_merchant_get_statisticsamount ("stats-refund",
merchant_url,
"refunds-granted", 6, 0,
@@ -2187,6 +2285,7 @@ run (void *cls,
merchant_url,
"tokens-used", 6, 0,
MHD_HTTP_OK),
+
/**
* End the suite.
*/
diff --git a/src/testing/test_merchant_api.conf b/src/testing/test_merchant_api.conf
@@ -2,6 +2,7 @@
#
[PATHS]
TALER_TEST_HOME = test_merchant_api_home/
+DONAU_RUNTIME_DIR = ${TMPDIR:-${TMP:-/tmp}}/donau-system-runtime/
[merchant-exchange-kudos]
DISABLED = YES
@@ -85,3 +86,40 @@ WIRE_GATEWAY_AUTH_METHOD = NONE
[admin-accountcredentials-exchange]
WIRE_GATEWAY_URL = "http://localhost:8082/accounts/2/taler-wire-gateway/"
WIRE_GATEWAY_AUTH_METHOD = NONE
+
+[donau]
+TERMS_ETAG = tos
+CURRENCY = EUR
+PRIVACY_ETAG = 0
+PORT = 8079
+DB = postgres
+DOMAIN = "Bern"
+BASE_URL = "http://localhost:8079/"
+SERVE = tcp
+EXPIRE_IDLE_SLEEP_INTERVAL ="1 s"
+EXPIRE_LEGAL = 5
+
+[donaudb-postgres]
+CONFIG = "postgres:///talercheck"
+
+[donau-secmod-cs]
+LOOKAHEAD_SIGN = "24 days"
+KEY_DIR = ${TALER_TEST_HOME}taler/exchange-secmod-cs/keys
+OVERLAP_DURATION = 0
+SM_PRIV_KEY = ${DONAU_RUNTIME_DIR}donau-secmod-cs/secmod-private-key
+UNIXPATH = ${DONAU_RUNTIME_DIR}donau-secmod-cs/server.sock
+
+[donau-secmod-rsa]
+LOOKAHEAD_SIGN = "24 days"
+KEY_DIR = ${TALER_TEST_HOME}taler/exchange-secmod-rsa/keys
+OVERLAP_DURATION = 0
+SM_PRIV_KEY = ${DONAU_RUNTIME_DIR}donau-secmod-rsa/secmod-private-key
+UNIXPATH = ${DONAU_RUNTIME_DIR}donau-secmod-rsa/server.sock
+
+[donau-secmod-eddsa]
+LOOKAHEAD_SIGN = "24 days"
+DURATION = "14 days"
+KEY_DIR = ${TALER_TEST_HOME}taler/exchange-secmod-eddsa/keys
+OVERLAP_DURATION = 0
+SM_PRIV_KEY = ${DONAU_RUNTIME_DIR}donau-secmod-eddsa/secmod-private-key
+UNIXPATH = ${DONAU_RUNTIME_DIR}donau-secmod-eddsa/server.sock
+\ No newline at end of file
diff --git a/src/testing/testing_api_cmd_delete_donau_instances.c b/src/testing/testing_api_cmd_delete_donau_instances.c
@@ -0,0 +1,190 @@
+/*
+ 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 Lesser General Public License as published by the Free Software
+ Foundation; either version 2.1, 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License along with TALER;
+ see the file COPYING.LGPL. If not, see <http://www.gnu.org/licenses/>
+ */
+/**
+ * @file testing_api_cmd_delete_donau_instances.c
+ * @brief Command to test DELETE /donau/$charity_id request
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+
+#include "platform.h"
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+#include "taler_merchant_donau.h"
+
+/**
+ * State for DELETE /donau/$charity_id testing command.
+ */
+struct DeleteDonauState
+{
+ /**
+ * Handle for a DELETE /donau request.
+ */
+ struct TALER_MERCHANT_DonauInstanceDeleteHandle *ddh;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the Donau service.
+ */
+ const char *url;
+
+ /**
+ * Charity ID to delete.
+ */
+ uint64_t charity_id;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_http_status;
+};
+
+/**
+ * Callback for DELETE /donau/$charity_id operation.
+ *
+ * @param cls closure for this function
+ * @param hr HTTP response details
+ */
+static void
+delete_donau_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr)
+{
+ struct DeleteDonauState *dds = cls;
+
+ dds->ddh = NULL;
+
+ if (dds->expected_http_status != hr->http_status)
+ {
+ TALER_TESTING_unexpected_status_with_body (
+ dds->is,
+ hr->http_status,
+ dds->expected_http_status,
+ hr->reply);
+ TALER_TESTING_interpreter_fail (dds->is);
+ return;
+ }
+
+ switch (hr->http_status)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "DELETE /donau succeeded\n");
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "DELETE /donau: Charity not found\n");
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "DELETE /donau: Unauthorized access\n");
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unhandled HTTP status %u\n",
+ hr->http_status);
+ }
+
+ TALER_TESTING_interpreter_next (dds->is);
+}
+
+
+/**
+ * Run the DELETE /donau/$charity_id test command.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+delete_donau_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct DeleteDonauState *dds = cls;
+
+ dds->is = is;
+ dds->ddh = TALER_MERCHANT_donau_instance_delete (
+ TALER_TESTING_interpreter_get_context (is),
+ dds->url,
+ dds->charity_id,
+ &delete_donau_cb,
+ dds);
+
+ if (NULL == dds->ddh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to initiate DELETE /donau/$charity_id\n");
+ TALER_TESTING_interpreter_fail (is);
+ return;
+ }
+}
+
+
+/**
+ * Clean up state for DELETE /donau/$charity_id command.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+delete_donau_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct DeleteDonauState *dds = cls;
+
+ if (NULL != dds->ddh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "DELETE /donau/$charity_id operation did not complete\n");
+ TALER_MERCHANT_donau_instance_delete_cancel (dds->ddh);
+ }
+ GNUNET_free (dds);
+}
+
+
+/**
+ * Create a DELETE /donau/$charity_id testing command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_delete_donau_instance (const char *label,
+ const char *url,
+ uint64_t charity_id,
+ unsigned int
+ expected_http_status)
+{
+ struct DeleteDonauState *dds = GNUNET_new (struct DeleteDonauState);
+
+ dds->url = url;
+ dds->charity_id = charity_id;
+ dds->expected_http_status = expected_http_status;
+
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = dds,
+ .label = label,
+ .run = &delete_donau_run,
+ .cleanup = &delete_donau_cleanup
+ };
+
+ return cmd;
+ }
+
+}
diff --git a/src/testing/testing_api_cmd_get_donau_instances.c b/src/testing/testing_api_cmd_get_donau_instances.c
@@ -0,0 +1,184 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2021 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 testing_api_cmd_kyc_get.c
+ * @brief command to test kyc_get request
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+#include "taler_merchant_donau.h"
+
+/**
+ * State of a "GET donau instances" CMD.
+ */
+struct GetDonauInstancesState
+{
+ /**
+ * Handle for a "GET donau instances" request.
+ */
+ struct TALER_MERCHANT_DonauInstanceGetHandle *dgh;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant/donau service.
+ */
+ const char *url;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_http_status;
+
+ /**
+ * The number of instances in the `instances` array.
+ */
+ unsigned int instances_length;
+};
+
+/**
+ * Callback for a /get/donau operation.
+ *
+ * @param cls closure for this function
+ * @param response response details from the Donau service
+ */
+static void
+get_donau_instances_cb (void *cls,
+ const struct
+ TALER_MERCHANT_DonauInstanceGetResponse *response)
+{
+ struct GetDonauInstancesState *gis = cls;
+
+ gis->dgh = NULL;
+
+ if (gis->expected_http_status != response->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected response code %u (%d) to command %s\n",
+ response->hr.http_status,
+ (int) response->hr.ec,
+ TALER_TESTING_interpreter_get_current_label (gis->is));
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+
+ if (MHD_HTTP_OK == response->hr.http_status)
+ {
+ unsigned int instance_count = response->details.ok.donau_instances_length;
+ if (instance_count != gis->instances_length)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Expected %u instances, but got %u\n",
+ gis->instances_length, instance_count);
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ }
+ TALER_TESTING_interpreter_next (gis->is);
+}
+
+
+/**
+ * Run the "GET donau instance" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+get_donau_instances_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct GetDonauInstancesState *gis = cls;
+
+ gis->is = is;
+ gis->dgh = TALER_MERCHANT_donau_instances_get (
+ TALER_TESTING_interpreter_get_context (is),
+ gis->url,
+ &get_donau_instances_cb,
+ gis);
+
+ GNUNET_assert (NULL != gis->dgh);
+}
+
+
+/**
+ * Free the state of a "GET donau instance" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+get_donau_instances_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct GetDonauInstancesState *gis = cls;
+
+ if (NULL != gis->dgh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "GET /donau instances operation did not complete\n");
+ TALER_MERCHANT_donau_instances_get_cancel (gis->dgh);
+ }
+
+ GNUNET_free (gis);
+}
+
+
+/**
+ * Creation of the "GET donau instances" CMD.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_donau_instances (const char *label,
+ const char *url,
+ unsigned int instance_count,
+ unsigned int
+ expected_http_status,
+ ...)
+{
+ struct GetDonauInstancesState *gis;
+ va_list ap;
+
+ gis = GNUNET_new (struct GetDonauInstancesState);
+ gis->url = url;
+ gis->expected_http_status = expected_http_status;
+ gis->instances_length = instance_count;
+
+ va_start (ap, expected_http_status);
+ va_end (ap);
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = gis,
+ .label = label,
+ .run = &get_donau_instances_run,
+ .cleanup = &get_donau_instances_cleanup
+ };
+ return cmd;
+ }
+}
diff --git a/src/testing/testing_api_cmd_get_statisticsamount.c b/src/testing/testing_api_cmd_get_statisticsamount.c
@@ -76,7 +76,7 @@ struct GetStatisticsAmountState
* Callback for a GET /statistics-amount operation.
*
* @param cls closure for this function
- * @param gpr response details
+ * @param scgr response details
*/
static void
get_statisticsamount_cb (void *cls,
diff --git a/src/testing/testing_api_cmd_get_statisticscounter.c b/src/testing/testing_api_cmd_get_statisticscounter.c
@@ -76,7 +76,7 @@ struct GetStatisticsCounterState
* Callback for a GET /statistics-counter operation.
*
* @param cls closure for this function
- * @param gpr response details
+ * @param scgr response details
*/
static void
get_statisticscounter_cb (void *cls,
diff --git a/src/testing/testing_api_cmd_pay_order.c b/src/testing/testing_api_cmd_pay_order.c
@@ -33,8 +33,318 @@
#include <taler/taler_testing_lib.h>
#include <taler/taler_signatures.h>
#include "taler_merchant_service.h"
+#include "taler_merchant_pay_service.h"
#include "taler_merchant_testing_lib.h"
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+#include <donau/donau_service.h>
+#include <donau/donau_testing_lib.h>
+#include <donau/donau_json_lib.h>
+#endif /* HAVE_DONAU_DONAU_SERVICE_H */
+
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+/**
+ * Struct for handling the CS approach in signing of the bkps
+ */
+struct CSR_Data
+{
+ /**
+ * Handle to the "batch issue receipt status" operation.
+ */
+ struct DONAU_CsRBatchIssueHandle *csr_handle;
+
+ /**
+ * CS-Nonce
+ */
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
+
+ /**
+ * batch issue receipt status state
+ */
+ struct StatusState *ss;
+
+ /**
+ * array position in batch issue receipt request (first position is zero)
+ */
+ size_t position;
+};
+
+/**
+ * Handling all data needed for the /pay DONAU CMD.
+ */
+struct MerchantDonauPayData
+{
+ /**
+ * Donau URL.
+ */
+ const char *donau_url;
+
+ /**
+ * Donau keys
+ */
+ struct DONAU_Keys *keys;
+
+ /**
+ * Charity reference
+ */
+ const char *charity_reference;
+
+ /**
+ * Charity id
+ */
+ uint64_t charity_id;
+
+ /**
+ * Amount of the donation
+ */
+ struct TALER_Amount donation_amount;
+
+ /**
+ * Number of BUDIs to create or fetch. Example only.
+ */
+ uint32_t num_bkps;
+
+ /**
+ * Year of the donation
+ */
+ uint64_t year;
+
+ /**
+ * Selected donation unit pub keys for this pay order request
+ */
+ struct DONAU_DonationUnitPublicKey *selected_pks;
+
+ /**
+ * BUDI key pairs used in the payment (blinded_udi + pubkey).
+ */
+ struct DONAU_BlindedUniqueDonorIdentifierKeyPair *bkps;
+
+ /**
+ * Blinding secrets, if needed for each BUDI (CS vs. RSA).
+ */
+ union GNUNET_CRYPTO_BlindingSecretP *blinding_secrets;
+
+ /**
+ * Blinding values. Cs-nonces, cipher.
+ */
+ const struct DONAU_BatchIssueValues **alg_values;
+
+ /**
+ * Hash of the salted donor tax id, if relevant.
+ */
+ struct DONAU_HashDonorTaxId h_donor_tax_id;
+
+ /**
+ * Array of donation receipts;
+ */
+ struct DONAU_DonationReceipt *receipts;
+
+ /**
+ * Array of hashed udis.
+ */
+ struct DONAU_UniqueDonorIdentifierHashP *h_udis;
+
+ /**
+ * If using the CS approach, we might track how many
+ * asynchronous calls are still pending, etc.
+ */
+ unsigned int cs_pending;
+
+ /**
+ * Budis Key Pairs json
+ */
+ json_t *budis_json;
+};
+
+
+/**
+ * Prepares the donau data for the /pay CMD.
+ *
+ * @param is interpreter state
+ * @param ss donau data to prepare
+ */
+static enum GNUNET_GenericReturnValue
+prepare_donau_data (struct TALER_TESTING_Interpreter *is,
+ struct MerchantDonauPayData *ss)
+{
+ /* Get charity id and the charity private key from trait */
+ {
+ const struct TALER_TESTING_Command *charity_post_cmd;
+ const uint64_t *charity_id;
+
+ charity_post_cmd = TALER_TESTING_interpreter_lookup_command (
+ is,
+ ss->charity_reference);
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_charity_id (charity_post_cmd,
+ &charity_id))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ ss->charity_id = (uint64_t) *(charity_id);
+ }
+
+ /* Get donau keys from trait */
+ {
+ const struct TALER_TESTING_Command *keys_cmd;
+ struct DONAU_Keys *keys;
+
+ keys_cmd = TALER_TESTING_interpreter_get_command (is,
+ "donau");
+
+ if (GNUNET_OK !=
+ TALER_TESTING_get_trait_donau_keys (keys_cmd,
+ &keys))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ ss->keys = keys;
+ }
+
+ /* Get selected_pks + num_bkps*/
+ {
+ enum GNUNET_GenericReturnValue sret;
+
+ sret = DONAU_select_donation_unit_keys_for_amount (
+ ss->keys,
+ &ss->donation_amount,
+ ss->year,
+ &ss->selected_pks,
+ &ss->num_bkps);
+
+ if (GNUNET_SYSERR == sret)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (is);
+ return GNUNET_SYSERR;
+ }
+ if ((GNUNET_NO == sret) || (0 == ss->num_bkps))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Could not compose exact amount from donation units\n");
+ TALER_TESTING_interpreter_fail (is);
+ return GNUNET_NO;
+ }
+ }
+
+ /* Get BUDIsKP */
+ {
+ ss->bkps
+ = GNUNET_new_array (ss->num_bkps,
+ struct DONAU_BlindedUniqueDonorIdentifierKeyPair);
+ ss->blinding_secrets
+ = GNUNET_new_array (ss->num_bkps,
+ union GNUNET_CRYPTO_BlindingSecretP);
+ ss->receipts
+ = GNUNET_new_array (ss->num_bkps,
+ struct DONAU_DonationReceipt);
+ ss->alg_values
+ = GNUNET_new_array (ss->num_bkps,
+ const struct DONAU_BatchIssueValues *);
+ ss->h_udis
+ = GNUNET_new_array (ss->num_bkps,
+ struct DONAU_UniqueDonorIdentifierHashP);
+
+ for (size_t cnt = 0; cnt < ss->num_bkps; cnt++)
+ {
+ struct DONAU_UniqueDonorIdentifierNonce *udi_nonce;
+ struct DONAU_BudiMasterSecretP ps;
+ const struct DONAU_BatchIssueValues *alg_values;
+ struct DONAU_BlindedUniqueDonorIdentifier *blinded_udi;
+ struct DONAU_UniqueDonorIdentifierHashP *udi_hash;
+
+ DONAU_donation_unit_pub_hash (&ss->selected_pks[cnt],
+ &ss->bkps[cnt].h_donation_unit_pub);
+
+ ss->receipts[cnt].h_donation_unit_pub
+ = ss->bkps[cnt].h_donation_unit_pub;
+ udi_nonce
+ = &ss->receipts[cnt].nonce;
+ blinded_udi
+ = &ss->bkps[cnt].blinded_udi;
+ udi_hash = &ss->h_udis[cnt];
+
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG,
+ &ps,
+ sizeof (ps));
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
+ udi_nonce,
+ sizeof (*udi_nonce));
+ switch (ss->selected_pks[cnt].bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ alg_values = DONAU_donation_unit_ewv_rsa_singleton ();
+ DONAU_budi_secret_create (&ps,
+ alg_values,
+ &ss->blinding_secrets[cnt]);
+ GNUNET_assert (GNUNET_OK ==
+ DONAU_donation_unit_blind (
+ &ss->selected_pks[cnt],
+ &ss->blinding_secrets[cnt],
+ NULL, /* no cs-nonce needed for rsa */
+ udi_nonce,
+ &ss->h_donor_tax_id,
+ alg_values,
+ udi_hash,
+ blinded_udi));
+ ss->alg_values[cnt] = alg_values;
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "CS donation-unit key not yet supported – skip");
+ return GNUNET_NO;
+ /* FIXME: BUG-#### Cs support missing/broken for donau
+ struct CSR_Data *csr_data = GNUNET_new (struct CSR_Data);
+ TALER_cs_withdraw_nonce_derive ( // FIXME: write new method
+ (struct TALER_PlanchetMasterSecretP *) &ps,
+ &csr_data->nonce.cs_nonce);
+ csr_data->ss = is;
+ csr_data->position = cnt;
+
+ csr_data->csr_handle = DONAU_csr_issue (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_donau_url (is),
+ &ss->selected_pks[cnt],
+ &csr_data->nonce.cs_nonce,
+ &cs_stage_two_callback,
+ csr_data);
+ if (NULL == csr_data->csr_handle)
+ {
+ GNUNET_break (0);
+ }
+ ss->cs_pending++; */
+ break;
+ default:
+ GNUNET_break (0);
+ }
+ }
+
+ {
+ json_t *budikeypairs = json_array ();
+
+ GNUNET_assert (NULL != budikeypairs);
+ for (size_t i = 0; i < ss->num_bkps; i++)
+ {
+ json_t *budikeypair = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("h_donation_unit_pub",
+ &ss->bkps[i].h_donation_unit_pub),
+ DONAU_JSON_pack_blinded_donation_identifier ("blinded_udi",
+ &ss->bkps[i].blinded_udi)
+ );
+
+ /* steal the reference into the array */
+ GNUNET_assert (0 == json_array_append_new (budikeypairs,
+ budikeypair));
+ }
+ ss->budis_json = budikeypairs;
+ }
+ }
+ return GNUNET_OK;
+};
+#endif /* HAVE_DONAU_DONAU_SERVICE_H */
/**
* State for a /pay CMD.
@@ -116,6 +426,11 @@ struct PayState
unsigned int num_issued_tokens;
/**
+ * Number of donau_tokens in @e issued_tokens.
+ */
+ unsigned int num_donau_tokens;
+
+ /**
* The session for which the payment is made.
*/
const char *session_id;
@@ -135,6 +450,12 @@ struct PayState
*/
int choice_index;
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+ /**
+ * Donau data, if required.
+ */
+ struct MerchantDonauPayData donau_data;
+#endif /* HAVE_DONAU_DONAU_SERVICE_H */
};
@@ -461,7 +782,8 @@ pay_cb (void *cls,
if (MHD_HTTP_OK == pr->hr.http_status)
{
ps->merchant_sig = pr->details.ok.merchant_sig;
- if (ps->num_issued_tokens != pr->details.ok.num_tokens)
+ if (ps->num_issued_tokens + ps->num_donau_tokens !=
+ pr->details.ok.num_tokens)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected number of tokens issued. "
@@ -755,23 +1077,15 @@ pay_run (void *cls,
const char *ierror_name = NULL;
unsigned int ierror_line = 0;
- struct GNUNET_JSON_Specification ispec[] = {
+ struct GNUNET_JSON_Specification typespec[] = {
GNUNET_JSON_spec_string ("type",
&kind),
- GNUNET_JSON_spec_string ("token_family_slug",
- &slug),
- GNUNET_JSON_spec_uint32 ("key_index",
- &key_index),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_uint32 ("count",
- &count),
- NULL),
GNUNET_JSON_spec_end ()
};
if (GNUNET_OK !=
GNUNET_JSON_parse (output,
- ispec,
+ typespec,
&ierror_name,
&ierror_line))
{
@@ -784,63 +1098,168 @@ pay_run (void *cls,
TALER_TESTING_FAIL (is);
}
- if (0 != strcmp ("token", kind))
+ if (0 == strcmp ("tax-receipt",
+ kind))
{
- continue;
- }
+ const json_t *donau_urls;
+ const char *donau_url_str = NULL;
+
+ // For test, we care only about the presence of it
+ struct GNUNET_JSON_Specification donauspec[] = {
+ GNUNET_JSON_spec_array_const ("donau_urls",
+ &donau_urls),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (output,
+ donauspec,
+ &ierror_name,
+ &ierror_line))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Parser failed on %s:%u for input `%s'\n",
+ ierror_name,
+ ierror_line,
+ json_dumps (output,
+ JSON_INDENT (2)));
+ TALER_TESTING_FAIL (is);
+ }
+
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+ if ( (NULL == donau_urls) ||
+ (0 == json_array_size (donau_urls)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No donau_urls found in output\n");
+ TALER_TESTING_FAIL (is);
+ }
- GNUNET_array_grow (ps->issued_tokens,
- ps->num_issued_tokens,
- ps->num_issued_tokens + count);
+ donau_url_str = json_string_value (json_array_get (donau_urls,
+ 0));
+ if (NULL == donau_url_str)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "First entry in donau_urls is not a string\n");
+ TALER_TESTING_FAIL (is);
+ }
+
+ ps->donau_data.donau_url = GNUNET_strdup (donau_url_str);
- for (unsigned int k = 0; k < count; k++)
+ if (NULL != ps->donau_data.charity_reference)
+ {
+ switch (prepare_donau_data (is,
+ &ps->donau_data))
+ {
+ case GNUNET_OK:
+ break;
+ case GNUNET_NO:
+ TALER_TESTING_interpreter_next (ps->is);
+ return;
+ case GNUNET_SYSERR:
+ TALER_TESTING_FAIL (is);
+ return;
+ }
+ ps->num_donau_tokens = ps->donau_data.num_bkps;
+ }
+#else /* HAVE_DONAU_DONAU_SERVICE_H */
+ /* SIMPLY NOTHING */
+#endif /* HAVE_DONAU_DONAU_SERVICE_H */
+ }
+
+ if (0 == strcmp ("token",
+ kind))
{
- struct TALER_MERCHANT_PrivateTokenDetails *details =
- &ps->issued_tokens[ps->num_issued_tokens - count + k];
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("token_family_slug",
+ &slug),
+ GNUNET_JSON_spec_uint32 ("key_index",
+ &key_index),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint32 ("count",
+ &count),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
if (GNUNET_OK !=
- find_token_public_key (token_families,
- slug,
- key_index,
- &details->issue_pub))
+ GNUNET_JSON_parse (output,
+ ispec,
+ &ierror_name,
+ &ierror_line))
{
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Parser failed on %s:%u for input `%s'\n",
+ ierror_name,
+ ierror_line,
+ json_dumps (output,
+ JSON_INDENT (2)));
TALER_TESTING_FAIL (is);
}
- /* Only RSA is supported for now. */
- GNUNET_assert (GNUNET_CRYPTO_BSA_RSA ==
- details->issue_pub.public_key->cipher);
-
- TALER_token_blind_input_copy (&details->blinding_inputs,
- TALER_token_blind_input_rsa_singleton ()
- );
- /* FIXME: Where to get details->blinding_inputs from? */
- TALER_token_use_setup_random (&details->master);
- TALER_token_use_setup_priv (&details->master,
- &details->blinding_inputs,
- &details->token_priv);
- TALER_token_use_blinding_secret_create (&details->master,
- &details->blinding_inputs,
- &details->blinding_secret);
- GNUNET_CRYPTO_eddsa_key_get_public (&details->token_priv.private_key
- ,
- &details->token_pub.public_key);
- GNUNET_CRYPTO_hash (&details->token_pub.public_key,
- sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
- &details->h_token_pub.hash);
- details->envelope.blinded_pub = GNUNET_CRYPTO_message_blind_to_sign
- (
- details->issue_pub.public_key,
- &details->blinding_secret,
- NULL, /* FIXME: Add session nonce to support CS tokens */
- &details->h_token_pub.hash,
- sizeof (details->h_token_pub.hash),
- details->blinding_inputs.blinding_inputs);
-
- if (NULL == details->envelope.blinded_pub)
+ if (0 != strcmp ("token",
+ kind))
{
- GNUNET_break (0);
- TALER_TESTING_FAIL (is);
+ continue;
+ }
+
+ GNUNET_array_grow (
+ ps->issued_tokens,
+ ps->num_issued_tokens,
+ ps->num_issued_tokens + count + ps->num_donau_tokens);
+
+ for (unsigned int k = 0; k < count; k++)
+ {
+ struct TALER_MERCHANT_PrivateTokenDetails *details =
+ &ps->issued_tokens[ps->num_issued_tokens - count + k
+ + ps->num_donau_tokens];
+
+ if (GNUNET_OK !=
+ find_token_public_key (token_families,
+ slug,
+ key_index,
+ &details->issue_pub))
+ {
+ TALER_TESTING_FAIL (is);
+ }
+
+ /* Only RSA is supported for now. */
+ GNUNET_assert (GNUNET_CRYPTO_BSA_RSA ==
+ details->issue_pub.public_key->cipher);
+
+ TALER_token_blind_input_copy (&details->blinding_inputs,
+ TALER_token_blind_input_rsa_singleton ()
+ );
+ /* FIXME: Where to get details->blinding_inputs from? */
+ TALER_token_use_setup_random (&details->master);
+ TALER_token_use_setup_priv (&details->master,
+ &details->blinding_inputs,
+ &details->token_priv);
+ TALER_token_use_blinding_secret_create (&details->master,
+ &details->blinding_inputs,
+ &details->blinding_secret)
+ ;
+ GNUNET_CRYPTO_eddsa_key_get_public (
+ &details->token_priv.private_key,
+ &details->token_pub.public_key);
+ GNUNET_CRYPTO_hash (&details->token_pub.public_key,
+ sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
+ &details->h_token_pub.hash);
+ details->envelope.blinded_pub =
+ GNUNET_CRYPTO_message_blind_to_sign
+ (
+ details->issue_pub.public_key,
+ &details->blinding_secret,
+ NULL, /* FIXME: Add session nonce to support CS tokens */
+ &details->h_token_pub.hash,
+ sizeof (details->h_token_pub.hash),
+ details->blinding_inputs.blinding_inputs);
+
+ if (NULL == details->envelope.blinded_pub)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_FAIL (is);
+ }
}
}
}
@@ -850,8 +1269,6 @@ pay_run (void *cls,
default:
TALER_TESTING_FAIL (is);
}
-
-
}
{
@@ -901,8 +1318,8 @@ pay_run (void *cls,
ps->num_issued_tokens);
for (unsigned int i = 0; i<len_output_tokens; i++)
{
- output_tokens[i].envelope.blinded_pub = ps->issued_tokens[i].envelope.
- blinded_pub;
+ output_tokens[i].envelope.blinded_pub
+ = ps->issued_tokens[i].envelope.blinded_pub;
}
if (GNUNET_OK !=
@@ -915,34 +1332,80 @@ pay_run (void *cls,
&h_proposal))
TALER_TESTING_FAIL (is);
ps->h_contract_terms = *h_proposal;
- ps->oph = TALER_MERCHANT_order_pay (
- TALER_TESTING_interpreter_get_context (is),
- ps->merchant_url,
- ps->session_id,
- h_proposal,
- ps->choice_index,
- &ps->total_amount,
- &max_fee,
- &merchant_pub,
- merchant_sig,
- timestamp,
- refund_deadline,
- pay_deadline,
- &h_wire,
- order_id,
- npay_coins,
- pay_coins,
- len_use_tokens,
- use_tokens,
- len_output_tokens,
- output_tokens,
- &pay_cb,
- ps);
+
+ /* New logic of setting pay params */
+ {
+ struct GNUNET_CURL_Context *ctx =
+ TALER_TESTING_interpreter_get_context (is);
+ struct TALER_MERCHANT_OrderPayOption opts[32];
+ size_t oi = 0;
+
+ ps->oph = TALER_MERCHANT_order_pay_create (ctx,
+ &pay_cb,
+ ps);
+
+ if (NULL == ps->oph)
+ TALER_TESTING_FAIL (is);
+
+#define ADD(_opt) opts[oi++] = (_opt)
+ ADD (TALER_MERCHANT_ORDER_PAY_OPTION_MERCHANT_URL (ps->merchant_url));
+ ADD (TALER_MERCHANT_ORDER_PAY_OPTION_ORDER_ID (order_id));
+ if (NULL != ps->session_id)
+ ADD (TALER_MERCHANT_ORDER_PAY_OPTION_SESSION_ID (ps->session_id));
+ ADD (TALER_MERCHANT_ORDER_PAY_OPTION_H_CONTRACT (h_proposal));
+ if (ps->choice_index >= 0)
+ ADD (TALER_MERCHANT_ORDER_PAY_OPTION_CHOICE_INDEX (ps->choice_index));
+ ADD (TALER_MERCHANT_ORDER_PAY_OPTION_AMOUNT (&ps->total_amount));
+ ADD (TALER_MERCHANT_ORDER_PAY_OPTION_MAX_FEE (&max_fee));
+ ADD (TALER_MERCHANT_ORDER_PAY_OPTION_MERCHANT_PUB (&merchant_pub));
+ ADD (TALER_MERCHANT_ORDER_PAY_OPTION_TIMESTAMP (timestamp));
+ ADD (TALER_MERCHANT_ORDER_PAY_OPTION_REFUND_DEADLINE (refund_deadline));
+ ADD (TALER_MERCHANT_ORDER_PAY_OPTION_PAY_DEADLINE (pay_deadline));
+ ADD (TALER_MERCHANT_ORDER_PAY_OPTION_H_WIRE (&h_wire));
+ ADD (TALER_MERCHANT_ORDER_PAY_OPTION_COINS (npay_coins,
+ pay_coins));
+ if (len_use_tokens > 0)
+ ADD (TALER_MERCHANT_ORDER_PAY_OPTION_INPUT_TOKENS (len_use_tokens,
+ use_tokens));
+ if (len_output_tokens > 0)
+ ADD (TALER_MERCHANT_ORDER_PAY_OPTION_OUTPUT_TOKENS (len_output_tokens,
+ output_tokens));
+
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+ if (ps->donau_data.charity_reference)
+ {
+ ADD (
+ TALER_MERCHANT_ORDER_PAY_OPTION_DONAU_URL (ps->donau_data.donau_url));
+ ADD (TALER_MERCHANT_ORDER_PAY_OPTION_DONAU_YEAR (ps->donau_data.year));
+ ADD (TALER_MERCHANT_ORDER_PAY_OPTION_DONAU_BUDIS (
+ ps->donau_data.budis_json));
+ }
+#endif
+ ADD (TALER_MERCHANT_ORDER_PAY_OPTION_TERMINATE ());
+#undef ADD
+
+ if (TALER_MERCHANT_OPOEC_OK !=
+ TALER_MERCHANT_order_pay_set_options (ps->oph,
+ opts,
+ oi))
+ TALER_TESTING_FAIL (is);
+
+ if (TALER_MERCHANT_OPOEC_OK !=
+ TALER_MERCHANT_order_pay_start (ps->oph))
+ TALER_TESTING_FAIL (is);
+ }
+
GNUNET_array_grow (pay_coins,
npay_coins,
0);
- if (NULL == ps->oph)
- TALER_TESTING_FAIL (is);
+
+ GNUNET_array_grow (use_tokens,
+ len_use_tokens,
+ 0);
+
+ GNUNET_array_grow (output_tokens,
+ len_output_tokens,
+ 0);
}
@@ -964,7 +1427,7 @@ pay_cleanup (void *cls,
"Command `%s' did not complete.\n",
TALER_TESTING_interpreter_get_current_label (
ps->is));
- TALER_MERCHANT_order_pay_cancel (ps->oph);
+ TALER_MERCHANT_order_pay_cancel1 (ps->oph);
}
GNUNET_free (ps);
@@ -1062,16 +1525,17 @@ pay_traits (void *cls,
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_pay_order_choices (const char *label,
- const char *merchant_url,
- unsigned int http_status,
- const char *proposal_reference,
- const char *coin_reference,
- const char *amount_with_fee,
- const char *amount_without_fee,
- const char *session_id,
- int choice_index,
- const char *token_reference)
+TALER_TESTING_cmd_merchant_pay_order_choices (
+ const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *proposal_reference,
+ const char *coin_reference,
+ const char *amount_with_fee,
+ const char *amount_without_fee,
+ const char *session_id,
+ int choice_index,
+ const char *token_reference)
{
struct PayState *ps;
@@ -1099,26 +1563,94 @@ TALER_TESTING_cmd_merchant_pay_order_choices (const char *label,
}
+#ifdef HAVE_DONAU_DONAU_SERVICE_H
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_pay_order_donau (
+ const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *proposal_reference,
+ const char *coin_reference,
+ const char *amount_with_fee,
+ const char *amount_without_fee,
+ const char *amount_donation,
+ const char *session_id,
+ int choice_index,
+ const char *charity_reference,
+ uint64_t year,
+ const char *donor_tax_id,
+ const char *salt)
+{
+ struct PayState *ps;
+
+ ps = GNUNET_new (struct PayState);
+ ps->http_status = http_status;
+ ps->proposal_reference = proposal_reference;
+ ps->coin_reference = coin_reference;
+ ps->merchant_url = merchant_url;
+ ps->amount_with_fee = amount_with_fee;
+ ps->amount_without_fee = amount_without_fee;
+ ps->session_id = session_id;
+ ps->token_reference = NULL;
+ ps->choice_index = choice_index;
+ ps->donau_data.year = year;
+ ps->donau_data.num_bkps = 5;
+ ps->donau_data.charity_reference = charity_reference;
+ if (GNUNET_OK !=
+ TALER_string_to_amount (amount_donation,
+ &ps->donau_data.donation_amount))
+ {
+ GNUNET_assert (0);
+ }
+
+ /* Compute h_donor_tax_id directly into ps->donau_data: */
+ if (! DONAU_compute_salted_tax_id_hash (donor_tax_id,
+ salt,
+ ps->donau_data.h_donor_tax_id.hash))
+ {
+ GNUNET_assert (0);
+ }
+
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ps,
+ .label = label,
+ .run = &pay_run,
+ .cleanup = &pay_cleanup,
+ .traits = &pay_traits
+ };
+
+ return cmd;
+ }
+}
+
+
+#endif /* HAVE_DONAU_DONAU_SERVICE_H */
+
+
struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_pay_order (const char *label,
- const char *merchant_url,
- unsigned int http_status,
- const char *proposal_reference,
- const char *coin_reference,
- const char *amount_with_fee,
- const char *amount_without_fee,
- const char *session_id)
+TALER_TESTING_cmd_merchant_pay_order (
+ const char *label,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *proposal_reference,
+ const char *coin_reference,
+ const char *amount_with_fee,
+ const char *amount_without_fee,
+ const char *session_id)
{
- return TALER_TESTING_cmd_merchant_pay_order_choices (label,
- merchant_url,
- http_status,
- proposal_reference,
- coin_reference,
- amount_with_fee,
- amount_without_fee,
- session_id,
- -1,
- NULL);
+ return TALER_TESTING_cmd_merchant_pay_order_choices (
+ label,
+ merchant_url,
+ http_status,
+ proposal_reference,
+ coin_reference,
+ amount_with_fee,
+ amount_without_fee,
+ session_id,
+ -1,
+ NULL);
}
diff --git a/src/testing/testing_api_cmd_post_donau_charity_merchant.c b/src/testing/testing_api_cmd_post_donau_charity_merchant.c
@@ -0,0 +1,259 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020-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 testing_api_cmd_post_donau_charity_merchant.c
+ * @brief command to test POST /donau charity with merchant_pub
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include <taler/taler_signatures.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+#include "taler_merchant_donau.h"
+#include <donau/donau_service.h>
+#include <donau/donau_testing_lib.h>
+
+
+/**
+ * State for a "status" CMD.
+ */
+struct StatusState
+{
+ /**
+ * Handle to the "charity status" operation.
+ */
+ struct DONAU_CharityPostHandle *cph;
+
+ /**
+ * The charity POST request.
+ */
+ struct DONAU_CharityRequest charity_req;
+
+ /**
+ * The bearer token for authorization.
+ */
+ const struct DONAU_BearerToken *bearer;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int expected_response_code;
+
+ /**
+ * Interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * charity id
+ */
+ uint64_t charity_id;
+
+ /**
+ * Merchant reference to fetch public key.
+ */
+ const char *merchant_reference;
+};
+
+/**
+ * Offer internal data from a CMD, to other commands.
+ *
+ * @param cls closure.
+ * @param[out] ret result.
+ * @param trait name of the trait.
+ * @param index index number of the object to offer.
+ * @return #GNUNET_OK on success.
+ */
+static enum GNUNET_GenericReturnValue
+charity_post_traits (void *cls,
+ const void **ret,
+ const char *trait,
+ unsigned int index)
+{
+ struct StatusState *ss = cls;
+ struct TALER_TESTING_Trait traits[] = {
+ TALER_TESTING_make_trait_charity_pub (&ss->charity_req.charity_pub),
+ TALER_TESTING_make_trait_charity_id (&ss->charity_id),
+ TALER_TESTING_trait_end ()
+ };
+
+ return TALER_TESTING_get_trait (traits,
+ ret,
+ trait,
+ index);
+}
+
+
+/**
+ * Check that the reserve balance and HTTP response code are
+ * both acceptable.
+ *
+ * @param cls closure.
+ * @param gcr HTTP response details
+ */
+static void
+charity_status_cb (void *cls,
+ const struct DONAU_PostCharityResponse *gcr)
+{
+ struct StatusState *ss = cls;
+
+ ss->cph = NULL;
+ if (ss->expected_response_code != gcr->hr.http_status)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Unexpected HTTP response code: %d in %s:%u\n",
+ gcr->hr.http_status,
+ __FILE__,
+ __LINE__);
+ json_dumpf (gcr->hr.reply,
+ stderr,
+ 0);
+ TALER_TESTING_interpreter_fail (ss->is);
+ return;
+ }
+ if (ss->expected_response_code == gcr->hr.http_status)
+ ss->charity_id = (unsigned long long) gcr->details.ok.charity_id;
+ TALER_TESTING_interpreter_next (ss->is);
+}
+
+
+/**
+ * Run the command.
+ *
+ * @param cls closure.
+ * @param cmd the command being executed.
+ * @param is the interpreter state.
+ */
+static void
+charity_status_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct StatusState *ss = cls;
+
+ ss->is = is;
+
+ if (NULL != ss->merchant_reference)
+ {
+ const struct TALER_TESTING_Command *mc;
+ const struct TALER_MerchantPublicKeyP *mpub;
+
+ mc = TALER_TESTING_interpreter_lookup_command (is,
+ ss->merchant_reference);
+ GNUNET_assert (NULL != mc);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_merchant_pub (mc,
+ &mpub));
+
+ ss->charity_req.charity_pub.eddsa_pub = mpub->eddsa_pub;
+ }
+
+ ss->cph = DONAU_charity_post (
+ TALER_TESTING_interpreter_get_context (is),
+ TALER_TESTING_get_donau_url (is),
+ &ss->charity_req,
+ ss->bearer,
+ &charity_status_cb,
+ ss);
+}
+
+
+/**
+ * Cleanup the state from a "reserve status" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd the command which is being cleaned up.
+ */
+static void
+cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct StatusState *ss = cls;
+
+ if (NULL != ss->cph)
+ {
+ // log incomplete command
+ TALER_TESTING_command_incomplete (ss->is,
+ cmd->label);
+ DONAU_charity_post_cancel (ss->cph);
+ ss->cph = NULL;
+ }
+ GNUNET_free (ss);
+}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_charity_post_merchant (const char *label,
+ const char *name,
+ const char *url,
+ const char *max_per_year,
+ const char *receipts_to_date,
+ uint64_t current_year,
+ const struct DONAU_BearerToken *bearer,
+ const char *merchant_reference,
+ unsigned int expected_response_code)
+{
+ struct StatusState *ss;
+
+ ss = GNUNET_new (struct StatusState);
+
+ ss->merchant_reference = merchant_reference;
+ ss->charity_req.name = name;
+ ss->charity_req.charity_url = url;
+ // parse string max_per_year to amount
+ if (GNUNET_OK !=
+ TALER_string_to_amount (max_per_year,
+ &ss->charity_req.max_per_year))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' at %s\n",
+ max_per_year,
+ label);
+ GNUNET_assert (0);
+ }
+ // parse string receipts_to_date to amount
+ if (GNUNET_OK !=
+ TALER_string_to_amount (receipts_to_date,
+ &ss->charity_req.receipts_to_date))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to parse amount `%s' at %s\n",
+ receipts_to_date,
+ label);
+ GNUNET_assert (0);
+ }
+ ss->charity_req.current_year = current_year;
+ ss->expected_response_code = expected_response_code;
+ ss->bearer = bearer;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ss,
+ .label = label,
+ .run = &charity_status_run,
+ .cleanup = &cleanup,
+ .traits = &charity_post_traits
+ };
+
+ return cmd;
+ }
+}
diff --git a/src/testing/testing_api_cmd_post_donau_instances.c b/src/testing/testing_api_cmd_post_donau_instances.c
@@ -0,0 +1,254 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020-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 testing_api_cmd_post_donau_instances.c
+ * @brief command to test POST /donau request
+ * @author Bohdan Potuzhnyi
+ * @author Vlada Svirsh
+ */
+
+#include "platform.h"
+#include <taler/taler_exchange_service.h>
+#include <taler/taler_testing_lib.h>
+#include "taler_merchant_service.h"
+#include "taler_merchant_testing_lib.h"
+#include "taler_merchant_donau.h"
+#include <donau/donau_testing_lib.h>
+
+/**
+ * State of a "POST /donau" CMD.
+ */
+struct PostDonauState
+{
+ /**
+ * Handle for a "POST donau" request.
+ */
+ struct TALER_MERCHANT_DonauInstancePostHandle *dph;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * Charity details.
+ */
+ struct TALER_MERCHANT_DONAU_Charity charity;
+
+ /**
+ * Merchant reference to fetch public key.
+ */
+ const char *merchant_reference;
+
+ /**
+ * Authentication token for the request.
+ */
+ const char *auth_token;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+};
+
+/**
+ * Callback for a POST /donau operation.
+ *
+ * @param cls closure for this function
+ * @param hr response being processed
+ */
+static void
+post_donau_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr)
+{
+ struct PostDonauState *pds = cls;
+
+ pds->dph = NULL;
+ if (pds->http_status != hr->http_status)
+ {
+ TALER_TESTING_unexpected_status_with_body (
+ pds->is,
+ hr->http_status,
+ pds->http_status,
+ hr->reply);
+ TALER_TESTING_interpreter_fail (pds->is);
+ return;
+ }
+
+ switch (hr->http_status)
+ {
+ case MHD_HTTP_NO_CONTENT:
+ break;
+ case MHD_HTTP_BAD_REQUEST:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "POST /donau returned BAD REQUEST: %s\n",
+ json_dumps (hr->reply, JSON_INDENT (2)));
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "POST /donau returned UNAUTHORIZED\n");
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "POST /donau returned NOT FOUND\n");
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unhandled HTTP status %u for POST /donau\n",
+ hr->http_status);
+ }
+ TALER_TESTING_interpreter_next (pds->is);
+}
+
+
+/**
+ * Run the "POST /donau" CMD.
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+post_donau_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PostDonauState *pds = cls;
+
+ pds->is = is;
+ pds->charity.charity_url = TALER_TESTING_get_donau_url (is);
+ if (NULL != pds->merchant_reference)
+ {
+ const struct TALER_TESTING_Command *mc;
+ const struct TALER_MerchantPublicKeyP *merchant_pub;
+
+ mc = TALER_TESTING_interpreter_lookup_command (is,
+ pds->merchant_reference);
+ GNUNET_assert (NULL != mc);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_TESTING_get_trait_merchant_pub (mc,
+ &merchant_pub));
+ pds->charity.charity_pub.eddsa_pub = merchant_pub->eddsa_pub;
+ }
+
+ pds->dph = TALER_MERCHANT_donau_instances_post (
+ TALER_TESTING_interpreter_get_context (is),
+ pds->merchant_url,
+ &pds->charity,
+ pds->auth_token,
+ &post_donau_cb,
+ pds);
+
+ if (NULL == pds->dph)
+ {
+ GNUNET_break (0);
+ TALER_TESTING_interpreter_fail (pds->is);
+ return;
+ }
+}
+
+
+/**
+ * Free the state of a "POST /donau" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+post_donau_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PostDonauState *pds = cls;
+
+ if (NULL != pds->dph)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "POST /donau operation did not complete\n");
+ TALER_MERCHANT_donau_instances_post_cancel (pds->dph);
+ }
+ GNUNET_free (pds);
+}
+
+
+/**
+ * Create a new testing command for POST /donau.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_donau_instance (const char *label,
+ const char *url,
+ const char *merchant_reference,
+ unsigned int
+ expected_http_status,
+ ...)
+{
+ struct PostDonauState *pds = GNUNET_new (struct PostDonauState);
+ struct DONAU_CharityPublicKeyP *charity_pub = GNUNET_new (struct
+ DONAU_CharityPublicKeyP);
+
+ struct TALER_Amount max_amount;
+ struct TALER_Amount date_amount;
+
+ const char*mamount = "EUR:100";
+ const char*damount = "EUR:20";
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (mamount,
+ &max_amount));
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (damount,
+ &date_amount));
+
+ {
+ struct TALER_MERCHANT_DONAU_Charity charity = {
+ .charity_url = "http://replaced.in.post_donau_run/",
+ .name = "example",
+ .charity_pub = *charity_pub,
+ .charity_id = 1,
+ .max_per_year = max_amount,
+ .receipts_to_date = date_amount,
+ .current_year = 2025
+ };
+ GNUNET_free (charity_pub);
+
+ pds->merchant_reference = merchant_reference;
+ pds->merchant_url = url;
+ pds->charity = charity;
+ pds->http_status = expected_http_status;
+ pds->auth_token = NULL;
+
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = pds,
+ .label = label,
+ .run = &post_donau_run,
+ .cleanup = &post_donau_cleanup
+ };
+
+ return cmd;
+ }
+
+ }
+}
+\ No newline at end of file
diff --git a/src/testing/testing_api_cmd_post_orders.c b/src/testing/testing_api_cmd_post_orders.c
@@ -986,3 +986,82 @@ TALER_TESTING_cmd_merchant_post_orders_choices (
return cmd;
}
}
+
+
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_post_orders_donau (
+ const char *label,
+ const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *merchant_url,
+ unsigned int http_status,
+ const char *order_id,
+ struct GNUNET_TIME_Timestamp refund_deadline,
+ struct GNUNET_TIME_Timestamp pay_deadline,
+ const char *amount)
+{
+ struct OrdersState *ps;
+ struct TALER_Amount brutto;
+ json_t *choice;
+ json_t *choices;
+ json_t *outputs;
+
+ ps = GNUNET_new (struct OrdersState);
+ ps->cfg = cfg;
+ make_order_json (order_id,
+ refund_deadline,
+ pay_deadline,
+ NULL,
+ &ps->order_terms);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (amount,
+ &brutto));
+
+ outputs = json_array ();
+ GNUNET_assert (NULL != outputs);
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ outputs,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "tax-receipt")
+ )));
+ choice
+ = GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("amount",
+ &brutto),
+ GNUNET_JSON_pack_array_steal ("outputs",
+ outputs));
+ choices = json_array ();
+ GNUNET_assert (NULL != choices);
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ choices,
+ choice));
+ GNUNET_assert (0 ==
+ json_object_set_new (ps->order_terms,
+ "choices",
+ choices)
+ );
+ GNUNET_assert (0 ==
+ json_object_set_new (ps->order_terms,
+ "version",
+ json_integer (1))
+ );
+
+
+ ps->http_status = http_status;
+ ps->expected_order_id = order_id;
+ ps->merchant_url = merchant_url;
+ ps->with_claim = true;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = ps,
+ .label = label,
+ .run = &orders_run3,
+ .cleanup = &orders_cleanup,
+ .traits = &orders_traits
+ };
+
+ return cmd;
+ }
+}
diff --git a/src/util/contract_parse.c b/src/util/contract_parse.c
@@ -34,7 +34,7 @@
*
* @param cls closure, unused parameter
* @param root the JSON object representing data
- * @param[out] spec where to write the data
+ * @param[out] ospec where to write the data
* @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
*/
static enum GNUNET_GenericReturnValue
@@ -545,7 +545,7 @@ contract_choice_free (
* @param cls closure, unused parameter
* @param root the JSON object representing data
* @param[out] ospec where to write the data
- * @return #GNUNET_OK upon successful parsing; @GNUNET_SYSERR upon error
+ * @return #GNUNET_OK upon successful parsing; #GNUNET_SYSERR upon error
*/
static enum GNUNET_GenericReturnValue
parse_token_details (void *cls,
@@ -693,7 +693,7 @@ parse_token_details (void *cls,
* in the contract token family. All fields from @a family are copied.
*
* @param name name of the token details field in the JSON
- * @param[out] token family where the token details have to be written
+ * @param[out] family token_family where the token details have to be written
*/
static struct GNUNET_JSON_Specification
spec_token_details (const char *name,