cp sql/ from GNU Taler exchange
This commit is contained in:
parent
4749bb7c62
commit
11ee418801
132 changed files with 16912 additions and 0 deletions
227
include/sql/tops-0001.sql
Normal file
227
include/sql/tops-0001.sql
Normal 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;
|
||||
Loading…
Add table
Add a link
Reference in a new issue