exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

commit 749aec3207ec3d8b203b3f0d6720a1e1354c2d04
parent f5eb43fc93770b12885433ffb5b1720e9f0a6d95
Author: Florian Dold <dold@taler.net>
Date:   Mon, 31 Aug 2026 10:57:57 +0200

exchange: expose consistent AML account summaries

Diffstat:
Msrc/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-accounts.c | 23+++++++++++++++++++----
Msrc/exchangedb/iterate_kyc_accounts.c | 16++++++++++++++++
Msrc/exchangedb/test_kyc_targets.c | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/include/exchange-database/iterate_kyc_accounts.h | 7+++++++
4 files changed, 95 insertions(+), 4 deletions(-)

diff --git a/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-accounts.c b/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-accounts.c @@ -137,6 +137,7 @@ free_rc (struct ResponseContext *rc) * @param close_time when was the account formally closed, * GNUNET_TIME_UNIT_ZERO_TS if it was never closed * @param comments comments on the account + * @param customer_label human-readable customer label * @param high_risk is this a high-risk business relationship * @param to_investigate TRUE if this account should be investigated * @param payto the payto URI of the account @@ -149,13 +150,13 @@ record_cb ( struct GNUNET_TIME_Timestamp open_time, struct GNUNET_TIME_Timestamp close_time, const char *comments, + const char *customer_label, bool high_risk, bool to_investigate, struct TALER_FullPayto payto) { const bool below_opening_threshold = - (NULL == comments) && - (GNUNET_TIME_absolute_is_never (open_time.abs_time)); + GNUNET_TIME_absolute_is_never (open_time.abs_time); const char *system_note = below_opening_threshold ? "BELOW_ACCOUNT_OPENING_THRESHOLD" : NULL; @@ -178,6 +179,9 @@ record_cb ( GNUNET_JSON_pack_string ("comments", comments)), GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("customer_label", + customer_label)), + GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ("system_note", system_note)), GNUNET_JSON_pack_int64 ("rowid", @@ -201,7 +205,8 @@ record_cb ( const char *export_comments = comments; epayto = TALER_escape_xml (payto.full_payto); - if (below_opening_threshold) + if ( (NULL == export_comments) && + below_opening_threshold) export_comments = "System: transactions remain below the threshold " "that opens an AML file"; @@ -270,7 +275,8 @@ record_cb ( size_t plen = strlen (payto.full_payto); size_t wpos = 0; - if (below_opening_threshold) + if ( (NULL == export_comments) && + below_opening_threshold) export_comments = "System: transactions remain below the threshold " "that opens an AML file"; @@ -337,6 +343,8 @@ TEH_handler_aml_accounts_get ( enum TALER_EXCHANGE_YesNoAll open_filter; enum TALER_EXCHANGE_YesNoAll high_risk_filter; enum TALER_EXCHANGE_YesNoAll investigation_filter; + struct TALER_NormalizedPaytoHashP h_payto; + bool have_payto = false; memset (&rctx, 0, @@ -377,6 +385,10 @@ TEH_handler_aml_accounts_get ( "high_risk", TALER_EXCHANGE_YNA_ALL, &high_risk_filter); + TALER_MHD_parse_request_arg_auto (rc->connection, + "h_payto", + &h_payto, + have_payto); { const char *mime; @@ -426,6 +438,9 @@ TEH_handler_aml_accounts_get ( limit = -MAX_RECORDS; qs = TALER_EXCHANGEDB_iterate_kyc_accounts ( TEH_pg, + have_payto + ? &h_payto + : NULL, investigation_filter, open_filter, high_risk_filter, diff --git a/src/exchangedb/iterate_kyc_accounts.c b/src/exchangedb/iterate_kyc_accounts.c @@ -78,6 +78,7 @@ handle_kyc_account_cb (void *cls, uint64_t rowid; struct TALER_NormalizedPaytoHashP h_payto; char *comments = NULL; + char *customer_label = NULL; struct GNUNET_TIME_Timestamp open_time = GNUNET_TIME_UNIT_FOREVER_TS; struct GNUNET_TIME_Timestamp close_time @@ -95,6 +96,10 @@ handle_kyc_account_cb (void *cls, &comments), NULL), GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_string ("customer_label", + &customer_label), + NULL), + GNUNET_PQ_result_spec_allow_null ( GNUNET_PQ_result_spec_timestamp ("open_time", &open_time), NULL), @@ -126,6 +131,7 @@ handle_kyc_account_cb (void *cls, open_time, close_time, comments, + customer_label, high_risk, to_investigate, payto); @@ -137,6 +143,7 @@ handle_kyc_account_cb (void *cls, enum GNUNET_DB_QueryStatus TALER_EXCHANGEDB_iterate_kyc_accounts ( struct TALER_EXCHANGEDB_PostgresContext *pg, + const struct TALER_NormalizedPaytoHashP *h_payto, enum TALER_EXCHANGE_YesNoAll investigation_only, enum TALER_EXCHANGE_YesNoAll open_only, enum TALER_EXCHANGE_YesNoAll high_risk_only, @@ -147,6 +154,7 @@ TALER_EXCHANGEDB_iterate_kyc_accounts ( { uint64_t ulimit = GNUNET_MIN ((uint64_t) MAX_RECORDS, TALER_EXCHANGEDB_abs_limit (limit)); + const struct TALER_NormalizedPaytoHashP no_h_payto = { 0 }; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint64 (&offset), GNUNET_PQ_query_param_uint64 (&ulimit), @@ -162,6 +170,10 @@ TALER_EXCHANGEDB_iterate_kyc_accounts ( high_risk_only)), GNUNET_PQ_query_param_bool ((TALER_EXCHANGE_YNA_YES == high_risk_only)), + GNUNET_PQ_query_param_bool (NULL == h_payto), + GNUNET_PQ_query_param_auto_from_type (NULL == h_payto + ? &no_h_payto + : h_payto), GNUNET_PQ_query_param_end }; struct KycAccountResultContext ctx = { @@ -183,6 +195,7 @@ TALER_EXCHANGEDB_iterate_kyc_accounts ( ",kt.open_time" ",kt.close_time" ",lo.jproperties ->> 'FILE_NOTE' AS comments" + ",lo.jproperties ->> 'CUSTOMER_LABEL' AS customer_label" ",COALESCE(lo.to_investigate,FALSE) AS to_investigate" ",COALESCE((lo.jproperties ->> 'HIGH_RISK_CUSTOMER')::bool,FALSE) AS high_risk" ",wt.payto_uri" @@ -204,6 +217,7 @@ TALER_EXCHANGEDB_iterate_kyc_accounts ( " AND ($5 OR (((kt.open_time IS NOT NULL)" " AND (kt.close_time IS NULL)) = $6))" " AND ($7 OR ((COALESCE((lo.jproperties ->>'HIGH_RISK_CUSTOMER')::bool,FALSE) = $8)))" + " AND ($9 OR (kt.h_normalized_payto = $10))" " ORDER BY kt.kyc_target_serial_id ASC" " LIMIT $2"); PREPARE (pg, @@ -214,6 +228,7 @@ TALER_EXCHANGEDB_iterate_kyc_accounts ( ",kt.open_time" ",kt.close_time" ",lo.jproperties ->> 'FILE_NOTE' AS comments" + ",lo.jproperties ->> 'CUSTOMER_LABEL' AS customer_label" ",COALESCE(lo.to_investigate,FALSE) AS to_investigate" ",COALESCE((lo.jproperties ->> 'HIGH_RISK_CUSTOMER')::bool,FALSE) AS high_risk" ",wt.payto_uri" @@ -235,6 +250,7 @@ TALER_EXCHANGEDB_iterate_kyc_accounts ( " AND ($5 OR (((kt.open_time IS NOT NULL)" " AND (kt.close_time IS NULL)) = $6))" " AND ($7 OR ((COALESCE((lo.jproperties ->>'HIGH_RISK_CUSTOMER')::bool,FALSE) = $8)))" + " AND ($9 OR (kt.h_normalized_payto = $10))" " ORDER BY kt.kyc_target_serial_id DESC" " LIMIT $2"); qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, diff --git a/src/exchangedb/test_kyc_targets.c b/src/exchangedb/test_kyc_targets.c @@ -174,6 +174,7 @@ struct AccountContext * @param open_time when the account was opened * @param close_time when it was closed * @param comments file note on the account + * @param customer_label human-readable customer label * @param high_risk whether it is a high-risk relationship * @param to_investigate whether it is flagged for investigation * @param payto the account's payto URI @@ -185,6 +186,7 @@ account_cb (void *cls, struct GNUNET_TIME_Timestamp open_time, struct GNUNET_TIME_Timestamp close_time, const char *comments, + const char *customer_label, bool high_risk, bool to_investigate, struct TALER_FullPayto payto) @@ -194,6 +196,7 @@ account_cb (void *cls, (void) open_time; (void) close_time; (void) comments; + (void) customer_label; if (0 == ctx->total++) ctx->first_row = row_id; if ( (NULL != ctx->h_payto) && @@ -248,6 +251,7 @@ check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg) &h_payto)); FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != TALER_EXCHANGEDB_iterate_kyc_accounts (pg, + NULL, TALER_EXCHANGE_YNA_ALL, TALER_EXCHANGE_YNA_ALL, TALER_EXCHANGE_YNA_ALL, @@ -375,6 +379,7 @@ static int check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) { struct AccountContext ctx; + struct TALER_NormalizedPaytoHashP unknown; uint64_t lowest; TDB_account (pg, @@ -386,6 +391,7 @@ check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) ctx.h_payto = &account.h_normalized; FAILIF (2 != TALER_EXCHANGEDB_iterate_kyc_accounts (pg, + NULL, TALER_EXCHANGE_YNA_ALL, TALER_EXCHANGE_YNA_ALL, TALER_EXCHANGE_YNA_ALL, @@ -400,12 +406,49 @@ check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) FAILIF (ctx.high_risk); lowest = ctx.first_row; + /* An exact account filter returns only the requested account. */ + memset (&ctx, + 0, + sizeof (ctx)); + ctx.h_payto = &account.h_normalized; + FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + TALER_EXCHANGEDB_iterate_kyc_accounts (pg, + &account.h_normalized, + TALER_EXCHANGE_YNA_ALL, + TALER_EXCHANGE_YNA_ALL, + TALER_EXCHANGE_YNA_ALL, + 0, + 10, + &account_cb, + &ctx)); + FAILIF (1 != ctx.total); + FAILIF (1 != ctx.matched); + + /* An unknown exact account filter returns no result. */ + TDB_FILL (unknown, + 99); + memset (&ctx, + 0, + sizeof (ctx)); + FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != + TALER_EXCHANGEDB_iterate_kyc_accounts (pg, + &unknown, + TALER_EXCHANGE_YNA_ALL, + TALER_EXCHANGE_YNA_ALL, + TALER_EXCHANGE_YNA_ALL, + 0, + 10, + &account_cb, + &ctx)); + FAILIF (0 != ctx.total); + /* the limit caps the result set */ memset (&ctx, 0, sizeof (ctx)); FAILIF (1 != TALER_EXCHANGEDB_iterate_kyc_accounts (pg, + NULL, TALER_EXCHANGE_YNA_ALL, TALER_EXCHANGE_YNA_ALL, TALER_EXCHANGE_YNA_ALL, @@ -421,6 +464,7 @@ check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) sizeof (ctx)); FAILIF (2 != TALER_EXCHANGEDB_iterate_kyc_accounts (pg, + NULL, TALER_EXCHANGE_YNA_ALL, TALER_EXCHANGE_YNA_ALL, TALER_EXCHANGE_YNA_ALL, @@ -436,6 +480,7 @@ check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) sizeof (ctx)); FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != TALER_EXCHANGEDB_iterate_kyc_accounts (pg, + NULL, TALER_EXCHANGE_YNA_YES, TALER_EXCHANGE_YNA_ALL, TALER_EXCHANGE_YNA_ALL, @@ -450,6 +495,7 @@ check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) sizeof (ctx)); FAILIF (2 != TALER_EXCHANGEDB_iterate_kyc_accounts (pg, + NULL, TALER_EXCHANGE_YNA_NO, TALER_EXCHANGE_YNA_ALL, TALER_EXCHANGE_YNA_ALL, @@ -464,6 +510,7 @@ check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) sizeof (ctx)); FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != TALER_EXCHANGEDB_iterate_kyc_accounts (pg, + NULL, TALER_EXCHANGE_YNA_ALL, TALER_EXCHANGE_YNA_ALL, TALER_EXCHANGE_YNA_YES, @@ -479,6 +526,7 @@ check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) sizeof (ctx)); FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != TALER_EXCHANGEDB_iterate_kyc_accounts (pg, + NULL, TALER_EXCHANGE_YNA_ALL, TALER_EXCHANGE_YNA_YES, TALER_EXCHANGE_YNA_ALL, @@ -491,6 +539,7 @@ check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) sizeof (ctx)); FAILIF (2 != TALER_EXCHANGEDB_iterate_kyc_accounts (pg, + NULL, TALER_EXCHANGE_YNA_ALL, TALER_EXCHANGE_YNA_NO, TALER_EXCHANGE_YNA_ALL, @@ -509,6 +558,7 @@ check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) sizeof (ctx)); FAILIF (1 != TALER_EXCHANGEDB_iterate_kyc_accounts (pg, + NULL, TALER_EXCHANGE_YNA_ALL, TALER_EXCHANGE_YNA_YES, TALER_EXCHANGE_YNA_ALL, @@ -521,6 +571,7 @@ check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) sizeof (ctx)); FAILIF (1 != TALER_EXCHANGEDB_iterate_kyc_accounts (pg, + NULL, TALER_EXCHANGE_YNA_ALL, TALER_EXCHANGE_YNA_NO, TALER_EXCHANGE_YNA_ALL, @@ -539,6 +590,7 @@ check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) sizeof (ctx)); FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != TALER_EXCHANGEDB_iterate_kyc_accounts (pg, + NULL, TALER_EXCHANGE_YNA_ALL, TALER_EXCHANGE_YNA_YES, TALER_EXCHANGE_YNA_ALL, @@ -551,6 +603,7 @@ check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg) sizeof (ctx)); FAILIF (2 != TALER_EXCHANGEDB_iterate_kyc_accounts (pg, + NULL, TALER_EXCHANGE_YNA_ALL, TALER_EXCHANGE_YNA_NO, TALER_EXCHANGE_YNA_ALL, diff --git a/src/include/exchange-database/iterate_kyc_accounts.h b/src/include/exchange-database/iterate_kyc_accounts.h @@ -37,6 +37,7 @@ * @param close_time when was the account formally closed, * GNUNET_TIME_UNIT_ZERO_TS if it was never closed * @param comments comments on the account + * @param customer_label human-readable customer label * @param high_risk is this a high-risk business relationship * @param to_investigate TRUE if this account should be investigated * @param payto the payto URI of the account @@ -55,6 +56,7 @@ typedef void struct GNUNET_TIME_Timestamp open_time, struct GNUNET_TIME_Timestamp close_time, const char *comments, + const char *customer_label, bool high_risk, bool to_investigate, struct TALER_FullPayto payto); @@ -72,6 +74,7 @@ typedef void * @param close_time when was the account formally closed, * GNUNET_TIME_UNIT_ZERO_TS if it was never closed * @param comments comments on the account + * @param customer_label human-readable customer label * @param high_risk is this a high-risk business relationship * @param to_investigate TRUE if this account should be investigated * @param payto the payto URI of the account @@ -84,6 +87,7 @@ typedef void struct GNUNET_TIME_Timestamp open_time, struct GNUNET_TIME_Timestamp close_time, const char *comments, + const char *customer_label, bool high_risk, bool to_investigate, struct TALER_FullPayto payto); @@ -94,6 +98,7 @@ typedef void * Primary test table: `kyc_targets` (see test_kyc_targets.c). * * @param pg the database context + * @param h_payto account to return, NULL to return all accounts * @param investigation_only filter by investigation state * @param open_only filter for only open accounts * @param high_risk_only filter for only high-risk accounts @@ -107,6 +112,8 @@ typedef void enum GNUNET_DB_QueryStatus TALER_EXCHANGEDB_iterate_kyc_accounts (struct TALER_EXCHANGEDB_PostgresContext * pg, + const struct + TALER_NormalizedPaytoHashP *h_payto, enum TALER_EXCHANGE_YesNoAll investigation_only, enum TALER_EXCHANGE_YesNoAll open_only,