cp sql/ from GNU Taler exchange

This commit is contained in:
swrup 2025-11-11 10:03:07 +01:00
parent 4749bb7c62
commit 11ee418801
132 changed files with 16912 additions and 0 deletions

227
include/sql/tops-0001.sql Normal file
View file

@ -0,0 +1,227 @@
--
-- This file is part of TALER
-- Copyright (C) 2025 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 tops-0001.sql
-- @brief special TOPS-specific (AML) rules to inject into an exchange
-- @author Christian Grothoff
-- Everything in one big transaction
BEGIN;
-- Check patch versioning is in place.
SELECT _v.register_patch('tops-0001', NULL, NULL);
-- Note: this NOT an accident: the schema MUST be named
-- using the filename prefix (and the name under --enable-custom of taler-exchange-dbinit).
CREATE SCHEMA IF NOT EXISTS tops;
SET search_path TO tops,exchange;
INSERT INTO exchange_statistic_interval_meta
(origin
,slug
,description
,stype
,ranges
,precisions)
VALUES
-- this first one is just for testing right now
('tops' -- must match schema!
,'deposit-transactions'
,'number of (batch) deposits performed by this merchant, used to detect sudden increase in number of transactions'
,'number'
,ARRAY(SELECT generate_series (60*60*24*7, 60*60*24*7*52, 60*60*24*7)) -- weekly volume over the last year
,array_fill (60*60*24, ARRAY[52]) -- precision is per day
),
('tops' -- must match schema!
,'deposit-volume'
,'total amount deposited by this merchant in (batch) deposits including deposit fees, used to detect sudden increase in transaction volume'
,'amount'
,ARRAY(SELECT generate_series (60*60*24*7, 60*60*24*7*52, 60*60*24*7)) -- weekly volume over the last year
,array_fill (60*60*24, ARRAY[52]) -- precision is per day
)
ON CONFLICT DO NOTHING;
INSERT INTO exchange_statistic_bucket_meta
(origin
,slug
,description
,stype
,ranges
,ages)
VALUES
-- this first one is just for testing right now
('tops' -- must match schema!
,'deposit-transactions'
,'number of (batch) deposits performed by this merchant, used to detect sudden increase in number of transactions'
,'number'
,ARRAY['day'::statistic_range,'week']
,ARRAY[5,5]
),
('tops' -- must match schema!
,'deposit-volume'
,'total amount deposited by this merchant in (batch) deposits including deposit fees, used to detect sudden increase in transaction volume'
,'amount'
,ARRAY['day'::statistic_range,'week']
,ARRAY[5,5]
)
ON CONFLICT DO NOTHING;
DROP FUNCTION IF EXISTS tops_deposit_statistics_trigger CASCADE;
CREATE FUNCTION tops_deposit_statistics_trigger()
RETURNS trigger
LANGUAGE plpgsql
AS $$
DECLARE
my_h_payto BYTEA; -- normalized h_payto of target account
my_rec RECORD;
my_last_year taler_amount; -- sum of deposits this year
my_last_month taler_amount; -- sum of deposits this month
my_old_rules RECORD;
my_properties TEXT;
my_investigate_property JSONB;
my_measure_name TEXT;
my_rules TEXT;
BEGIN
SELECT wt.h_normalized_payto
INTO my_h_payto
FROM wire_targets wt
WHERE wire_target_h_payto = NEW.wire_target_h_payto;
CALL exchange_do_bump_amount_stat
('deposit-volume'
,my_h_payto
,CURRENT_TIMESTAMP(0)::TIMESTAMP
,NEW.total_amount);
-- FIXME: this is just for testing, I want to also check
-- the 'counter'-based functions.
CALL exchange_do_bump_number_stat
('deposit-transactions'
,my_h_payto
,CURRENT_TIMESTAMP(0)::TIMESTAMP
,1);
-- Get historical deposit volumes and extract the yearly and monthly
-- interval statistic values from the result for the AML trigger check.
FOR my_rec IN
SELECT *
FROM exchange_statistic_interval_amount_get(
'deposit-volume'
,my_h_payto
)
LOOP
IF (my_rec.range = 60*60*24*7*52)
THEN
my_last_year = my_rec.rvalue;
END IF;
IF (my_rec.range = 60*60*24*7*4)
THEN
my_last_month = my_rec.rvalue;
END IF;
END LOOP;
-- Note: it is OK to ignore '.frac', as that cannot be significant.
-- Also, we effectively exclude the current month's revenue from
-- "last year" as otherwise the rule makes no sense.
-- Finally, we define the "current month" always as the last 4 weeks,
-- just like the "last year" is the last 52 weeks.
IF (my_last_year.val < my_last_month.val * 2)
THEN
-- This is suspicious. => Flag account for AML review!
--
-- FIXME: we probably want to factor the code from
-- this branch out into a generic
-- function to trigger investigations at some point!
--
-- First, get existing rules and clear an 'is_active'
-- flag, but ONLY if we are not _already_ investigating
-- the account (as in the latter case, we'll do no INSERT).
UPDATE legitimization_outcomes
SET is_active=NOT to_investigate
WHERE h_payto = my_h_payto
AND is_active
RETURNING jproperties
,new_measure_name
,jnew_rules
,to_investigate
INTO my_old_rules;
-- Note that if we have no active legitimization_outcome
-- that means we are on default rules and the account
-- did not cross KYC thresholds and thus we have no
-- established business relationship. In this case, we
-- do not care as the overall volume is insignificant.
-- This also takes care of the case where a customer
-- is new (and obviously the first few months are
-- basically always above the inherently zero or near-zero
-- transactions from the previous year).
-- Thus, we only proceed IF FOUND.
IF FOUND
THEN
my_properties = my_old_rules.jproperties;
my_measure_name = my_old_rules.new_measure_name;
my_rules = my_old_rules.jnew_rules;
my_investigate_property = json_object(ARRAY['AML_INVESTIGATION_STATE',
'AML_INVESTIGATION_TRIGGER'],
ARRAY['INVESTIATION_PENDING',
'DEPOSIT_ANOMALY']);
IF my_properties IS NULL
THEN
my_properties = my_investigate_property::TEXT;
ELSE
my_properties = (my_properties::JSONB || my_investigate_property)::TEXT;
END IF;
-- Note: here we could in theory manipulate my_properties,
-- say to set a note as to why the investigation was started.
IF NOT my_old_rules.to_investigate
THEN
-- Only insert if 'to_investigate' was not already set.
INSERT INTO legitimization_outcomes (
h_payto
,decision_time
,expiration_time
,jproperties
,new_measure_name
,to_investigate
,is_active
,jnew_rules
) VALUES (
my_h_payto
,my_now
,my_now + 366*24*60*60
,my_properties
,my_measure_name
,TRUE
,TRUE
,my_rules);
END IF;
END IF;
END IF;
RETURN NEW;
END $$;
COMMENT ON FUNCTION tops_deposit_statistics_trigger
IS 'creates deposit statistics';
-- Whenever a deposit is made, call our trigger to bump statistics
CREATE TRIGGER tops_batch_deposits_on_insert
AFTER INSERT
ON batch_deposits
FOR EACH ROW EXECUTE FUNCTION tops_deposit_statistics_trigger();
COMMIT;