merchant

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

commit 4f070c63034224197f070c009e5b29b11fcc87e7
parent 4e9b06c237beb9f99d805060e5dfcb2a5da2a1ef
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date:   Mon, 28 Apr 2025 14:47:46 +0200

Merge branch 'master' into dev/bohdan-potuzhnyi/donau-integration

Diffstat:
Mconfigure.ac | 4++++
Mcontrib/merchant-spa.lock | 2+-
Mdebian/changelog | 22+++++++++++++++++++---
Mdebian/taler-merchant.postinst | 17+++++++++++++++++
Mdebian/taler-merchant.prerm | 13+++++++++++++
Mdebian/taler-merchant.taler-merchant-dbinit-gc.service | 4++++
Mdebian/taler-merchant.taler-merchant-depositcheck.service | 4++++
Mdebian/taler-merchant.taler-merchant-httpd.service | 2++
Mdebian/taler-merchant.taler-merchant-kyccheck.service | 4++++
Mdebian/taler-merchant.taler-merchant-reconciliation.service | 3+++
Mdebian/taler-merchant.taler-merchant-webhook.service | 3+++
Mdebian/taler-merchant.taler-merchant-wirewatch.service | 4++++
Mdoc/doxygen/taler.doxy | 2+-
Msrc/backend/taler-merchant-depositcheck.c | 2++
Msrc/backend/taler-merchant-exchangekeyupdate.c | 43+++++++++++++++++++++++++++++++++----------
Msrc/backend/taler-merchant-httpd_exchanges.c | 9+++++++++
Msrc/backend/taler-merchant-httpd_post-orders-ID-pay.c | 11++++++-----
Msrc/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/backend/taler-merchant-kyccheck.c | 2++
Msrc/backend/taler-merchant-reconciliation.c | 2++
Msrc/backenddb/Makefile.am | 4++++
Msrc/backenddb/merchant-0016.sql | 69+++++----------------------------------------------------------------
Asrc/backenddb/merchant-0017.sql | 35+++++++++++++++++++++++++++++++++++
Asrc/backenddb/merchant-0018.sql | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/backenddb/pg_insert_exchange_keys.c | 15++++++++++-----
Msrc/backenddb/pg_insert_exchange_keys.h | 7+++++--
Msrc/backenddb/pg_select_exchange_keys.c | 6+++++-
Msrc/backenddb/pg_select_exchange_keys.h | 2++
Msrc/include/taler_merchantdb_plugin.h | 6+++++-
29 files changed, 348 insertions(+), 93 deletions(-)

diff --git a/configure.ac b/configure.ac @@ -18,7 +18,11 @@ # This configure file is in the public domain AC_PREREQ([2.69]) +<<<<<<< HEAD AC_INIT([taler-merchant],[0.14.9],[taler-bug@gnunet.org]) +======= +AC_INIT([taler-merchant],[0.14.11],[taler-bug@gnunet.org]) +>>>>>>> master AC_CONFIG_SRCDIR([src/backend/taler-merchant-httpd.c]) AC_CONFIG_HEADERS([taler_merchant_config.h]) # support for non-recursive builds diff --git a/contrib/merchant-spa.lock b/contrib/merchant-spa.lock @@ -1 +1 @@ -0.14.13-dev.4 +0.14.15-dev.3 diff --git a/debian/changelog b/debian/changelog @@ -1,8 +1,24 @@ +taler-merchant (0.14.11) unstable; urgency=low + + * Fix retries when updating exchange keys in database + * Update to latest SPA + * log to journald + * Release version 0.14.11 + + -- Christian Grothoff <grothoff@gnu.org> Sun, 27 Apr 2025 23:50:23 +0200 + +taler-merchant (0.14.10) unstable; urgency=low + + * Release version 0.14.10 + + -- Christian Grothoff <grothoff@gnu.org> Fri, 18 Apr 2025 13:50:23 +0200 + taler-merchant (0.14.9) unstable; urgency=low + * various SPA fixes * Release version 0.14.9 - -- Christian Grothoff <grothoff@gnu.org> Wed, 23 Apr 2025 16:51:13 +0200 + -- Christian Grothoff <grothoff@gnu.org> Sun, 13 Apr 2025 13:50:23 +0200 taler-merchant (0.14.8) unstable; urgency=low @@ -105,13 +121,13 @@ taler-merchant (0.11.3) unstable; urgency=low * Remove legacy user-type from API. * Importing bank account info. - -- Sebastian Marchano <sebasjm@taler.net> Mon 3 Jun 2024 13:20:12 -0300 + -- Sebastian Marchano <sebasjm@taler.net> Mon, 3 Jun 2024 13:20:12 -0300 taler-merchant (0.11.2) unstable; urgency=low * Fixed bad bug in SQL procedure. - -- Christian Grothoff <grothoff@gnu.org> Wed 22 Apr 2024 19:50:12 +0200 + -- Christian Grothoff <grothoff@gnu.org> Wed, 22 Apr 2024 19:50:12 +0200 taler-merchant (0.11.1) unstable; urgency=low diff --git a/debian/taler-merchant.postinst b/debian/taler-merchant.postinst @@ -2,6 +2,8 @@ set -e +MARKER="/run/taler/merchant.was-enabled" + if [ -d /run/systemd/system ]; then systemctl --system daemon-reload >/dev/null || true fi @@ -62,6 +64,21 @@ configure) taler-merchant-httpd root 460 \ /etc/taler-merchant/secrets/merchant-db.secret.conf fi + + if [ -x /usr/bin/taler-merchant-dbinit ]; then + /usr/bin/taler-merchant-dbinit >/dev/null 2>&1 || true + fi + + if [ -f "$MARKER" ] && grep -q "enabled" "$MARKER"; then + echo "taler-merchant-httpd was previously enabled, running DB config." + + systemctl enable --now taler-merchant.target || true + else + echo "Not enabling or starting Taler merchant services (marker not found or was 'disabled')." + fi + + # Cleanup marker file + rm -f "$MARKER" ;; abort-upgrade | abort-remove | abort-deconfigure) ;; diff --git a/debian/taler-merchant.prerm b/debian/taler-merchant.prerm @@ -2,8 +2,21 @@ set -e +MARKER="/run/taler/merchant.was-enabled" + if [ -d /run/systemd/system ] && [ "$1" = remove ]; then deb-systemd-invoke stop 'taler-merchant*' >/dev/null || true fi +if [ -d /run/systemd/system ] && [ "$1" = upgrade ]; then + + if systemctl is-enabled taler-merchant-httpd.service >/dev/null 2>&1; then + echo "taler-merchant-httpd was enabled before upgrade/remove." + mkdir -p /run/taler + echo "enabled" > "$MARKER" + fi + + systemctl disable --now taler-merchant.target || true +fi + exit 0 diff --git a/debian/taler-merchant.taler-merchant-dbinit-gc.service b/debian/taler-merchant.taler-merchant-dbinit-gc.service @@ -4,3 +4,6 @@ Description=Job to remove stale data from the taler-merchant database (run as a [Service] Type=simple ExecStart=taler-merchant-dbinit -c /etc/taler-merchant/taler-merchant.conf -g + +StandardOutput=journal +StandardError=journal +\ No newline at end of file diff --git a/debian/taler-merchant.taler-merchant-depositcheck.service b/debian/taler-merchant.taler-merchant-depositcheck.service @@ -15,3 +15,6 @@ PrivateDevices=yes ProtectSystem=full RuntimeMaxSec=3600s Slice=taler-merchant.slice + +StandardOutput=journal +StandardError=journal +\ No newline at end of file diff --git a/debian/taler-merchant.taler-merchant-httpd.service b/debian/taler-merchant.taler-merchant-httpd.service @@ -14,6 +14,8 @@ RestartPreventExitStatus=9 RuntimeMaxSec=3600s ExecStart=/usr/bin/taler-merchant-httpd -c /etc/taler-merchant/taler-merchant.conf -L INFO Slice=taler-merchant.slice +StandardOutput=journal +StandardError=journal [Install] WantedBy=multi-user.target diff --git a/debian/taler-merchant.taler-merchant-kyccheck.service b/debian/taler-merchant.taler-merchant-kyccheck.service @@ -15,3 +15,6 @@ PrivateDevices=yes ProtectSystem=full RuntimeMaxSec=3600s Slice=taler-merchant.slice + +StandardOutput=journal +StandardError=journal +\ No newline at end of file diff --git a/debian/taler-merchant.taler-merchant-reconciliation.service b/debian/taler-merchant.taler-merchant-reconciliation.service @@ -15,3 +15,6 @@ PrivateDevices=yes ProtectSystem=full RuntimeMaxSec=3600s Slice=taler-merchant.slice + +StandardOutput=journal +StandardError=journal diff --git a/debian/taler-merchant.taler-merchant-webhook.service b/debian/taler-merchant.taler-merchant-webhook.service @@ -15,3 +15,6 @@ PrivateDevices=yes ProtectSystem=full RuntimeMaxSec=3600s Slice=taler-merchant.slice + +StandardOutput=journal +StandardError=journal diff --git a/debian/taler-merchant.taler-merchant-wirewatch.service b/debian/taler-merchant.taler-merchant-wirewatch.service @@ -16,3 +16,6 @@ ProtectSystem=full RuntimeMaxSec=3600s Slice=taler-merchant.slice + +StandardOutput=journal +StandardError=journal +\ No newline at end of file diff --git a/doc/doxygen/taler.doxy b/doc/doxygen/taler.doxy @@ -5,7 +5,7 @@ #--------------------------------------------------------------------------- DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = "GNU Taler: Merchant" -PROJECT_NUMBER = 0.14.9 +PROJECT_NUMBER = 0.14.11 PROJECT_LOGO = logo.svg OUTPUT_DIRECTORY = . CREATE_SUBDIRS = YES diff --git a/src/backend/taler-merchant-depositcheck.c b/src/backend/taler-merchant-depositcheck.c @@ -950,6 +950,7 @@ update_exchange_keys (void *cls, { enum GNUNET_DB_QueryStatus qs; + struct GNUNET_TIME_Absolute earliest_retry; if (NULL != keys) { @@ -958,6 +959,7 @@ update_exchange_keys (void *cls, } qs = db_plugin->select_exchange_keys (db_plugin->cls, exchange_url, + &earliest_retry, &keys); if (qs < 0) { diff --git a/src/backend/taler-merchant-exchangekeyupdate.c b/src/backend/taler-merchant-exchangekeyupdate.c @@ -287,10 +287,12 @@ add_restriction (json_t *restrictions, * depending on the return value. * * @param keys information to persist + * @param first_retry earliest we may retry fetching the keys * @return transaction status */ static enum GNUNET_DB_QueryStatus -insert_keys_data (const struct TALER_EXCHANGE_Keys *keys) +insert_keys_data (const struct TALER_EXCHANGE_Keys *keys, + struct GNUNET_TIME_Absolute first_retry) { enum GNUNET_DB_QueryStatus qs; @@ -317,7 +319,8 @@ insert_keys_data (const struct TALER_EXCHANGE_Keys *keys) } qs = db_plugin->insert_exchange_keys (db_plugin->cls, - keys); + keys, + first_retry); if (0 > qs) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); @@ -424,6 +427,9 @@ insert_keys_data (const struct TALER_EXCHANGE_Keys *keys) .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS) }; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Informing other processes about keys change for %s\n", + keys->exchange_url); db_plugin->event_notify (db_plugin->cls, &es, keys->exchange_url, @@ -439,10 +445,12 @@ insert_keys_data (const struct TALER_EXCHANGE_Keys *keys) * that may care about them). * * @param keys the keys to store + * @param first_retry earliest we may retry fetching the keys * @return true on success */ static bool -store_keys (struct TALER_EXCHANGE_Keys *keys) +store_keys (struct TALER_EXCHANGE_Keys *keys, + struct GNUNET_TIME_Absolute first_retry) { enum GNUNET_DB_QueryStatus qs; @@ -458,8 +466,9 @@ store_keys (struct TALER_EXCHANGE_Keys *keys) return false; } - qs = insert_keys_data (keys); - if (qs < 0) + qs = insert_keys_data (keys, + first_retry); + if (0 > qs) { db_plugin->rollback (db_plugin->cls); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) @@ -469,7 +478,7 @@ store_keys (struct TALER_EXCHANGE_Keys *keys) } qs = db_plugin->commit (db_plugin->cls); - if (qs < 0) + if (0 > qs) { db_plugin->rollback (db_plugin->cls); if (GNUNET_DB_STATUS_SOFT_ERROR == qs) @@ -477,6 +486,7 @@ store_keys (struct TALER_EXCHANGE_Keys *keys) GNUNET_break (0); return false; } + break; } /* end of retry loop */ if (qs < 0) { @@ -503,6 +513,7 @@ cert_cb ( { struct Exchange *e = cls; struct GNUNET_TIME_Absolute n; + struct GNUNET_TIME_Absolute first_retry; e->conn = NULL; switch (kr->hr.http_status) @@ -528,15 +539,22 @@ cert_cb ( e->exchange_url); break; } - if (! store_keys (keys)) + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Got new keys for %s, updating database\n", + e->exchange_url); + first_retry = GNUNET_TIME_relative_to_absolute ( + EXCHANGE_MAXFREQ); + if (! store_keys (keys, + first_retry)) + { + GNUNET_break (0); break; + } e->keys = TALER_EXCHANGE_keys_incref (keys); /* Reset back-off */ e->retry_delay = EXCHANGE_MAXFREQ; /* limit retry */ - e->first_retry - = GNUNET_TIME_relative_to_absolute ( - EXCHANGE_MAXFREQ); + e->first_retry = first_retry; /* Limit by expiration */ n = GNUNET_TIME_absolute_max (e->first_retry, keys->key_data_expiration.abs_time); @@ -589,6 +607,10 @@ download_keys (void *cls) } e->retry_delay = GNUNET_TIME_STD_BACKOFF (e->retry_delay); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Downloading keys from %s (%s)\n", + e->exchange_url, + e->force_retry ? "forced" : "regular"); e->conn = TALER_EXCHANGE_get_keys (ctx, e->exchange_url, e->force_retry @@ -815,6 +837,7 @@ accept_exchanges (void *cls, qs = db_plugin->select_exchange_keys (db_plugin->cls, url, + &e->first_retry, &keys); if (qs < 0) { diff --git a/src/backend/taler-merchant-httpd_exchanges.c b/src/backend/taler-merchant-httpd_exchanges.c @@ -520,6 +520,8 @@ TMH_EXCHANGES_keys4exchange ( (NULL != exchange->keys) ) { /* Return results immediately. */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Earliest retry is in the future, returning keys now\n"); fo->at = GNUNET_SCHEDULER_add_now (&return_keys, fo); /* *no* return here, we MAY schedule a 'retry_task' in the @@ -885,9 +887,11 @@ reload_exchange_keys (struct TMH_Exchange *exchange) { enum GNUNET_DB_QueryStatus qs; struct TALER_EXCHANGE_Keys *keys; + struct GNUNET_TIME_Absolute first_retry; qs = TMH_db->select_exchange_keys (TMH_db->cls, exchange->url, + &first_retry, &keys); if (qs < 0) { @@ -901,7 +905,11 @@ reload_exchange_keys (struct TMH_Exchange *exchange) exchange->url); return; } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Loading latest keys of `%s' from database\n", + exchange->url); exchange->retry_delay = GNUNET_TIME_UNIT_ZERO; + exchange->first_retry = first_retry; if (NULL == exchange->currency) exchange->currency = GNUNET_strdup (keys->currency); if (0 != strcmp (keys->currency, @@ -1191,6 +1199,7 @@ TMH_EXCHANGES_init (const struct GNUNET_CONFIGURATION_Handle *cfg) .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS) }; + GNUNET_assert (NULL == keys_eh); keys_eh = TMH_db->event_listen (TMH_db->cls, &es, GNUNET_TIME_UNIT_FOREVER_REL, diff --git a/src/backend/taler-merchant-httpd_post-orders-ID-pay.c b/src/backend/taler-merchant-httpd_post-orders-ID-pay.c @@ -1268,7 +1268,7 @@ process_pay_with_keys ( pc->batch_deposits.pending_at_eg--; GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id); GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Processing payment with exchange %s\n", + "Processing payment with keys from exchange %s\n", eg->exchange_url); GNUNET_assert (GNUNET_YES == pc->suspended); if (NULL == keys) @@ -1384,7 +1384,8 @@ process_pay_with_keys ( return; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Have missing denomination for exchange, updating %s\n", + "Missing denomination %s from exchange %s, updating keys\n", + GNUNET_h2s (&dc->cdd.h_denom_pub.hash), eg->exchange_url); force_keys (eg); return; @@ -1445,7 +1446,7 @@ AGE_FAIL: if (0 < code) { GNUNET_break_op (0); - GNUNET_free (dc->age_commitment.keys); + TALER_age_commitment_free (&dc->age_commitment); resume_pay_with_response ( pc, MHD_HTTP_BAD_REQUEST, @@ -1460,7 +1461,7 @@ AGE_FAIL: * Calculate the hash of the age commitment. */ TALER_age_commitment_hash (&dc->age_commitment, &dc->cdd.h_age_commitment); - GNUNET_free (dc->age_commitment.keys); + TALER_age_commitment_free (&dc->age_commitment); } else if (is_age_restricted_denom && dc->no_h_age_commitment) @@ -1572,7 +1573,7 @@ force_keys (struct ExchangeGroup *eg) eg->tried_force_keys = true; GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Forcing /keys download (once) as wire fees are unknown\n"); + "Forcing /keys download (once)\n"); eg->fo = TMH_EXCHANGES_keys4exchange ( eg->exchange_url, true, diff --git a/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c b/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c @@ -528,6 +528,61 @@ map_to_status (const struct ExchangeKycRequest *ekr) } if (ekr->kyc_ok) { + if (NULL != ekr->jlimits) + { + size_t off; + json_t *limit; + json_array_foreach (ekr->jlimits, off, limit) + { + struct TALER_Amount threshold; + enum TALER_KYCLOGIC_KycTriggerEvent operation_type; + bool soft = false; + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_kycte ("operation_type", + &operation_type), + TALER_JSON_spec_amount_any ("threshold", + &threshold), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("soft_limit", + &soft), + NULL), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (limit, + spec, + NULL, NULL)) + { + GNUNET_break (0); + return "merchant-internal-error"; + } + if (! TALER_amount_is_zero (&threshold)) + continue; /* only care about zero-limits */ + if (! soft) + continue; /* only care about soft limits */ + if ( (operation_type == TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT) || + (operation_type == TALER_KYCLOGIC_KYC_TRIGGER_AGGREGATE) || + (operation_type == TALER_KYCLOGIC_KYC_TRIGGER_TRANSACTION) ) + return "kyc-required"; + } + } + if (NULL == ekr->jlimits) + { + /* check default limits */ + const struct TALER_EXCHANGE_Keys *keys = ekr->keys; + + for (unsigned int i = 0; i < keys->zero_limits_length; i++) + { + enum TALER_KYCLOGIC_KycTriggerEvent operation_type + = keys->zero_limits[i].operation_type; + + if ( (operation_type == TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT) || + (operation_type == TALER_KYCLOGIC_KYC_TRIGGER_AGGREGATE) || + (operation_type == TALER_KYCLOGIC_KYC_TRIGGER_TRANSACTION) ) + return "kyc-required"; + } + } return "ready"; } if (! ekr->auth_ok) diff --git a/src/backend/taler-merchant-kyccheck.c b/src/backend/taler-merchant-kyccheck.c @@ -1077,9 +1077,11 @@ find_keys (const char *exchange_url) enum GNUNET_DB_QueryStatus qs; struct TALER_EXCHANGE_Keys *keys; struct Exchange *e; + struct GNUNET_TIME_Absolute first_retry; qs = db_plugin->select_exchange_keys (db_plugin->cls, exchange_url, + &first_retry, &keys); if (qs < 0) { diff --git a/src/backend/taler-merchant-reconciliation.c b/src/backend/taler-merchant-reconciliation.c @@ -325,9 +325,11 @@ sync_keys (struct Exchange *e) { enum GNUNET_DB_QueryStatus qs; struct TALER_EXCHANGE_Keys *keys; + struct GNUNET_TIME_Absolute first_retry; qs = db_plugin->select_exchange_keys (db_plugin->cls, e->exchange_url, + &first_retry, &keys); if (qs < 0) { diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am @@ -33,6 +33,10 @@ sql_DATA = \ merchant-0014.sql \ merchant-0015.sql \ merchant-0016.sql \ +<<<<<<< HEAD +======= + merchant-0017.sql \ +>>>>>>> master drop.sql BUILT_SOURCES = \ diff --git a/src/backenddb/merchant-0016.sql b/src/backenddb/merchant-0016.sql @@ -1,6 +1,6 @@ -- -- This file is part of TALER --- Copyright (C) 2024 Taler Systems SA +-- 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 @@ -15,9 +15,8 @@ -- -- @file merchant-0016.sql --- @brief Create table to store donau related information --- @author Bohdan Potuzhnyi --- @author Vlada Svirsh +-- @brief Rename default instance to admin instance +-- @author Florian Dold BEGIN; @@ -26,63 +25,6 @@ SELECT _v.register_patch('merchant-0016', 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 - ); +UPDATE merchant_instances SET merchant_id='admin' WHERE merchant_id='default'; -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'; - -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 - ,charity_pub_key BYTEA CHECK (LENGTH(charity_pub_key)=32) - ,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.charity_pub_key - IS 'The public key of the charity organization linked to this instance, with a 32-byte length constraint'; -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_donau -(order_donau_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY - ,order_serial BIGINT NOT NULL - REFERENCES merchant_orders (order_serial) ON DELETE CASCADE - ,donau_budis TEXT NOT NULL -); - -COMMENT ON TABLE merchant_order_donau - IS 'Table linking merchant orders with Donau BUDIS information'; -COMMENT ON COLUMN merchant_order_donau.order_donau_serial - IS 'Unique serial identifier for Donau order linkage'; -COMMENT ON COLUMN merchant_order_donau.order_serial - IS 'Foreign key linking to the corresponding merchant order'; -COMMENT ON COLUMN merchant_order_donau.donau_budis - IS 'Donau BUDIs json associated with the order'; - -COMMIT; -\ No newline at end of file +COMMIT; diff --git a/src/backenddb/merchant-0017.sql b/src/backenddb/merchant-0017.sql @@ -0,0 +1,35 @@ +-- +-- 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-0015.sql +-- @brief Tables for statistics +-- @author Christian Grothoff + + +BEGIN; + +-- Check patch versioning is in place. +SELECT _v.register_patch('merchant-0017', NULL, NULL); + +SET search_path TO merchant; + +ALTER TABLE merchant_exchange_keys + ADD first_retry INT8 NOT NULL DEFAULT (0); + +COMMENT ON COLUMN merchant_exchange_keys.first_retry + IS 'Absolute time when this merchant may retry to fetch the keys from this exchange at the earliest'; + +COMMIT; diff --git a/src/backenddb/merchant-0018.sql b/src/backenddb/merchant-0018.sql @@ -0,0 +1,88 @@ +-- +-- 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 merchant-0018.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-0018', 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 + ); + +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'; + +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 + ,charity_pub_key BYTEA CHECK (LENGTH(charity_pub_key)=32) + ,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.charity_pub_key + IS 'The public key of the charity organization linked to this instance, with a 32-byte length constraint'; +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_donau +(order_donau_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY + ,order_serial BIGINT NOT NULL + REFERENCES merchant_orders (order_serial) ON DELETE CASCADE + ,donau_budis TEXT NOT NULL +); + +COMMENT ON TABLE merchant_order_donau + IS 'Table linking merchant orders with Donau BUDIS information'; +COMMENT ON COLUMN merchant_order_donau.order_donau_serial + IS 'Unique serial identifier for Donau order linkage'; +COMMENT ON COLUMN merchant_order_donau.order_serial + IS 'Foreign key linking to the corresponding merchant order'; +COMMENT ON COLUMN merchant_order_donau.donau_budis + IS 'Donau BUDIs json associated with the order'; + +COMMIT; +\ No newline at end of file diff --git a/src/backenddb/pg_insert_exchange_keys.c b/src/backenddb/pg_insert_exchange_keys.c @@ -27,13 +27,16 @@ enum GNUNET_DB_QueryStatus -TMH_PG_insert_exchange_keys (void *cls, - const struct TALER_EXCHANGE_Keys *keys) +TMH_PG_insert_exchange_keys ( + void *cls, + const struct TALER_EXCHANGE_Keys *keys, + struct GNUNET_TIME_Absolute first_retry) { struct PostgresClosure *pg = cls; json_t *jkeys = TALER_EXCHANGE_keys_to_json (keys); struct GNUNET_PQ_QueryParam params[] = { TALER_PQ_query_param_json (jkeys), + GNUNET_PQ_query_param_absolute_time (&first_retry), GNUNET_PQ_query_param_timestamp (&keys->last_denom_issue_date), GNUNET_PQ_query_param_string (keys->exchange_url), GNUNET_PQ_query_param_end @@ -45,16 +48,18 @@ TMH_PG_insert_exchange_keys (void *cls, "insert_exchange_keys", "INSERT INTO merchant_exchange_keys" "(keys_json" + ",first_retry" ",expiration_time" ",exchange_url" - ") VALUES ($1, $2, $3);"); + ") VALUES ($1, $2, $3, $4);"); PREPARE (pg, "update_exchange_keys", "UPDATE merchant_exchange_keys SET" " keys_json=$1" - ",expiration_time=$2" + ",first_retry=$2" + ",expiration_time=$3" " WHERE" - " exchange_url=$3;"); + " exchange_url=$4;"); qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "update_exchange_keys", params); diff --git a/src/backenddb/pg_insert_exchange_keys.h b/src/backenddb/pg_insert_exchange_keys.h @@ -31,10 +31,13 @@ * * @param cls plugin closure * @param keys data to store + * @param first_retry earliest we may retry fetching the keys * @return transaction status */ enum GNUNET_DB_QueryStatus -TMH_PG_insert_exchange_keys (void *cls, - const struct TALER_EXCHANGE_Keys *keys); +TMH_PG_insert_exchange_keys ( + void *cls, + const struct TALER_EXCHANGE_Keys *keys, + struct GNUNET_TIME_Absolute first_retry); #endif diff --git a/src/backenddb/pg_select_exchange_keys.c b/src/backenddb/pg_select_exchange_keys.c @@ -29,6 +29,7 @@ enum GNUNET_DB_QueryStatus TMH_PG_select_exchange_keys (void *cls, const char *exchange_url, + struct GNUNET_TIME_Absolute *first_retry, struct TALER_EXCHANGE_Keys **keys) { struct PostgresClosure *pg = cls; @@ -38,6 +39,8 @@ TMH_PG_select_exchange_keys (void *cls, }; 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 @@ -48,7 +51,8 @@ TMH_PG_select_exchange_keys (void *cls, PREPARE (pg, "select_exchange_keys", "SELECT" - " keys_json" + " first_retry" + ",keys_json" " FROM merchant_exchange_keys" " WHERE exchange_url=$1;"); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, diff --git a/src/backenddb/pg_select_exchange_keys.h b/src/backenddb/pg_select_exchange_keys.h @@ -31,12 +31,14 @@ * * @param cls plugin closure * @param exchange_url base URL of the exchange + * @param[out] first_retry set to earliest we may retry fetching the keys * @param[out] keys set to the keys of the exchange * @return transaction status */ enum GNUNET_DB_QueryStatus TMH_PG_select_exchange_keys (void *cls, const char *exchange_url, + struct GNUNET_TIME_Absolute *first_retry, struct TALER_EXCHANGE_Keys **keys); diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h @@ -3679,12 +3679,14 @@ struct TALER_MERCHANTDB_Plugin * * @param cls plugin closure * @param exchange_url base URL of the exchange + * @param[out] first_retry set to earliest we may retry fetching the keys * @param[out] keys set to the keys of the exchange * @return transaction status */ enum GNUNET_DB_QueryStatus (*select_exchange_keys)(void *cls, const char *exchange_url, + struct GNUNET_TIME_Absolute *first_retry, struct TALER_EXCHANGE_Keys **keys); @@ -3693,11 +3695,13 @@ struct TALER_MERCHANTDB_Plugin * * @param cls plugin closure * @param keys data to store + * @param first_retry earliest we may retry fetching the keys * @return transaction status */ enum GNUNET_DB_QueryStatus (*insert_exchange_keys)(void *cls, - const struct TALER_EXCHANGE_Keys *keys); + const struct TALER_EXCHANGE_Keys *keys, + struct GNUNET_TIME_Absolute first_retry); /**