commit 5704af28059714cd5d0e65183dbf61d7d5bbaee1
parent 718ae1bf0cd58fe9fc962e629f0169c732674b36
Author: Christian Grothoff <christian@grothoff.org>
Date: Sat, 10 Oct 2026 14:06:09 +0200
implement #11789
Diffstat:
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