merchant

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

commit 3903c8bae8dadb0887cb9347bcc719b03a2c73a1
parent 5845596472d3a265d16836f5b0e401f34b74531c
Author: Christian Grothoff <christian@grothoff.org>
Date:   Wed,  5 Aug 2026 23:00:43 +0200

fix send_kyc_notification stored procedure that still referenced removed merchant_serial column

Diffstat:
Msrc/backenddb/pg_merchant_send_kyc_notification.sql | 23++++++++++++++---------
Msrc/backenddb/test_merchantdb.c | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 96 insertions(+), 9 deletions(-)

diff --git a/src/backenddb/pg_merchant_send_kyc_notification.sql b/src/backenddb/pg_merchant_send_kyc_notification.sql @@ -13,8 +13,8 @@ -- 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_kyc_trigger.sql --- @brief Fix trigger logic +-- @file pg_merchant_send_kyc_notification.sql +-- @brief Queue a report to alert the merchant about a KYC status change -- @author Christian Grothoff @@ -67,10 +67,16 @@ BEGIN RETURN; END IF; - my_report_token = random_bytea(32); + -- Note: random_bytea(), uri_escape() and base32_crockford() live in the + -- 'merchant' schema, while this procedure runs with a search_path that + -- contains only the per-instance schema (see set_instance.c), so they + -- MUST be schema-qualified here. + my_report_token = merchant.random_bytea(32); + -- Note: merchant_reports is the per-instance table; it lost its + -- 'merchant_serial' column when the tables were moved into per-instance + -- schemata (merchant-0036). INSERT INTO merchant_reports ( - merchant_serial - ,report_program_section + report_program_section ,report_description ,mime_type ,report_token @@ -81,13 +87,12 @@ BEGIN ,next_transmission ,one_shot_hidden ) VALUES ( - my_instance_serial - ,'email' + 'email' ,'automatically triggered KYC alert' ,'text/plain' ,my_report_token - ,'/private/kyc?exchange_url=' || uri_escape(in_exchange_url) - || '&h_wire=' || base32_crockford (my_h_wire) + ,'/private/kyc?exchange_url=' || merchant.uri_escape(in_exchange_url) + || '&h_wire=' || merchant.base32_crockford (my_h_wire) ,my_email ,0 ,0 diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c @@ -191,6 +191,66 @@ static struct TALER_MERCHANTDB_PostgresContext *pg; } while (0) +/** + * Runs @a sql directly on the database connection of the test. Used to + * set up or inspect state that has no dedicated MERCHANTDB entry point, + * such as the notification settings of an instance or the rows queued by + * a trigger. + * + * @param sql the SQL statement(s) to execute + * @return 0 on success, 1 otherwise. + */ +static int +exec_sql (const char *sql) +{ + struct GNUNET_PQ_ExecuteStatement es[] = { + GNUNET_PQ_make_execute (sql), + GNUNET_PQ_EXECUTE_STATEMENT_END + }; + + TEST_COND_RET_ON_FAIL (GNUNET_OK == + GNUNET_PQ_exec_statements (pg->conn, + es), + "Direct SQL execution failed\n"); + return 0; +} + + +/** + * Evaluates @a sql, which must be a parameter-less query returning + * exactly one row with a single INT8 column named "num". + * + * @param sql the query to run + * @param[out] num set to the value returned + * @return 0 on success, 1 otherwise. + */ +static int +query_sql_num (const char *sql, + uint64_t *num) +{ + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_uint64 ("num", + num), + GNUNET_PQ_result_spec_end + }; + + TEST_COND_RET_ON_FAIL (GNUNET_OK == + GNUNET_PQ_prepare_anon (pg->conn, + sql), + "Preparing direct query failed\n"); + TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == + GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "", + params, + rs), + "Direct query failed\n"); + return 0; +} + + /* ********** Instances ********** */ @@ -6861,6 +6921,7 @@ test_kyc (void) struct TALER_MERCHANTDB_AccountDetails account; bool fail; struct GNUNET_TIME_Timestamp now; + uint64_t alerts; make_instance ("test_kyc", &instance); @@ -6871,6 +6932,14 @@ test_kyc (void) TEST_RET_ON_FAIL (test_insert_account (&instance, &account, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); + /* Arm the KYC alert path: merchant_send_kyc_notification() returns + early unless the instance has BOTH an e-mail address and a + notification language. No C entry point writes + 'notification_language', hence the direct UPDATE. */ + TEST_RET_ON_FAIL (exec_sql ("UPDATE merchant.merchant_instances" + " SET email='kyc-alerts@example.com'" + " ,notification_language='en'" + " WHERE merchant_id='test_kyc'")); now = GNUNET_TIME_timestamp_get (); TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != TALER_MERCHANTDB_insert_kyc_status (pg, @@ -6887,6 +6956,19 @@ test_kyc (void) NULL, false, false)); + /* The AFTER INSERT trigger must have queued exactly one alert. + Regression test: merchant_send_kyc_notification() used to call + random_bytea()/uri_escape()/base32_crockford() unqualified -- they + live in schema 'merchant' only, while the search_path holds just the + per-instance schema -- and to insert a 'merchant_serial' column that + merchant-0036 dropped. Either turned this call into a HARD_ERROR, + which taler-merchant-kyccheck treats as fatal. */ + TEST_RET_ON_FAIL (query_sql_num ("SELECT COUNT(*) AS num" + " FROM merchant_reports" + " WHERE one_shot_hidden", + &alerts)); + TEST_COND_RET_ON_FAIL (1 == alerts, + "KYC status change did not queue an alert report\n"); TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != TALER_MERCHANTDB_insert_kyc_status (pg, instance.instance.id,