merchant

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

commit 5704af28059714cd5d0e65183dbf61d7d5bbaee1
parent 718ae1bf0cd58fe9fc962e629f0169c732674b36
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat, 10 Oct 2026 14:06:09 +0200

implement #11789

Diffstat:
Acontrib/instance_details.en.must | 9+++++++++
Mcontrib/meson.build | 1+
Msrc/backend/taler-merchant-httpd_get-management-instances-INSTANCE.c | 98++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/backenddb/pg_create_instance_trigger.sql | 8+++++++-
Asrc/backenddb/pg_merchant_send_signup_notification.sql | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/backenddb/sql-schema/meson.build | 1+
Msrc/testing/meson.build | 1+
Asrc/testing/test_merchant_signup_email.sh | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 292 insertions(+), 2 deletions(-)

diff --git a/contrib/instance_details.en.must b/contrib/instance_details.en.must @@ -0,0 +1,9 @@ +GNU Taler Merchant: Account Ready +================================= + +Your merchant account has been set up. Keep this message: +you need the user name below to log in. + +Log in at : {{{base_url}}} +User name : {{{id}}} +Business name : {{{name}}} diff --git a/contrib/meson.build b/contrib/meson.build @@ -21,6 +21,7 @@ install_data( install_data( 'kyc_text.en.must', 'account_status.en.must', + 'instance_details.en.must', install_dir: get_option('datadir') / 'taler-merchant' / 'templates', ) diff --git a/src/backend/taler-merchant-httpd_get-management-instances-INSTANCE.c b/src/backend/taler-merchant-httpd_get-management-instances-INSTANCE.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2019-2023 Taler Systems SA + (C) 2019-2026 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 @@ -20,7 +20,83 @@ */ #include "platform.h" #include "taler-merchant-httpd_get-management-instances-INSTANCE.h" +#include "taler-merchant-httpd_helper.h" #include <taler/taler_json_lib.h> +#include <taler/taler_templating_lib.h> + + +/** + * Reply to a GET "/instances/$ID" request with the details a + * merchant needs to log in, as human-readable text. This is what + * the sign-up e-mail queued by the database contains. + * + * @param mi instance to return information about + * @param connection the MHD connection to handle + * @return MHD result code + */ +static enum MHD_Result +get_instances_ID_text (struct TMH_MerchantInstance *mi, + struct MHD_Connection *connection) +{ + struct GNUNET_Buffer buf; + char *base_url; + struct MHD_Response *resp; + unsigned int response_code = MHD_HTTP_OK; + json_t *obj; + enum GNUNET_GenericReturnValue res; + enum MHD_Result ret; + + /* The login page is at the top-level URL, whatever the instance. */ + if (GNUNET_OK != + TMH_base_url_by_connection (connection, + "admin", + &buf)) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MISSING, + MHD_HTTP_HEADER_HOST); + } + GNUNET_buffer_write_path (&buf, + ""); + base_url = GNUNET_buffer_reap_str (&buf); + obj = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("id", + mi->settings.id), + GNUNET_JSON_pack_string ("name", + mi->settings.name), + GNUNET_JSON_pack_string ("base_url", + base_url)); + GNUNET_free (base_url); + res = TALER_TEMPLATING_build (connection, + &response_code, + "instance_details", + mi->settings.id, + NULL, + obj, + &resp); + json_decref (obj); + if (GNUNET_SYSERR == res) + { + GNUNET_break (0); + return MHD_NO; + } + if (GNUNET_OK == res) + { + TALER_MHD_add_global_headers (resp, + false); + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_CONTENT_TYPE, + "text/plain")); + } + ret = MHD_queue_response (connection, + response_code, + resp); + MHD_destroy_response (resp); + return ret; +} /** @@ -34,10 +110,30 @@ static enum MHD_Result get_instances_ID (struct TMH_MerchantInstance *mi, struct MHD_Connection *connection) { + const char *mime; json_t *ja; json_t *auth; GNUNET_assert (NULL != mi); + mime = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_ACCEPT); + if (NULL == mime) + mime = "application/json"; + if (0 == strcmp (mime, + "text/plain")) + return get_instances_ID_text (mi, + connection); + if (0 != strcmp (mime, + "application/json")) + { + GNUNET_break_op (0); + return TALER_MHD_REPLY_JSON_PACK ( + connection, + MHD_HTTP_NOT_ACCEPTABLE, + GNUNET_JSON_pack_string ("hint", + mime)); + } ja = json_array (); GNUNET_assert (NULL != ja); for (struct TMH_WireMethod *wm = mi->wm_head; diff --git a/src/backenddb/pg_create_instance_trigger.sql b/src/backenddb/pg_create_instance_trigger.sql @@ -21,11 +21,17 @@ CREATE FUNCTION merchant.merchant_instances_after_insert_trigger() AS $$ BEGIN PERFORM merchant.create_instance_schema(NEW.merchant_serial); + IF NEW.email IS NOT NULL + THEN + CALL merchant.merchant_send_signup_notification(NEW.merchant_serial, + NEW.email); + END IF; RETURN NEW; END $$; COMMENT ON FUNCTION merchant.merchant_instances_after_insert_trigger() IS 'Builds the per-instance schema merchant_instance_<merchant_serial>' - ' for the newly inserted row.'; + ' for the newly inserted row and, if the instance has an e-mail' + ' address, queues the sign-up message to it.'; DROP TRIGGER IF EXISTS merchant_instances_on_insert ON merchant.merchant_instances; diff --git a/src/backenddb/pg_merchant_send_signup_notification.sql b/src/backenddb/pg_merchant_send_signup_notification.sql @@ -0,0 +1,59 @@ +-- +-- This file is part of TALER +-- Copyright (C) 2026 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 pg_merchant_send_signup_notification.sql +-- @brief Queue a report telling the merchant the details of a new instance +-- @author Christian Grothoff + + +DROP PROCEDURE IF EXISTS merchant.merchant_send_signup_notification; +CREATE PROCEDURE merchant.merchant_send_signup_notification( + in_merchant_serial INT8 + ,in_email TEXT + ) +LANGUAGE plpgsql +AS $$ +BEGIN + -- Unlike the KYC and account alerts, this does not check + -- notification_language: the message tells the merchant their + -- login name, which they need whether or not they want alerts. + -- Runs from the INSERT trigger on merchant.merchant_instances, + -- so the per-instance schema must be named explicitly. + EXECUTE format( + 'INSERT INTO %I.merchant_reports' + ' (report_program_section' + ' ,report_description' + ' ,mime_type' + ' ,report_token' + ' ,data_source' + ' ,target_address' + ' ,frequency' + ' ,frequency_shift' + ' ,next_transmission' + ' ,one_shot_hidden' + ' ) VALUES ($1, $2, $3, $4, $5, $6, 0, 0, 0, TRUE)' + ,'merchant_instance_' || in_merchant_serial::TEXT) + USING 'email' + ,'Your GNU Taler merchant account' + ,'text/plain' + ,merchant.random_bytea(32) + ,'/private' + ,in_email; + -- Notify taler-merchant-report-generator + NOTIFY XSSAB8NCBQR1K2VK7H2M6SMY3V5TNJT1C3BW0SN4F2QV0KHR3PRB0; +END $$; +COMMENT ON PROCEDURE merchant.merchant_send_signup_notification(INT8, TEXT) + IS 'Queues a one-shot e-mail report with the details (including the' + ' login name) of the instance with the given merchant_serial.'; diff --git a/src/backenddb/sql-schema/meson.build b/src/backenddb/sql-schema/meson.build @@ -19,6 +19,7 @@ sql_global_procedures = [ '../iterate_pending_reports.sql', '../pg_select_accounts.sql', '../pg_create_instance_schema.sql', + '../pg_merchant_send_signup_notification.sql', '../pg_create_instance_trigger.sql', '../gc.sql', ] diff --git a/src/testing/meson.build b/src/testing/meson.build @@ -13,6 +13,7 @@ check_SCRIPTS = [ 'test_merchant_order_refund', 'test_merchant_order_autocleanup', 'test_merchant_product_creation', + 'test_merchant_signup_email', 'test_merchant_statistics', 'test_merchant_templates', 'test_merchant_transfer_tracking', diff --git a/src/testing/test_merchant_signup_email.sh b/src/testing/test_merchant_signup_email.sh @@ -0,0 +1,117 @@ +#!/usr/bin/env bash +# This file is part of TALER +# Copyright (C) 2026 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/> +# +# Check that creating an instance with an e-mail address sends the +# merchant a message with their login name. Instead of e-mailing it, +# the report generator writes the message to "$EMAIL.txt". +# +set -eu + +. setup.sh + +# Launch only the merchant. +setup -c test_template.conf -m +CONF="test_template.conf.edited" +LAST_RESPONSE=$(mktemp -p "${TMPDIR:-/tmp}" test_response.conf-XXXXXX) +EMAIL="signup@example.com" +MESSAGE="$EMAIL.txt" +rm -f "$MESSAGE" + +function signup_cleanup() +{ + rm -f "$LAST_RESPONSE" "$MESSAGE" + exit_cleanup +} +trap signup_cleanup EXIT + +# Write "e-mails" to a file instead of sending them. +taler-merchant-config \ + -c "$CONF" \ + -s report-generator-email \ + -o BINARY \ + -V taler-merchant-report-generator-file + +echo -n "Configuring admin instance without e-mail address ..." +STATUS=$(curl -H "Content-Type: application/json" -X POST \ + http://localhost:9966/management/instances \ + -d '{"auth":{"method":"external"},"id":"admin","name":"default","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' \ + -w "%{http_code}" -s -o "$LAST_RESPONSE") + +if [ "$STATUS" != "204" ] +then + jq < "$LAST_RESPONSE" + exit_fail "Expected 204, instance created. got: $STATUS" +fi +echo " OK" + +echo -n "Creating instance with e-mail address ..." +STATUS=$(curl -H "Content-Type: application/json" -X POST \ + http://localhost:9966/management/instances \ + -d '{"auth":{"method":"token","password":"pass1234"},"id":"signup","name":"Signup & Sons","email":"'"$EMAIL"'","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 3600000000},"default_pay_delay":{"d_us": 3600000000}}' \ + -w "%{http_code}" -s -o "$LAST_RESPONSE") + +if [ "$STATUS" != "204" ] +then + jq < "$LAST_RESPONSE" + exit_fail "Expected 204, instance created. got: $STATUS" +fi +echo " OK" + +echo -n "Sending sign-up message ..." +taler-merchant-report-generator \ + -c "$CONF" \ + --test \ + -L INFO \ + 2> report-generator-signup.log +if [ ! -f "$MESSAGE" ] +then + cat report-generator-signup.log >&2 + exit_fail "No sign-up message was written to $MESSAGE" +fi +# The instance never set a notification language: the sign-up +# message must be sent anyway. +for EXPECTED in \ + "User name : signup" \ + "Business name : Signup & Sons" \ + "Log in at : http://localhost:9966/" +do + if ! grep -qxF "$EXPECTED" "$MESSAGE" + then + cat "$MESSAGE" >&2 + exit_fail "Sign-up message lacks line '$EXPECTED'" + fi +done +echo " OK" + +echo -n "Checking that the sign-up message is sent only once ..." +rm -f "$MESSAGE" +taler-merchant-report-generator \ + -c "$CONF" \ + --test \ + -L INFO \ + 2> report-generator-signup-again.log +if [ -f "$MESSAGE" ] +then + cat "$MESSAGE" >&2 + exit_fail "Sign-up message was sent again" +fi +echo " OK" + +echo "TEST PASSED" + +exit 0