mte/include/sql/exchange_do_insert_aml_decision.sql
2025-11-11 10:03:07 +01:00

337 lines
8.8 KiB
PL/PgSQL

--
-- This file is part of TALER
-- Copyright (C) 2023, 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/>
--
DROP FUNCTION IF EXISTS exchange_do_insert_aml_decision;
CREATE FUNCTION exchange_do_insert_aml_decision(
IN in_payto_uri TEXT, -- can be NULL!
IN in_h_normalized_payto BYTEA,
IN in_h_full_payto BYTEA, -- can be NULL!
IN in_decision_time INT8,
IN in_expiration_time INT8,
IN in_properties JSONB, -- can be NULL
IN in_kyc_attributes_enc BYTEA, -- can be NULL
IN in_kyc_attributes_hash BYTEA, -- can be NULL
IN in_kyc_attributes_expiration INT8, -- can be NULL
IN in_new_rules JSONB,
IN in_to_investigate BOOLEAN,
IN in_new_measure_name TEXT, -- can be NULL
IN in_jmeasures JSONB, -- can be NULL
IN in_justification TEXT, -- can be NULL
IN in_decider_pub BYTEA, -- can be NULL
IN in_decider_sig BYTEA, -- can be NULL
IN in_notify_s TEXT,
IN ina_events TEXT[],
IN in_form_name TEXT, -- can be NULL
OUT out_invalid_officer BOOLEAN,
OUT out_account_unknown BOOLEAN,
OUT out_last_date INT8,
OUT out_legitimization_measure_serial_id INT8,
OUT out_is_wallet BOOL) -- can be (left at) NULL
LANGUAGE plpgsql
AS $$
DECLARE
my_outcome_serial_id INT8;
my_legitimization_process_serial_id INT8;
my_kyc_attributes_serial_id INT8;
my_rec RECORD;
my_access_token BYTEA;
my_i INT4;
ini_event TEXT;
BEGIN
out_account_unknown=FALSE;
out_legitimization_measure_serial_id=0;
IF in_decider_pub IS NOT NULL
THEN
IF in_justification IS NULL OR in_decider_sig IS NULL
THEN
RAISE EXCEPTION 'Got in_decider_sig without justification or signature.';
END IF;
-- Check officer is eligible to make decisions.
PERFORM
FROM aml_staff
WHERE decider_pub=in_decider_pub
AND is_active
AND NOT read_only;
IF NOT FOUND
THEN
out_invalid_officer=TRUE;
out_last_date=0;
RETURN;
END IF;
END IF;
out_invalid_officer=FALSE;
-- Check no more recent decision exists.
SELECT decision_time
INTO out_last_date
FROM legitimization_outcomes
WHERE h_payto=in_h_normalized_payto
AND is_active
ORDER BY decision_time DESC, outcome_serial_id DESC;
IF FOUND
THEN
IF in_decider_pub IS NOT NULL AND out_last_date > in_decision_time
THEN
-- Refuse to insert older decision for officer decisions.
RETURN;
END IF;
UPDATE legitimization_outcomes
SET is_active=FALSE
WHERE h_payto=in_h_normalized_payto
AND is_active;
ELSE
out_last_date = 0;
END IF;
SELECT access_token
,is_wallet
INTO my_rec
FROM kyc_targets
WHERE h_normalized_payto=in_h_normalized_payto;
IF NOT FOUND
THEN
-- AML decision for previously unknown account; better includes
-- all required details about the account ...
IF in_payto_uri IS NULL
THEN
-- AML decision on an unknown account without payto_uri => fail.
out_account_unknown=TRUE;
RETURN;
END IF;
-- Well, fine, setup the account
out_is_wallet
= (LOWER (SUBSTRING (in_payto_uri, 22)) =
'payto://taler-reserve/') OR
(LOWER (SUBSTRING (in_payto_uri, 27)) =
'payto://taler-reserve-http/');
INSERT INTO kyc_targets
(h_normalized_payto
,is_wallet
) VALUES (
in_h_normalized_payto
,out_is_wallet
)
RETURNING access_token
INTO my_access_token;
INSERT INTO wire_targets
(wire_target_h_payto
,h_normalized_payto
,payto_uri
) VALUES (
in_h_full_payto
,in_h_normalized_payto
,in_payto_uri
)
ON CONFLICT DO NOTHING;
ELSE
my_access_token = my_rec.access_token;
out_is_wallet = my_rec.is_wallet;
END IF;
-- Did KYC measures get prescribed?
IF in_jmeasures IS NOT NULL
THEN
-- First check if a perfectly equivalent legi measure
-- already exists, to avoid creating tons of duplicates.
SELECT legitimization_measure_serial_id
INTO out_legitimization_measure_serial_id
FROM legitimization_measures
WHERE access_token=my_access_token
AND jmeasures=in_jmeasures
AND NOT is_finished;
IF NOT FOUND
THEN
-- Enable new legitimization measure
INSERT INTO legitimization_measures
(access_token
,start_time
,jmeasures
,display_priority
) VALUES (
my_access_token
,in_decision_time
,in_jmeasures
,1)
RETURNING
legitimization_measure_serial_id
INTO
out_legitimization_measure_serial_id;
END IF;
-- end if for where we had in_jmeasures
END IF;
RAISE NOTICE 'marking legi measures of % as finished except for %', my_access_token, out_legitimization_measure_serial_id;
-- AML decision: mark all other active measures finished!
UPDATE legitimization_measures
SET is_finished=TRUE
WHERE access_token=my_access_token
AND NOT is_finished
AND legitimization_measure_serial_id != out_legitimization_measure_serial_id;
UPDATE legitimization_outcomes
SET is_active=FALSE
WHERE h_payto=in_h_normalized_payto
-- this clause is a minor optimization to avoid
-- updating outcomes that have long expired.
AND expiration_time >= in_decision_time;
INSERT INTO legitimization_outcomes
(h_payto
,decision_time
,expiration_time
,jproperties
,new_measure_name
,to_investigate
,jnew_rules
) VALUES (
in_h_normalized_payto
,in_decision_time
,in_expiration_time
,in_properties
,in_new_measure_name
,in_to_investigate
,in_new_rules
)
RETURNING outcome_serial_id
INTO my_outcome_serial_id;
IF in_kyc_attributes_enc IS NOT NULL
THEN
IF in_kyc_attributes_hash IS NULL OR in_kyc_attributes_expiration IS NULL
THEN
RAISE EXCEPTION 'Got in_kyc_attributes_hash without hash or expiration.';
END IF;
IF in_decider_pub IS NULL
THEN
RAISE EXCEPTION 'Got in_kyc_attributes_hash without in_decider_pub.';
END IF;
-- Simulate a legi process for attribute insertion by AML Officer
INSERT INTO legitimization_processes
(h_payto
,start_time
,expiration_time
,provider_name
,provider_user_id
,finished
) VALUES (
in_h_normalized_payto
,in_decision_time
-- Process starts and finishes instantly
,in_decision_time
,'aml-officer'
,in_decider_pub
,TRUE
)
RETURNING legitimization_process_serial_id
INTO my_legitimization_process_serial_id;
-- Now we can insert the attribute!
INSERT INTO kyc_attributes
(h_payto
,collection_time
,expiration_time
,form_name
,by_aml_officer
,encrypted_attributes
,legitimization_serial
) VALUES (
in_h_normalized_payto
,in_decision_time
,in_kyc_attributes_expiration
,in_form_name
,TRUE
,in_kyc_attributes_enc
,my_legitimization_process_serial_id
)
RETURNING kyc_attributes_serial_id
INTO my_kyc_attributes_serial_id;
-- Wake up taler-exchange-sanctionscheck to check new attributes
-- This is value for TALER_DBEVENT_EXCHANGE_NEW_KYC_ATTRIBUTES.
NOTIFY XSX9Z5XGWWYFKXTAYCES63B62527JKNX9XD0131Z08THVV8YW5BZG;
END IF;
IF in_decider_pub IS NOT NULL
THEN
INSERT INTO aml_history
(h_payto
,outcome_serial_id
,justification
,decider_pub
,decider_sig
,kyc_attributes_hash
,kyc_attributes_serial_id
) VALUES (
in_h_normalized_payto
,my_outcome_serial_id
,in_justification
,in_decider_pub
,in_decider_sig
,in_kyc_attributes_hash
,my_kyc_attributes_serial_id
);
END IF;
-- Trigger events
FOR i IN 1..COALESCE(array_length(ina_events,1),0)
LOOP
ini_event = ina_events[i];
INSERT INTO kyc_events
(event_timestamp
,event_type
) VALUES (
in_decision_time
,ini_event);
IF (ini-event = 'ACCOUNT_OPEN')
THEN
UPDATE kyc_targets
SET open_time=in_decision_time
,close_time=NULL
WHERE h_normalized_payto=in_h_normalized_payto;
END IF;
IF (ini-event = 'ACCOUNT_IDLE')
THEN
UPDATE kyc_targets
SET close_time=in_decision_time
WHERE h_normalized_payto=in_h_normalized_payto;
END IF;
END LOOP;
-- wake up taler-exchange-aggregator
INSERT INTO kyc_alerts
(h_payto
,trigger_type
) VALUES (
in_h_normalized_payto
,1
)
ON CONFLICT DO NOTHING;
EXECUTE FORMAT (
'NOTIFY %s'
,in_notify_s);
END $$;
COMMENT ON FUNCTION exchange_do_insert_aml_decision(TEXT, BYTEA, BYTEA, INT8, INT8, JSONB, BYTEA, BYTEA, INT8, JSONB, BOOLEAN, TEXT, JSONB, TEXT, BYTEA, BYTEA, TEXT, TEXT[], TEXT)
IS 'Checks whether the AML officer is eligible to make AML decisions and if so inserts the decision into the table';