merchant

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

commit 291363e2c291543b59203145e039f16446153318
parent ed83bf53a07ba09d174a7bc6472ea69260eb96ff
Author: Christian Grothoff <christian@grothoff.org>
Date:   Wed,  3 Jun 2026 00:59:51 +0200

another fix

Diffstat:
Msrc/backenddb/pg_statistics_helpers.sql | 4++--
Msrc/backenddb/sql-schema/merchant-0037.sql | 374+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
2 files changed, 243 insertions(+), 135 deletions(-)

diff --git a/src/backenddb/pg_statistics_helpers.sql b/src/backenddb/pg_statistics_helpers.sql @@ -215,7 +215,7 @@ BEGIN (my_meta ,my_start ,in_delta) - ON CONFLICT (imeta_serial_id, merchant_serial, slot) + ON CONFLICT (imeta_serial_id, slot) DO UPDATE SET delta = msce.delta + in_delta RETURNING nevent_serial_id @@ -311,7 +311,7 @@ BEGIN ,(in_delta).val ,(in_delta).frac ) - ON CONFLICT (imeta_serial_id, merchant_serial, slot, delta_curr) + ON CONFLICT (imeta_serial_id, slot, delta_curr) DO UPDATE SET delta_value = msae.delta_value + (in_delta).val + CASE diff --git a/src/backenddb/sql-schema/merchant-0037.sql b/src/backenddb/sql-schema/merchant-0037.sql @@ -29,141 +29,249 @@ CREATE PROCEDURE merchant.merchant_0037_init(s TEXT) LANGUAGE plpgsql AS $OUTER$ BEGIN - EXECUTE format('CREATE TABLE %I.merchant_tos_accepted (' - || ' tos_accepted_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,' - || ' exchange_url TEXT NOT NULL UNIQUE,' - || ' tos_version TEXT NOT NULL' - || ')', s); - EXECUTE format('COMMENT ON TABLE %I.merchant_tos_accepted IS %L', s, + EXECUTE format('SET LOCAL search_path TO %I', s); + + CREATE TABLE merchant_tos_accepted ( + tos_accepted_serial INT8 GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + exchange_url TEXT NOT NULL UNIQUE, + tos_version TEXT NOT NULL + ); + COMMENT ON TABLE merchant_tos_accepted IS 'Latest Taler-Terms-Version (etag) of the terms of service accepted' - ' early (ahead of the KYC flow) by the user, one row per exchange.'); - EXECUTE format('COMMENT ON COLUMN %I.merchant_tos_accepted.exchange_url IS %L', - s, 'Base URL of the exchange the terms of service were accepted for.'); - EXECUTE format('COMMENT ON COLUMN %I.merchant_tos_accepted.tos_version IS %L', - s, 'Accepted Taler-Terms-Version of the exchange terms of service.'); - - -- merchant_categories - EXECUTE format('CREATE OR REPLACE FUNCTION %I.handle_category_changes()' - || ' RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$;', s); - EXECUTE format('CREATE TRIGGER trigger_category_changes' - || ' AFTER INSERT OR UPDATE OR DELETE' - || ' ON %I.merchant_categories' - || ' FOR EACH ROW EXECUTE FUNCTION %I.handle_category_changes()', s, s); - - -- recreate triggers lost in merchant-0036... - EXECUTE format('CREATE OR REPLACE FUNCTION %I.handle_inventory_changes()' - || ' RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$;', s); - EXECUTE format('CREATE TRIGGER trigger_inventory_changes' - || ' AFTER INSERT OR UPDATE OR DELETE' - || ' ON %I.merchant_inventory' - || ' FOR EACH ROW EXECUTE FUNCTION %I.handle_inventory_changes()', s, s); - - EXECUTE format('CREATE OR REPLACE FUNCTION %I.merchant_orders_insert_statistics_trigger()' - || ' RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$;', s); - EXECUTE format('CREATE TRIGGER merchant_orders_on_insert_statistic' - || ' AFTER INSERT' - || ' ON %I.merchant_orders' - || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_orders_insert_statistics_trigger()', s, s); - - EXECUTE format('CREATE OR REPLACE FUNCTION %I.merchant_contract_terms_insert_statistics_trigger()' - || ' RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$;', s); - EXECUTE format('CREATE TRIGGER merchant_contract_terms_on_insert_statistic' - || ' AFTER INSERT' - || ' ON %I.merchant_contract_terms' - || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_contract_terms_insert_statistics_trigger()', s, s); - - EXECUTE format('CREATE OR REPLACE FUNCTION %I.merchant_contract_terms_update_statistics_trigger()' - || ' RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$;', s); - EXECUTE format('CREATE TRIGGER merchant_contract_terms_on_update_statistic' - || ' AFTER UPDATE' - || ' ON %I.merchant_contract_terms' - || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_contract_terms_update_statistics_trigger()', s, s); - - EXECUTE format('CREATE OR REPLACE FUNCTION %I.merchant_refunds_insert_statistics_trigger()' - || ' RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$;', s); - EXECUTE format('CREATE TRIGGER merchant_refunds_on_insert_statistic' - || ' AFTER INSERT' - || ' ON %I.merchant_refunds' - || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_refunds_insert_statistics_trigger()', s, s); - - EXECUTE format('CREATE OR REPLACE FUNCTION %I.merchant_transfer_signatures_insert_statistics_trigger()' - || ' RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$;', s); - EXECUTE format('CREATE TRIGGER merchant_transfer_signatures_on_insert_statistic' - || ' AFTER INSERT' - || ' ON %I.merchant_transfer_signatures' - || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_transfer_signatures_insert_statistics_trigger()', s, s); - - EXECUTE format('CREATE OR REPLACE FUNCTION %I.merchant_issued_tokens_insert_statistics_trigger()' - || ' RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$;', s); - EXECUTE format('CREATE TRIGGER merchant_issued_tokens_on_insert_statistic' - || ' AFTER INSERT' - || ' ON %I.merchant_issued_tokens' - || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_issued_tokens_insert_statistics_trigger()', s, s); - - EXECUTE format('CREATE OR REPLACE FUNCTION %I.merchant_used_tokens_insert_statistics_trigger()' - || ' RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$;', s); - EXECUTE format('CREATE TRIGGER merchant_used_tokens_on_insert_statistic' - || ' AFTER INSERT' - || ' ON %I.merchant_used_tokens' - || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_used_tokens_insert_statistics_trigger()', s, s); - - EXECUTE format('CREATE OR REPLACE FUNCTION %I.merchant_deposits_insert_statistics_trigger()' - || ' RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$;', s); - EXECUTE format('CREATE TRIGGER merchant_deposits_on_insert_statistic' - || ' AFTER INSERT' - || ' ON %I.merchant_deposits' - || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_deposits_insert_statistics_trigger()', s, s); - - EXECUTE format('CREATE OR REPLACE FUNCTION %I.merchant_expected_transfers_insert_statistics_trigger()' - || ' RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$;', s); - EXECUTE format('CREATE TRIGGER merchant_expected_transfers_on_insert_statistic' - || ' AFTER INSERT' - || ' ON %I.merchant_expected_transfers' - || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_expected_transfers_insert_statistics_trigger()', s, s); - - EXECUTE format('CREATE OR REPLACE FUNCTION %I.merchant_expected_transfers_update_statistics_trigger()' - || ' RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$;', s); - EXECUTE format('CREATE TRIGGER merchant_expected_transfers_on_update_statistic' - || ' AFTER INSERT' - || ' ON %I.merchant_expected_transfers' - || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_expected_transfers_update_statistics_trigger()', s, s); - - - EXECUTE format('CREATE OR REPLACE FUNCTION %I.merchant_expected_transfers_insert_trigger()' - || ' RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$;', s); - EXECUTE format('CREATE TRIGGER merchant_expected_transfers_on_insert' - || ' BEFORE INSERT' - || ' ON %I.merchant_expected_transfers' - || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_expected_transfers_insert_trigger()', s, s); - - EXECUTE format('CREATE OR REPLACE FUNCTION %I.merchant_expected_transfers_update_trigger()' - || ' RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$;', s); - EXECUTE format('CREATE TRIGGER merchant_expected_transfers_on_update' - || ' BEFORE UPDATE' - || ' ON %I.merchant_expected_transfers' - || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_expected_transfers_update_trigger()', s, s); - - EXECUTE format('CREATE OR REPLACE FUNCTION %I.merchant_transfers_insert_trigger()' - || ' RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$;', s); - EXECUTE format('CREATE TRIGGER merchant_transfers_on_insert' - || ' BEFORE INSERT' - || ' ON %I.merchant_transfers' - || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_transfers_insert_trigger()', s, s); - - EXECUTE format('CREATE OR REPLACE FUNCTION %I.merchant_kyc_insert_trigger()' - || ' RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$;', s); - EXECUTE format('CREATE TRIGGER merchant_kyc_on_insert' - || ' AFTER INSERT' - || ' ON %I.merchant_kyc' - || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_kyc_insert_trigger()', s, s); - - EXECUTE format('CREATE OR REPLACE FUNCTION %I.merchant_kyc_update_trigger()' - || ' RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$;', s); - EXECUTE format('CREATE TRIGGER merchant_kyc_on_update' - || ' AFTER UPDATE' - || ' ON %I.merchant_kyc' - || ' FOR EACH ROW EXECUTE FUNCTION %I.merchant_kyc_update_trigger()', s, s); + ' early (ahead of the KYC flow) by the user, one row per exchange.'; + COMMENT ON COLUMN merchant_tos_accepted.exchange_url IS + 'Base URL of the exchange the terms of service were accepted for.'; + COMMENT ON COLUMN merchant_tos_accepted.tos_version IS + 'Accepted Taler-Terms-Version of the exchange terms of service.'; + + -- Restore triggers previously lost in merchant-0036 + CREATE OR REPLACE FUNCTION handle_category_changes() + RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$; + CREATE TRIGGER trigger_category_changes + AFTER INSERT OR UPDATE OR DELETE + ON merchant_categories + FOR EACH ROW EXECUTE FUNCTION handle_category_changes(); + + CREATE OR REPLACE FUNCTION handle_inventory_changes() + RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$; + CREATE TRIGGER trigger_inventory_changes + AFTER INSERT OR UPDATE OR DELETE + ON merchant_inventory + FOR EACH ROW EXECUTE FUNCTION handle_inventory_changes(); + + CREATE OR REPLACE FUNCTION merchant_orders_insert_statistics_trigger() + RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$; + CREATE TRIGGER merchant_orders_on_insert_statistic + AFTER INSERT + ON merchant_orders + FOR EACH ROW EXECUTE FUNCTION merchant_orders_insert_statistics_trigger(); + + CREATE OR REPLACE FUNCTION merchant_contract_terms_insert_statistics_trigger() + RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$; + CREATE TRIGGER merchant_contract_terms_on_insert_statistic + AFTER INSERT + ON merchant_contract_terms + FOR EACH ROW EXECUTE FUNCTION merchant_contract_terms_insert_statistics_trigger(); + + CREATE OR REPLACE FUNCTION merchant_contract_terms_update_statistics_trigger() + RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$; + CREATE TRIGGER merchant_contract_terms_on_update_statistic + AFTER UPDATE + ON merchant_contract_terms + FOR EACH ROW EXECUTE FUNCTION merchant_contract_terms_update_statistics_trigger(); + + CREATE OR REPLACE FUNCTION merchant_refunds_insert_statistics_trigger() + RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$; + CREATE TRIGGER merchant_refunds_on_insert_statistic + AFTER INSERT + ON merchant_refunds + FOR EACH ROW EXECUTE FUNCTION merchant_refunds_insert_statistics_trigger(); + + CREATE OR REPLACE FUNCTION merchant_transfer_signatures_insert_statistics_trigger() + RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$; + CREATE TRIGGER merchant_transfer_signatures_on_insert_statistic + AFTER INSERT + ON merchant_transfer_signatures + FOR EACH ROW EXECUTE FUNCTION merchant_transfer_signatures_insert_statistics_trigger(); + + CREATE OR REPLACE FUNCTION merchant_issued_tokens_insert_statistics_trigger() + RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$; + CREATE TRIGGER merchant_issued_tokens_on_insert_statistic + AFTER INSERT + ON merchant_issued_tokens + FOR EACH ROW EXECUTE FUNCTION merchant_issued_tokens_insert_statistics_trigger(); + + CREATE OR REPLACE FUNCTION merchant_used_tokens_insert_statistics_trigger() + RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$; + CREATE TRIGGER merchant_used_tokens_on_insert_statistic + AFTER INSERT + ON merchant_used_tokens + FOR EACH ROW EXECUTE FUNCTION merchant_used_tokens_insert_statistics_trigger(); + + CREATE OR REPLACE FUNCTION merchant_deposits_insert_statistics_trigger() + RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$; + CREATE TRIGGER merchant_deposits_on_insert_statistic + AFTER INSERT + ON merchant_deposits + FOR EACH ROW EXECUTE FUNCTION merchant_deposits_insert_statistics_trigger(); + + CREATE OR REPLACE FUNCTION merchant_expected_transfers_insert_statistics_trigger() + RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$; + CREATE TRIGGER merchant_expected_transfers_on_insert_statistic + AFTER INSERT + ON merchant_expected_transfers + FOR EACH ROW EXECUTE FUNCTION merchant_expected_transfers_insert_statistics_trigger(); + + CREATE OR REPLACE FUNCTION merchant_expected_transfers_update_statistics_trigger() + RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$; + CREATE TRIGGER merchant_expected_transfers_on_update_statistic + AFTER INSERT + ON merchant_expected_transfers + FOR EACH ROW EXECUTE FUNCTION merchant_expected_transfers_update_statistics_trigger(); + + CREATE OR REPLACE FUNCTION merchant_expected_transfers_insert_trigger() + RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$; + CREATE TRIGGER merchant_expected_transfers_on_insert + BEFORE INSERT + ON merchant_expected_transfers + FOR EACH ROW EXECUTE FUNCTION merchant_expected_transfers_insert_trigger(); + + CREATE OR REPLACE FUNCTION merchant_expected_transfers_update_trigger() + RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$; + CREATE TRIGGER merchant_expected_transfers_on_update + BEFORE UPDATE + ON merchant_expected_transfers + FOR EACH ROW EXECUTE FUNCTION merchant_expected_transfers_update_trigger(); + + CREATE OR REPLACE FUNCTION merchant_transfers_insert_trigger() + RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$; + CREATE TRIGGER merchant_transfers_on_insert + BEFORE INSERT + ON merchant_transfers + FOR EACH ROW EXECUTE FUNCTION merchant_transfers_insert_trigger(); + + CREATE OR REPLACE FUNCTION merchant_kyc_insert_trigger() + RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$; + CREATE TRIGGER merchant_kyc_on_insert + AFTER INSERT + ON merchant_kyc + FOR EACH ROW EXECUTE FUNCTION merchant_kyc_insert_trigger(); + + CREATE OR REPLACE FUNCTION merchant_kyc_update_trigger() + RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN RETURN NEW; END $$; + CREATE TRIGGER merchant_kyc_on_update + AFTER UPDATE + ON merchant_kyc + FOR EACH ROW EXECUTE FUNCTION merchant_kyc_update_trigger(); + + -- Install default statistics tracking, if not already present + -- (for existing instances, we would have copied it over, but + -- not for new instances created after the initial migration). + -- Enable interval statistics + INSERT INTO merchant_statistic_interval_meta + (slug + ,description + ,stype + ,ranges + ,precisions) + VALUES + ('orders-created' + ,'number of orders created (but not necessarily claimed by wallets)' + ,'number' + ,ARRAY(SELECT generate_series (1, 60*60, 60)) -- for last hour, per minute + || ARRAY(SELECT generate_series (60*60*2, 60*60*48, 60 * 60)) -- for last 2 days, per hour + ,array_fill (5, ARRAY[60]) -- precision: 5s + || array_fill (5 * 60, ARRAY[48-1]) -- precision: 5 minutes + ), + ('orders-claimed' + ,'number of orders claimed by a wallet (but not necessarily paid)' + ,'number' + ,ARRAY(SELECT generate_series (1, 60*60, 60)) -- for last hour, per minute + || ARRAY(SELECT generate_series (60*60*2, 60*60*48, 60 * 60)) -- for last 2 days, per hour + ,array_fill (5, ARRAY[60]) -- precision: 5s + || array_fill (5 * 60, ARRAY[48-1]) -- precision: 5 minutes + ), + ('orders-paid' + ,'number of orders paid (but not necessarily settled by the exchange)' + ,'number' + ,ARRAY(SELECT generate_series (1, 60*60, 60)) -- for last hour, per minute + || ARRAY(SELECT generate_series (60*60*2, 60*60*48, 60 * 60)) -- for last 2 days, per hour + ,array_fill (5, ARRAY[60]) -- precision: 5s + || array_fill (5 * 60, ARRAY[48-1]) -- precision: 5 minutes + ), + ('orders-settled' + ,'number of orders settled' + ,'number' + ,ARRAY(SELECT generate_series (1, 60*60, 60)) -- for last hour, per minute + || ARRAY(SELECT generate_series (60*60*2, 60*60*48, 60 * 60)) -- for last 2 days, per hour + ,array_fill (5, ARRAY[60]) -- precision: 5s + || array_fill (5 * 60, ARRAY[48-1]) -- precision: 5 minutes + ), + ('tokens-issued' + ,'number of tokens issued to customers' + ,'number' + ,ARRAY(SELECT generate_series (1, 60*60, 60)) -- for last hour, per minute + || ARRAY(SELECT generate_series (60*60*2, 60*60*48, 60 * 60)) -- for last 2 days, per hour + ,array_fill (5, ARRAY[60]) -- precision: 5s + || array_fill (5 * 60, ARRAY[48-1]) -- precision: 5 minutes + ), + ('tokens-used' + ,'number of tokens used by customers' + ,'number' + ,ARRAY(SELECT generate_series (1, 60*60, 60)) -- for last hour, per minute + || ARRAY(SELECT generate_series (60*60*2, 60*60*48, 60 * 60)) -- for last 2 days, per hour + ,array_fill (5, ARRAY[60]) -- precision: 5s + || array_fill (5 * 60, ARRAY[48-1]) -- precision: 5 minutes + ) + ON CONFLICT DO NOTHING; + + -- Enable bucket statistics + INSERT INTO merchant_statistic_bucket_meta + (slug + ,description + ,stype + ,ranges + ,ages) + VALUES + ('payments-received-after-deposit-fee' + ,'amount customers paid to us (excluded deposit fees paid by us or customers, wire fees are still deducted by the exchange)' + ,'amount' + ,ARRAY['hour'::merchant.statistic_range, 'day', 'week', 'month', 'quarter', 'year'] + ,ARRAY[72, 14, 12, 24, 12, 10] + ), + ('total-deposit-fees-paid' + ,'deposit fees we or our customers paid to the exchange (includes those waived on refunds)' + ,'amount' + ,ARRAY['hour'::merchant.statistic_range, 'day', 'week', 'month', 'quarter', 'year'] + ,ARRAY[72, 14, 12, 24, 12, 10] + ), + ('total-wire-fees-paid' + ,'wire fees we paid to the exchange' + ,'amount' + ,ARRAY['hour'::merchant.statistic_range, 'day', 'week', 'month', 'quarter', 'year'] + ,ARRAY[72, 12, 12, 24, 12, 10] + ), + ('refunds-granted' + ,'refunds granted by us to our customers' + ,'amount' + ,ARRAY['hour'::merchant.statistic_range, 'day', 'week', 'month', 'quarter', 'year'] + ,ARRAY[72, 14, 12, 24, 12, 10] + ), + ('tokens-issued' + ,'number of tokens issued to customers' + ,'number' + ,ARRAY['hour'::merchant.statistic_range, 'day', 'week', 'month', 'quarter', 'year'] + ,ARRAY[72, 14, 12, 24, 12, 10] + ), + ('tokens-used' + ,'number of tokens used by customers' + ,'number' + ,ARRAY['hour'::merchant.statistic_range, 'day', 'week', 'month', 'quarter', 'year'] + ,ARRAY[72, 14, 12, 24, 12, 10] + ) + ON CONFLICT DO NOTHING; + SET LOCAL search_path TO merchant; END $OUTER$;