commit ef93cc616fdc7085f422d6966ffed932076c8466
parent 86327fdbf0ac41cbf5a90247e0eb7281c9e98ac3
Author: bohdan-potuzhnyi <bohdan.potuzhnyi@gmail.com>
Date: Sun, 8 Dec 2024 14:53:41 +0100
Merge branch 'master' into dev/bohdan-potuzhnyi/donau-integration
Diffstat:
6 files changed, 921 insertions(+), 3 deletions(-)
diff --git a/src/backend/taler-merchant-httpd_exchanges.c b/src/backend/taler-merchant-httpd_exchanges.c
@@ -685,7 +685,11 @@ TMH_exchange_check_debit (
if (0 != strcasecmp (keys->currency,
max_amount->currency))
{
- GNUNET_break (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Currency missmatch: exchange %s uses %s, we need %s\n",
+ exchange->url,
+ keys->currency,
+ max_amount->currency);
return GNUNET_SYSERR;
}
np = TALER_payto_normalize (wm->payto_uri);
diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am
@@ -246,15 +246,20 @@ test_merchantdb_postgres_LDFLAGS = \
-ljansson \
$(XLIB)
+check_SCRIPTS = \
+ test_webhook_trigger.sh
+
test_merchantdb_postgres_LDADD = \
$(top_builddir)/src/util/libtalermerchantutil.la \
$(top_builddir)/src/backenddb/libtalermerchantdb.la
TESTS = \
- test-merchantdb-postgres
+ $(check_PROGRAMS) \
+ $(check_SCRIPTS)
EXTRA_DIST = \
test-merchantdb-postgres.conf \
merchantdb-postgres.conf \
$(sqlinputs) \
- $(sql_DATA)
+ $(sql_DATA) \
+ $(check_SCRIPTS)
diff --git a/src/backenddb/merchant-0013.sql b/src/backenddb/merchant-0013.sql
@@ -0,0 +1,495 @@
+--
+-- 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-0013.sql
+-- @brief Creating trigger for the category change webhook
+-- @author Bohdan Potuzhnyi
+-- @author Vlada Svirsh
+
+
+BEGIN;
+
+-- Check patch versioning is in place.
+SELECT _v.register_patch('merchant-0013', NULL, NULL);
+
+SET search_path TO merchant;
+
+-- Function to replace placeholders in a string with a given value
+CREATE OR REPLACE FUNCTION replace_placeholder(
+ template TEXT,
+ key TEXT,
+ value TEXT
+) RETURNS TEXT AS $$
+BEGIN
+ RETURN regexp_replace(
+ template,
+ '{{\s*' || key || '\s*}}', -- Match the key with optional spaces
+ value,
+ 'g' -- Global replacement
+ );
+END;
+$$ LANGUAGE plpgsql;
+
+-- Trigger function to handle pending webhooks for category changes
+CREATE OR REPLACE FUNCTION handle_category_changes()
+RETURNS TRIGGER AS $$
+DECLARE
+ my_merchant_serial BIGINT;
+ resolved_body TEXT;
+ webhook RECORD; -- To iterate over all webhooks matching the event type
+BEGIN
+ -- Fetch the merchant_serial directly from the NEW or OLD row
+ my_merchant_serial := COALESCE(OLD.merchant_serial, NEW.merchant_serial);
+
+ -- INSERT case: Add a webhook for category addition
+ IF TG_OP = 'INSERT' THEN
+ FOR webhook IN
+ SELECT webhook_serial,
+ merchant_serial,
+ url,
+ http_method,
+ body_template
+ FROM merchant_webhook
+ WHERE event_type = 'category_added'
+ AND merchant_serial = my_merchant_serial
+ LOOP
+ -- Resolve placeholders for the current webhook
+ resolved_body := webhook.body_template;
+ resolved_body := replace_placeholder(resolved_body,
+ 'webhook_type',
+ 'category_added');
+ resolved_body := replace_placeholder(resolved_body,
+ 'category_serial',
+ NEW.category_serial::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'category_name',
+ NEW.category_name);
+ resolved_body := replace_placeholder(resolved_body,
+ 'merchant_serial',
+ my_merchant_serial::TEXT);
+
+ -- Insert into pending webhooks for this webhook
+ INSERT INTO merchant_pending_webhooks
+ (merchant_serial, webhook_serial, url, http_method, body)
+ VALUES
+ (webhook.merchant_serial,
+ webhook.webhook_serial,
+ webhook.url,
+ webhook.http_method,
+ resolved_body);
+ END LOOP;
+
+ -- Notify the webhook service for the TALER_DBEVENT_MERCHANT_WEBHOOK_PENDING
+ NOTIFY XXJWF6C1DCS1255RJH7GQ1EK16J8DMRSQ6K9EDKNKCP7HRVWAJPKG;
+ END IF;
+
+ -- UPDATE case: Add a webhook for category update
+ IF TG_OP = 'UPDATE' THEN
+ FOR webhook IN
+ SELECT webhook_serial,
+ merchant_serial,
+ url,
+ http_method,
+ body_template
+ FROM merchant_webhook
+ WHERE event_type = 'category_updated'
+ AND merchant_serial = my_merchant_serial
+ LOOP
+ -- Resolve placeholders for the current webhook
+ resolved_body := webhook.body_template;
+ resolved_body := replace_placeholder(resolved_body,
+ 'webhook_type',
+ 'category_updated');
+ resolved_body := replace_placeholder(resolved_body,
+ 'category_serial',
+ NEW.category_serial::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'old_category_name',
+ OLD.category_name);
+ resolved_body := replace_placeholder(resolved_body,
+ 'category_name',
+ NEW.category_name);
+ resolved_body := replace_placeholder(resolved_body,
+ 'category_name_i18n',
+ encode(NEW.category_name_i18n,
+ 'escape'));
+ resolved_body := replace_placeholder(resolved_body,
+ 'old_category_name_i18n',
+ encode(OLD.category_name_i18n,
+ 'escape'));
+
+ -- Insert into pending webhooks for this webhook
+ INSERT INTO merchant_pending_webhooks
+ (merchant_serial, webhook_serial, url, http_method, body)
+ VALUES
+ (webhook.merchant_serial,
+ webhook.webhook_serial,
+ webhook.url,
+ webhook.http_method,
+ resolved_body);
+ END LOOP;
+
+ -- Notify the webhook service for the TALER_DBEVENT_MERCHANT_WEBHOOK_PENDING
+ NOTIFY XXJWF6C1DCS1255RJH7GQ1EK16J8DMRSQ6K9EDKNKCP7HRVWAJPKG;
+ END IF;
+
+ -- DELETE case: Add a webhook for category deletion
+ IF TG_OP = 'DELETE' THEN
+ FOR webhook IN
+ SELECT webhook_serial,
+ merchant_serial,
+ url,
+ http_method,
+ body_template
+ FROM merchant_webhook
+ WHERE event_type = 'category_deleted'
+ AND merchant_serial = my_merchant_serial
+ LOOP
+ -- Resolve placeholders for the current webhook
+ resolved_body := webhook.body_template;
+ resolved_body := replace_placeholder(resolved_body,
+ 'webhook_type',
+ 'category_deleted');
+ resolved_body := replace_placeholder(resolved_body,
+ 'category_serial',
+ OLD.category_serial::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'category_name',
+ OLD.category_name);
+
+ -- Insert into pending webhooks for this webhook
+ INSERT INTO merchant_pending_webhooks
+ (merchant_serial, webhook_serial, url, http_method, body)
+ VALUES
+ (webhook.merchant_serial,
+ webhook.webhook_serial,
+ webhook.url,
+ webhook.http_method,
+ resolved_body);
+ END LOOP;
+
+ -- Notify the webhook service for the TALER_DBEVENT_MERCHANT_WEBHOOK_PENDING
+ NOTIFY XXJWF6C1DCS1255RJH7GQ1EK16J8DMRSQ6K9EDKNKCP7HRVWAJPKG;
+ END IF;
+
+ RETURN NULL;
+END;
+$$ LANGUAGE plpgsql;
+
+-- Trigger to invoke the trigger function
+CREATE TRIGGER trigger_category_changes
+AFTER INSERT OR UPDATE OR DELETE
+ON merchant_categories
+FOR EACH ROW
+EXECUTE FUNCTION handle_category_changes();
+
+-- Function to handle inventory changes and notify webhooks
+CREATE OR REPLACE FUNCTION handle_inventory_changes()
+ RETURNS TRIGGER AS $$
+DECLARE
+ my_merchant_serial BIGINT;
+ resolved_body TEXT;
+ webhook RECORD; -- To iterate over all matching webhooks
+BEGIN
+ -- Fetch the merchant_serial directly from the NEW or OLD row
+ my_merchant_serial := COALESCE(OLD.merchant_serial, NEW.merchant_serial);
+
+ -- INSERT case: Notify webhooks for inventory addition
+ IF TG_OP = 'INSERT' THEN
+ FOR webhook IN
+ SELECT webhook_serial,
+ merchant_serial,
+ url,
+ http_method,
+ body_template
+ FROM merchant_webhook
+ WHERE event_type = 'inventory_added'
+ AND merchant_serial = my_merchant_serial
+ LOOP
+ -- Resolve placeholders for the current webhook
+ resolved_body := webhook.body_template;
+ resolved_body := replace_placeholder(resolved_body,
+ 'webhook_type',
+ 'inventory_added');
+ resolved_body := replace_placeholder(resolved_body,
+ 'product_serial',
+ NEW.product_serial::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'product_id',
+ NEW.product_id);
+ resolved_body := replace_placeholder(resolved_body,
+ 'description',
+ NEW.description);
+ resolved_body := replace_placeholder(resolved_body,
+ 'description_i18n',
+ encode(NEW.description_i18n,
+ 'escape'));
+ resolved_body := replace_placeholder(resolved_body,
+ 'unit',
+ NEW.unit);
+ resolved_body := replace_placeholder(resolved_body,
+ 'image',
+ NEW.image);
+ resolved_body := replace_placeholder(resolved_body,
+ 'taxes',
+ encode(NEW.taxes,
+ 'escape'));
+ resolved_body := replace_placeholder(resolved_body,
+ 'price',
+ NEW.price::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'total_stock',
+ NEW.total_stock::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'total_sold',
+ NEW.total_sold::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'total_lost',
+ NEW.total_lost::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'address',
+ encode(NEW.address,
+ 'escape'));
+ resolved_body := replace_placeholder(resolved_body,
+ 'next_restock',
+ NEW.next_restock::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'minimum_age',
+ NEW.minimum_age::TEXT);
+
+ -- Insert into pending webhooks for this webhook
+ INSERT INTO merchant_pending_webhooks
+ (merchant_serial, webhook_serial, url, http_method, body)
+ VALUES
+ (webhook.merchant_serial,
+ webhook.webhook_serial,
+ webhook.url,
+ webhook.http_method,
+ resolved_body);
+ END LOOP;
+
+ -- Notify the webhook service
+ NOTIFY XXJWF6C1DCS1255RJH7GQ1EK16J8DMRSQ6K9EDKNKCP7HRVWAJPKG;
+ END IF;
+
+ -- UPDATE case: Notify webhooks for inventory update
+ IF TG_OP = 'UPDATE' THEN
+ FOR webhook IN
+ SELECT webhook_serial,
+ merchant_serial,
+ url,
+ http_method,
+ body_template
+ FROM merchant_webhook
+ WHERE event_type = 'inventory_updated'
+ AND merchant_serial = my_merchant_serial
+ LOOP
+ -- Resolve placeholders for the current webhook
+ resolved_body := webhook.body_template;
+ resolved_body := replace_placeholder(resolved_body,
+ 'webhook_type',
+ 'inventory_updated');
+ resolved_body := replace_placeholder(resolved_body,
+ 'product_serial',
+ NEW.product_serial::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'product_id',
+ NEW.product_id);
+ resolved_body := replace_placeholder(resolved_body,
+ 'old_description',
+ OLD.description);
+ resolved_body := replace_placeholder(resolved_body,
+ 'description',
+ NEW.description);
+ resolved_body := replace_placeholder(resolved_body,
+ 'old_description_i18n',
+ encode(OLD.description_i18n,
+ 'escape'));
+ resolved_body := replace_placeholder(resolved_body,
+ 'description_i18n',
+ encode(NEW.description_i18n,
+ 'escape'));
+ resolved_body := replace_placeholder(resolved_body,
+ 'old_unit',
+ OLD.unit);
+ resolved_body := replace_placeholder(resolved_body,
+ 'unit',
+ NEW.unit);
+ resolved_body := replace_placeholder(resolved_body,
+ 'old_image',
+ OLD.image);
+ resolved_body := replace_placeholder(resolved_body,
+ 'image',
+ NEW.image);
+ resolved_body := replace_placeholder(resolved_body,
+ 'old_taxes',
+ encode(OLD.taxes,
+ 'escape'));
+ resolved_body := replace_placeholder(resolved_body,
+ 'taxes',
+ encode(NEW.taxes,
+ 'escape'));
+ resolved_body := replace_placeholder(resolved_body,
+ 'old_price',
+ OLD.price::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'price',
+ NEW.price::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'old_total_stock',
+ OLD.total_stock::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'total_stock',
+ NEW.total_stock::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'old_total_sold',
+ OLD.total_sold::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'total_sold',
+ NEW.total_sold::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'old_total_lost',
+ OLD.total_lost::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'total_lost',
+ NEW.total_lost::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'old_address',
+ encode(OLD.address,
+ 'escape'));
+ resolved_body := replace_placeholder(resolved_body,
+ 'address',
+ encode(NEW.address,
+ 'escape'));
+ resolved_body := replace_placeholder(resolved_body,
+ 'old_next_restock',
+ OLD.next_restock::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'next_restock',
+ NEW.next_restock::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'old_minimum_age',
+ OLD.minimum_age::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'minimum_age',
+ NEW.minimum_age::TEXT);
+
+ -- Insert into pending webhooks for this webhook
+ INSERT INTO merchant_pending_webhooks
+ (merchant_serial, webhook_serial, url, http_method, body)
+ VALUES
+ (webhook.merchant_serial,
+ webhook.webhook_serial,
+ webhook.url,
+ webhook.http_method,
+ resolved_body);
+ END LOOP;
+
+ -- Notify the webhook service
+ NOTIFY XXJWF6C1DCS1255RJH7GQ1EK16J8DMRSQ6K9EDKNKCP7HRVWAJPKG;
+ END IF;
+
+ -- DELETE case: Notify webhooks for inventory deletion
+ IF TG_OP = 'DELETE' THEN
+ FOR webhook IN
+ SELECT webhook_serial,
+ merchant_serial,
+ url,
+ http_method,
+ body_template
+ FROM merchant_webhook
+ WHERE event_type = 'inventory_deleted'
+ AND merchant_serial = my_merchant_serial
+ LOOP
+ -- Resolve placeholders for the current webhook
+ resolved_body := webhook.body_template;
+ resolved_body := replace_placeholder(resolved_body,
+ 'webhook_type',
+ 'inventory_deleted');
+ resolved_body := replace_placeholder(resolved_body,
+ 'product_serial',
+ OLD.product_serial::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'product_id',
+ OLD.product_id);
+ resolved_body := replace_placeholder(resolved_body,
+ 'description',
+ OLD.description);
+ resolved_body := replace_placeholder(resolved_body,
+ 'description_i18n',
+ encode(OLD.description_i18n,
+ 'escape'));
+ resolved_body := replace_placeholder(resolved_body,
+ 'unit',
+ OLD.unit);
+ resolved_body := replace_placeholder(resolved_body,
+ 'image',
+ OLD.image);
+ resolved_body := replace_placeholder(resolved_body,
+ 'taxes',
+ encode(OLD.taxes,
+ 'escape'));
+ resolved_body := replace_placeholder(resolved_body,
+ 'price',
+ OLD.price::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'total_stock',
+ OLD.total_stock::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'total_sold',
+ OLD.total_sold::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'total_lost',
+ OLD.total_lost::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'address',
+ encode(OLD.address,
+ 'escape'));
+ resolved_body := replace_placeholder(resolved_body,
+ 'next_restock',
+ OLD.next_restock::TEXT);
+ resolved_body := replace_placeholder(resolved_body,
+ 'minimum_age',
+ OLD.minimum_age::TEXT);
+
+ -- Insert into pending webhooks for this webhook
+ INSERT INTO merchant_pending_webhooks
+ (merchant_serial, webhook_serial, url, http_method, body)
+ VALUES
+ (webhook.merchant_serial,
+ webhook.webhook_serial,
+ webhook.url,
+ webhook.http_method,
+ resolved_body);
+ END LOOP;
+
+ -- Notify the webhook service
+ NOTIFY XXJWF6C1DCS1255RJH7GQ1EK16J8DMRSQ6K9EDKNKCP7HRVWAJPKG;
+ END IF;
+
+ RETURN NULL;
+END;
+$$ LANGUAGE plpgsql;
+
+-- Trigger to invoke the trigger function
+CREATE TRIGGER trigger_inventory_changes
+ AFTER INSERT OR UPDATE OR DELETE
+ ON merchant_inventory
+ FOR EACH ROW
+EXECUTE FUNCTION handle_inventory_changes();
+
+
+COMMIT;
diff --git a/src/backenddb/pg_insert_deposit_to_transfer.sql b/src/backenddb/pg_insert_deposit_to_transfer.sql
@@ -35,6 +35,10 @@ DECLARE
my_decose INT8;
DECLARE
my_order_serial INT8;
+DECLARE
+ my_merchant_serial INT8;
+DECLARE
+ my_order_id TEXT;
BEGIN
-- Find exchange sign key
@@ -157,4 +161,20 @@ UPDATE merchant_contract_terms
WHERE mdc.wire_pending
AND mdc.order_serial=my_order_serial);
+-- POSSIBLE LOCATION FOR THE WIRE WEBHOOK OF ORDER
+--
+-- INSERT INTO merchant_pending_webhooks
+-- (merchant_serial
+-- ,webhook_serial
+-- ,url
+-- ,http_method
+-- ,body)
+-- SELECT mw.merchant_serial
+-- ,mw.webhook_serial
+-- ,mw.url
+-- ,mw.http_method
+-- ,json_build_object('order_id', my_order_id)::TEXT
+-- FROM merchant_webhook mw
+-- WHERE mw.event_type = 'order_settled'
+-- AND mw.merchant_serial = my_merchant_serial;
END $$;
diff --git a/src/backenddb/pg_insert_transfer_details.sql b/src/backenddb/pg_insert_transfer_details.sql
@@ -41,6 +41,8 @@ DECLARE
my_signkey_serial INT8;
my_credit_serial INT8;
my_affected_orders RECORD;
+ my_merchant_serial INT8;
+ my_order_id TEXT;
i INT8;
curs CURSOR (arg_coin_pub BYTEA) FOR
SELECT mcon.deposit_confirmation_serial,
@@ -220,6 +222,29 @@ LOOP
FROM merchant_deposit_confirmations mdc
WHERE mdc.wire_pending
AND mdc.order_serial=my_affected_orders.order_serial);
+
+ -- Select merchant_serial and order_id for webhook
+ SELECT merchant_serial, order_id
+ INTO my_merchant_serial, my_order_id
+ FROM merchant_contract_terms
+ WHERE order_serial=my_affected_orders.order_serial;
+
+ -- Insert pending webhook if it exists
+ INSERT INTO merchant_pending_webhooks
+ (merchant_serial
+ ,webhook_serial
+ ,url
+ ,http_method
+ ,body)
+ SELECT mw.merchant_serial
+ ,mw.webhook_serial
+ ,mw.url
+ ,mw.http_method
+ ,replace_placeholder(mw.body_template, 'order_id', my_order_id)::TEXT
+ FROM merchant_webhook mw
+ WHERE mw.event_type = 'order_settled'
+ AND mw.merchant_serial = my_merchant_serial;
+
END IF;
END LOOP; -- END curs LOOP
diff --git a/src/backenddb/test_webhook_trigger.sh b/src/backenddb/test_webhook_trigger.sh
@@ -0,0 +1,369 @@
+#!/bin/bash
+# This file is in the public domain.
+
+set -eu
+
+echo -n "Test if OpenSSL is available..."
+openssl &>/dev/null || (echo " No!" ; exit 77 )
+echo " OK"
+
+taler-merchant-dbinit -r -c test-merchantdb-postgres.conf
+
+# Generate random BYTEA values for testing
+WTID=$(openssl rand -hex 32)
+EXCHANGE_PUB=$(openssl rand -hex 32)
+EXCHANGE_SIG=$(openssl rand -hex 64)
+COIN_PUB=$(openssl rand -hex 32)
+
+# Generate valid BYTEA values
+MERCHANT_PUB=$(openssl rand -hex 32)
+AUTH_HASH=$(openssl rand -hex 64)
+AUTH_SALT=$(openssl rand -hex 32)
+
+# Define other parameters
+INSTANCE_ID="test-merchant"
+EXCHANGE_URL="http://example.com/exchange"
+PAYTO_URI="payto://example.com/account"
+EXECUTION_TIME=$(date +%s)
+
+# Generate taler_amount_currency values
+TOTAL_AMOUNT="ROW(100, 0, 'USD')::taler_amount_currency"
+WIRE_FEE="ROW(1, 0, 'USD')::taler_amount_currency"
+
+# Generate arrays for taler_amount_currency and BYTEA
+# Generate arrays for taler_amount_currency and BYTEA
+COIN_VALUES="ARRAY[ROW(10, 0, 'USD')::taler_amount_currency, ROW(20, 0, 'USD')::taler_amount_currency, ROW(30, 0, 'USD')::taler_amount_currency]::taler_amount_currency[]"
+DEPOSIT_FEES="ARRAY[ROW(1, 0, 'USD')::taler_amount_currency, ROW(2, 50000000, 'USD')::taler_amount_currency, ROW(3, 25000000, 'USD')::taler_amount_currency]::taler_amount_currency[]"
+COIN_PUBS="ARRAY[decode('$COIN_PUB', 'hex'), decode('$(openssl rand -hex 32)', 'hex'), decode('$(openssl rand -hex 32)', 'hex')]::BYTEA[]"
+CONTRACT_TERMS="ARRAY[decode('$(openssl rand -hex 32)', 'hex'), decode('$(openssl rand -hex 32)', 'hex'), decode('$(openssl rand -hex 32)', 'hex')]::BYTEA[]"
+
+# Create the merchant database
+echo "SET search_path TO merchant;
+ INSERT INTO merchant_instances (
+ merchant_serial,
+ merchant_name,
+ merchant_id,
+ address,
+ jurisdiction,
+ default_wire_transfer_delay,
+ default_pay_delay,
+ merchant_pub,
+ auth_hash,
+ auth_salt
+ ) VALUES (
+ 1,
+ 'Test Merchant',
+ 'test-merchant',
+ '{"city":"TestCity"}',
+ '{"country":"TestCountry"}',
+ 3600,
+ 3600,
+ decode('$MERCHANT_PUB', 'hex'),
+ decode('$AUTH_HASH', 'hex'),
+ decode('$AUTH_SALT', 'hex')
+ );" | psql talercheck
+
+# Create the webhooks
+echo "SET search_path TO merchant;
+ INSERT INTO merchant_webhook (merchant_serial, webhook_id, event_type, url, http_method, body_template)
+ VALUES
+ (1, 'category_added_hook', 'category_added', 'http://example.com/category_added', 'POST', '{"event":"{{ webhook_type }}"}'),
+ (1, 'category_updated_hook', 'category_updated', 'http://example.com/category_updated', 'POST', '{"event":"{{ webhook_type }}"}'),
+ (1, 'category_deleted_hook', 'category_deleted', 'http://example.com/category_deleted', 'POST', '{"event":"{{ webhook_type }}"}'),
+ (1, 'inventory_added_hook', 'inventory_added', 'http://example.com/inventory_added', 'POST', '{"event":"{{ webhook_type }}"}'),
+ (1, 'inventory_updated_hook', 'inventory_updated', 'http://example.com/inventory_updated', 'POST', '{"event":"{{ webhook_type }}"}'),
+ (1, 'inventory_deleted_hook', 'inventory_deleted', 'http://example.com/inventory_deleted', 'POST', '{"event":"{{ webhook_type }}"}'),
+ (1, 'order_settled_hook', 'order_settled', 'http://example.com/order_settled', 'POST', '{"event":"{{ webhook_type }}"}');" | psql talercheck
+
+# Create the category
+echo "SET search_path TO merchant;
+ INSERT INTO merchant_categories (merchant_serial, category_name, category_name_i18n)
+ VALUES (1, 'Test Category', '{"en":"Test Category"}');" | psql talercheck
+
+# Update the category
+echo "SET search_path TO merchant;
+ UPDATE merchant_categories
+ SET category_name = 'Updated Category'
+ WHERE category_name = 'Test Category';" | psql talercheck
+
+# Delete the category
+echo "SET search_path TO merchant;
+ DELETE FROM merchant_categories
+ WHERE category_name = 'Updated Category';" | psql talercheck
+
+# Add product to inventory
+echo "SET search_path TO merchant;
+ INSERT INTO merchant_inventory (merchant_serial, product_id, description, description_i18n, unit, image, taxes, price, total_stock, address, next_restock)
+ VALUES
+ (1, 'test-product', 'Test Product', '{"en":"Test Product"}', 'unit', 'image-data-url', '[]', ROW(10, 0, 'USD'), 100, '{"city":"TestCity"}', 0);" | psql talercheck
+
+# Update product in inventory
+echo "SET search_path TO merchant;
+ UPDATE merchant_inventory
+ SET description = 'Updated Test Product'
+ WHERE product_id = 'test-product';" | psql talercheck
+
+# Delete product from inventory
+echo "SET search_path TO merchant;
+ DELETE FROM merchant_inventory
+ WHERE product_id = 'test-product';" | psql talercheck
+
+# Create the merchant account
+echo "SET search_path TO merchant;
+ INSERT INTO merchant_accounts (
+ merchant_serial,
+ h_wire,
+ salt,
+ credit_facade_url,
+ credit_facade_credentials,
+ last_bank_serial,
+ payto_uri,
+ active
+ ) VALUES (
+ 1,
+ decode('$(openssl rand -hex 64)', 'hex'),
+ decode('$(openssl rand -hex 16)', 'hex'),
+ 'http://example.com/credit',
+ '{"key":"value"}',
+ 0,
+ '$PAYTO_URI',
+ TRUE
+ );" | psql talercheck
+
+# Create the merchant transfer
+echo "SET search_path TO merchant;
+ INSERT INTO merchant_transfers (
+ credit_serial,
+ exchange_url,
+ wtid,
+ credit_amount,
+ account_serial
+ ) VALUES (
+ 1,
+ 'http://example.com/exchange',
+ decode('$WTID', 'hex'),
+ ROW(100, 0, 'USD')::taler_amount_currency,
+ 1
+ );" | psql talercheck
+
+# Create the exchange signing key
+echo "SET search_path TO merchant;
+ INSERT INTO merchant_exchange_signing_keys (
+ master_pub,
+ exchange_pub,
+ start_date,
+ expire_date,
+ end_date,
+ master_sig
+ ) VALUES (
+ decode('$(openssl rand -hex 32)', 'hex'),
+ decode('$EXCHANGE_PUB', 'hex'),
+ EXTRACT(EPOCH FROM NOW()),
+ EXTRACT(EPOCH FROM NOW()) + 3600,
+ EXTRACT(EPOCH FROM NOW()) + 7200,
+ decode('$(openssl rand -hex 64)', 'hex')
+ );" | psql talercheck
+
+# Create the merchant_contract_terms
+echo "SET search_path TO merchant;
+ INSERT INTO merchant_contract_terms (
+ order_serial,
+ merchant_serial,
+ order_id,
+ contract_terms,
+ wallet_data,
+ h_contract_terms,
+ creation_time,
+ pay_deadline,
+ refund_deadline,
+ paid,
+ wired,
+ fulfillment_url,
+ session_id,
+ pos_key,
+ pos_algorithm,
+ claim_token
+ ) VALUES (
+ 1001,
+ 1,
+ 'order-1234',
+ decode('$(openssl rand -hex 64)', 'hex'),
+ '{"wallet":"test"}',
+ decode('$(openssl rand -hex 64)', 'hex'),
+ EXTRACT(EPOCH FROM NOW()),
+ EXTRACT(EPOCH FROM NOW()) + 3600,
+ EXTRACT(EPOCH FROM NOW()) + 7200,
+ FALSE,
+ FALSE,
+ 'http://example.com/fulfillment',
+ '',
+ NULL,
+ 0,
+ decode('$(openssl rand -hex 16)', 'hex')
+ );" | psql talercheck
+
+echo "SET search_path TO merchant;
+ INSERT INTO merchant_deposit_confirmations (
+ order_serial,
+ deposit_timestamp,
+ exchange_url,
+ total_without_fee,
+ wire_fee,
+ signkey_serial,
+ exchange_sig,
+ account_serial,
+ wire_pending
+ ) VALUES (
+ 1001,
+ EXTRACT(EPOCH FROM NOW()),
+ '$EXCHANGE_URL',
+ ROW(90, 0, 'USD')::taler_amount_currency,
+ ROW(10, 0, 'USD')::taler_amount_currency,
+ 1,
+ decode('$EXCHANGE_SIG', 'hex'),
+ 1,
+ TRUE
+ );" | psql talercheck
+
+echo "SET search_path TO merchant;
+ UPDATE merchant_contract_terms
+ SET wired = TRUE
+ WHERE order_serial = 1001
+ AND NOT EXISTS (
+ SELECT 1
+ FROM merchant_deposit_confirmations
+ WHERE wire_pending
+ AND order_serial = 1001
+ );" | psql talercheck
+
+# Handle deposit confirmation
+echo "SET search_path TO merchant;
+ INSERT INTO merchant_deposit_confirmations (
+ order_serial,
+ deposit_timestamp,
+ exchange_url,
+ total_without_fee,
+ wire_fee,
+ signkey_serial,
+ exchange_sig,
+ account_serial,
+ wire_pending
+ ) VALUES (
+ 1001,
+ EXTRACT(EPOCH FROM NOW()),
+ 'http://example.com/exchange',
+ ROW(90, 0, 'USD')::taler_amount_currency,
+ ROW(10, 0, 'USD')::taler_amount_currency,
+ 1,
+ decode('$(openssl rand -hex 64)', 'hex'),
+ 1,
+ FALSE
+ );" | psql talercheck
+
+echo "SET search_path TO merchant;
+ UPDATE merchant_contract_terms
+ SET wired = TRUE
+ WHERE order_serial = 1001
+ AND NOT EXISTS (
+ SELECT 1
+ FROM merchant_deposit_confirmations
+ WHERE wire_pending
+ AND order_serial = 1001
+ );" | psql talercheck
+
+# Add entries to merchant_deposits
+echo "SET search_path TO merchant;
+ INSERT INTO merchant_deposits (
+ deposit_serial,
+ coin_offset,
+ deposit_confirmation_serial,
+ coin_pub,
+ coin_sig,
+ amount_with_fee,
+ deposit_fee,
+ refund_fee
+ ) VALUES (
+ (SELECT COALESCE(MAX(deposit_serial), 0) + 1 FROM merchant_deposits), -- deposit_serial
+ 0, -- coin_offset
+ 1, -- deposit_confirmation_serial (link to confirmation)
+ decode('$COIN_PUB', 'hex'), -- coin_pub
+ decode('$(openssl rand -hex 64)', 'hex'), -- coin_sig
+ ROW(30, 0, 'USD')::taler_amount_currency, -- amount_with_fee
+ ROW(1, 0, 'USD')::taler_amount_currency, -- deposit_fee
+ ROW(0, 0, 'USD')::taler_amount_currency -- refund_fee
+ );" | psql talercheck
+
+# Link deposits to wire transfers
+echo "SET search_path TO merchant;
+ INSERT INTO merchant_deposit_to_transfer (
+ deposit_serial,
+ coin_contribution_value,
+ execution_time,
+ signkey_serial,
+ exchange_sig,
+ wtid
+ ) VALUES (
+ (SELECT MAX(deposit_serial) FROM merchant_deposits), -- deposit_serial
+ ROW(10, 0, 'USD')::taler_amount_currency, -- coin_contribution_value
+ $EXECUTION_TIME, -- execution_time
+ 1, -- signkey_serial
+ decode('$(openssl rand -hex 64)', 'hex'), -- exchange_sig
+ decode('$WTID', 'hex') -- wtid
+ );" | psql talercheck
+
+
+# Debug the UPDATE condition
+echo "SET search_path TO merchant;
+ SELECT md.deposit_confirmation_serial, mdtt.wtid
+ FROM merchant_deposits md
+ LEFT JOIN merchant_deposit_to_transfer mdtt
+ USING (deposit_serial)
+ WHERE md.deposit_confirmation_serial=1
+ AND mdtt.wtid IS NULL;" | psql talercheck
+
+# Re-run the update logic
+echo "SET search_path TO merchant;
+ UPDATE merchant_deposit_confirmations
+ SET wire_pending=FALSE
+ WHERE deposit_confirmation_serial=1
+ AND NOT EXISTS
+ (SELECT 1
+ FROM merchant_deposits md
+ LEFT JOIN merchant_deposit_to_transfer mdtt
+ USING (deposit_serial)
+ WHERE md.deposit_confirmation_serial=1
+ AND mdtt.wtid IS NULL);" | psql talercheck
+
+
+echo "SET search_path TO merchant;
+ SELECT merchant_do_insert_transfer_details(
+ '$INSTANCE_ID',
+ '$EXCHANGE_URL',
+ '$PAYTO_URI',
+ decode('$WTID', 'hex')::BYTEA,
+ $EXECUTION_TIME::BIGINT,
+ decode('$EXCHANGE_PUB', 'hex')::BYTEA,
+ decode('$EXCHANGE_SIG', 'hex')::BYTEA,
+ $TOTAL_AMOUNT,
+ $WIRE_FEE,
+ $COIN_VALUES,
+ $DEPOSIT_FEES,
+ $COIN_PUBS,
+ $CONTRACT_TERMS
+ );" | psql talercheck
+
+# Echo all webhooks
+echo "SET search_path TO merchant;
+ SELECT * FROM merchant_pending_webhooks;" | psql talercheck
+
+# Fetch and validate the number of pending webhooks
+WEBHOOK_COUNT=$(echo "SET search_path TO merchant;
+ SELECT COUNT(*)
+ FROM merchant_pending_webhooks;" | psql talercheck -t -A | tr -d '[:space:]')
+
+if [ "$WEBHOOK_COUNT" -ne 7 ]; then
+ echo "ERROR: Expected 7 webhook entries, but received $WEBHOOK_COUNT."
+ exit 1
+else
+ echo "SUCCESS: Received 7 webhook entries as expected."
+fi