merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit f97dbdb7497e2d156f424321d283c209836f33bb
parent 8b8e4f45737a436702a504e1464a920098fdd0f8
Author: Christian Grothoff <grothoff@gnunet.org>
Date:   Mon, 11 Aug 2025 12:56:45 +0200

fix merge conflict

Diffstat:
Mdebian/taler-merchant.postinst | 3+++
Adebian/taler-merchant.taler-merchant-donaukeyupdate.service | 18++++++++++++++++++
Msrc/backend/Makefile.am | 37++++++++++++++++++++++++++++++++++++-
Msrc/backend/taler-merchant-depositcheck.c | 2+-
Asrc/backend/taler-merchant-donaukeyupdate.c | 1045+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/backend/taler-merchant-httpd.c | 28+++++++++++++++++++++++++++-
Msrc/backend/taler-merchant-httpd.h | 2+-
Msrc/backend/taler-merchant-httpd_post-orders-ID-pay.c | 1398+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Asrc/backend/taler-merchant-httpd_private-delete-donau-instance-ID.c | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/taler-merchant-httpd_private-delete-donau-instance-ID.h | 44++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/taler-merchant-httpd_private-get-donau-instances.c | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/taler-merchant-httpd_private-get-donau-instances.h | 42++++++++++++++++++++++++++++++++++++++++++
Msrc/backend/taler-merchant-httpd_private-get-incoming.c | 2+-
Msrc/backend/taler-merchant-httpd_private-get-instances-ID-tokens.c | 7+++++--
Msrc/backend/taler-merchant-httpd_private-get-statistics-amount-SLUG.c | 10+++++-----
Msrc/backend/taler-merchant-httpd_private-get-statistics-counter-SLUG.c | 6+++---
Msrc/backend/taler-merchant-httpd_private-get-transfers.c | 4++--
Asrc/backend/taler-merchant-httpd_private-post-donau-instance.c | 270+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/taler-merchant-httpd_private-post-donau-instance.h | 41+++++++++++++++++++++++++++++++++++++++++
Msrc/backend/taler-merchant-httpd_private-post-orders.c | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Msrc/backenddb/Makefile.am | 23+++++++++++++++++++++++
Asrc/backenddb/merchant-0023.sql | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_delete_donau_instance.c | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_delete_donau_instance.h | 43+++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_insert_donau_instance.c | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_insert_donau_instance.h | 46++++++++++++++++++++++++++++++++++++++++++++++
Msrc/backenddb/pg_insert_login_token.h | 1+
Asrc/backenddb/pg_insert_order_blinded_sigs.c | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_insert_order_blinded_sigs.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_lookup_donau_keys.c | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_lookup_donau_keys.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_lookup_order_charity.c | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_lookup_order_charity.h | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_select_donau_instance_by_serial.c | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_select_donau_instance_by_serial.h | 46++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_select_donau_instances.c | 176+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_select_donau_instances.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_select_donau_instances_filtered.c | 132+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_select_donau_instances_filtered.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_select_order_blinded_sigs.c | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_select_order_blinded_sigs.h | 47+++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_update_donau_instance.c | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_update_donau_instance.h | 46++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_update_donau_instance_receipts_amount.c | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_update_donau_instance_receipts_amount.h | 44++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_upsert_donau_keys.c | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backenddb/pg_upsert_donau_keys.h | 44++++++++++++++++++++++++++++++++++++++++++++
Msrc/backenddb/plugin_merchantdb_postgres.c | 44++++++++++++++++++++++++++++++++++++++++++--
Msrc/include/Makefile.am | 6++++++
Asrc/include/cutted_from_service | 156+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/include/taler_merchant_donau.h | 397+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/include/taler_merchant_pay_service.h | 613+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/include/taler_merchant_service.h | 11+++++------
Msrc/include/taler_merchant_testing_lib.h | 163++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/include/taler_merchant_util.h | 8+-------
Msrc/include/taler_merchantdb_plugin.h | 215+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib/Makefile.am | 13++++++++++++-
Asrc/lib/merchant_api_delete_donau_instance.c | 199+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/merchant_api_get_donau_instance.c | 286+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib/merchant_api_get_statistics.c | 6+++++-
Asrc/lib/merchant_api_post_donau_instance.c | 238+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/taler_merchant_pay_service.c | 1060+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/testing/Makefile.am | 23+++++++++++++++++++++++
Msrc/testing/test_merchant_api-rsa.conf | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/testing/test_merchant_api.c | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/testing/test_merchant_api.conf | 39+++++++++++++++++++++++++++++++++++++++
Asrc/testing/testing_api_cmd_delete_donau_instances.c | 190+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/testing/testing_api_cmd_get_donau_instances.c | 184+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/testing/testing_api_cmd_get_statisticsamount.c | 2+-
Msrc/testing/testing_api_cmd_get_statisticscounter.c | 2+-
Msrc/testing/testing_api_cmd_pay_order.c | 762+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Asrc/testing/testing_api_cmd_post_donau_charity_merchant.c | 259+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/testing/testing_api_cmd_post_donau_instances.c | 255+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/testing/testing_api_cmd_post_orders.c | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/util/contract_parse.c | 6+++---
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", + &current_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,