-- -- 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 -- 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';