exchange

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

commit 0b9dfe45e1d635dcbd4107e7e3094eed525b1c5f
parent 437c041cb5b22b959938f45e8b18fc93269a79b7
Author: Christian Grothoff <christian@grothoff.org>
Date:   Fri,  5 Dec 2025 13:53:16 +0100

work on #10666: get AML file number, output attachments, towards cover page generation (WiP, untested)

Diffstat:
Mcontrib/typst/Makefile.am | 1+
Acontrib/typst/_cover_.typ | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/exchange/taler-exchange-httpd_aml-attributes-get.c | 224+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Msrc/exchangedb/Makefile.am | 1+
Msrc/exchangedb/exchangedb_history.c | 4++++
Asrc/exchangedb/pg_lookup_aml_file_number.c | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/exchangedb/pg_lookup_aml_file_number.h | 44++++++++++++++++++++++++++++++++++++++++++++
Msrc/exchangedb/pg_lookup_aml_history.c | 38+++++++++++++++++++++++++++++++++++---
Msrc/exchangedb/pg_lookup_aml_history.h | 4++++
Msrc/exchangedb/plugin_exchangedb_postgres.c | 3+++
Msrc/include/taler/taler_error_codes.h | 8++++++++
Msrc/include/taler/taler_exchangedb_plugin.h | 20++++++++++++++++++++
Msrc/util/taler_error_codes.c | 10+++++++++-
13 files changed, 494 insertions(+), 13 deletions(-)

diff --git a/contrib/typst/Makefile.am b/contrib/typst/Makefile.am @@ -2,6 +2,7 @@ SUBDIRS = . formdatadir = $(datadir)/taler-exchange/typst-forms/ dist_formdata_DATA = \ + _cover_.typ \ challenger_postal.typ \ challenger_sms.typ \ pointing_finger.svg \ diff --git a/contrib/typst/_cover_.typ b/contrib/typst/_cover_.typ @@ -0,0 +1,91 @@ +// Cover page for AML files. +// Renders all account properties, current rules, etc. + +#let form(data) = { + set page( + paper: "a4", + margin: (left: 2cm, right: 2cm, top: 2cm, bottom: 2.5cm), + footer: context [ + #grid( + columns: (1fr, 1fr), + align: (left, right), + text(size: 8pt)[ + ], + text(size: 8pt)[ + Page #here().page() of #counter(page).final().first() + ] + ) + ] + ) + + set text(font: "Liberation Sans", size: 10pt) + set par(justify: false, leading: 0.65em) + + // Helper function to get value or empty string + let get(key, default: "") = { + data.at(key, default: default) + } + + // Helper function for checkbox + let checkbox(checked) = { + box( + width: 3mm, + height: 3mm, + stroke: 0.5pt + black, + inset: 0.3mm, + if checked == true or checked == "true" { + place(center + horizon, text(size: 8pt, sym.checkmark)) + } + ) + } + + // Header + align(center, text(size: 11pt, weight: "bold")[CONFIDENTIAL]) + + v(0.5em) + + grid( + columns: (50%, 50%), + gutter: 1em, + image("vss_vqf_verein.png", width: 80%), + align(right)[ + #table( + columns: (1fr, 1fr), + stroke: 0.5pt + black, + inset: 5pt, + align: (left, left), + [VQF member no.], [AMLA File No.], + [#get("VQF_MEMBER_NUMBER")], [#get("FILE_NUMBER")] + ) + ] + ) + + v(1em) + + // Section 1: FIXME -- Validated address + text(size: 11pt, weight: "bold")[Validated address:] + + v(0.5em) + + block(breakable: false)[ + #v(0.5em) + #table( + columns: (35%, 65%), + stroke: 0.5pt + black, + inset: 5pt, + [Contact name:], [#get("CONTACT_NAME")], + [Address:], [#get("ADDRESS_LINES").split("\n").join(linebreak())], + [Country:], [#get("ADDRESS_COUNTRY")], + ) + #v(0.5em) + ] +} + +// Example usage: +#form(( + "FILE_NUMBER": "42", + "properties": { "PROP" : "VALUE" }, // FIXME: better example... + "rules": { }, // FIXME: object? + "is_active" : false, + "to_investigate" : false, +)) +\ No newline at end of file diff --git a/src/exchange/taler-exchange-httpd_aml-attributes-get.c b/src/exchange/taler-exchange-httpd_aml-attributes-get.c @@ -185,6 +185,82 @@ free_rc (struct TEH_RequestContext *rc) /** + * Dump attachment @a attach into @a rc. + * + * @param[in,out] rc response context to update + * @param attach attachment to dump + * @return true on success + */ +static bool +dump_attachment (struct ResponseContext *rc, + const json_t *attach) +{ + if (! json_is_string (attach)) + return false; + if (rc->details.pdf.off >= MAX_RECORDS) + { + GNUNET_break (0); + return false; + } + rc->details.pdf.jdata[rc->details.pdf.off] + = json_incref ((json_t *) attach); + rc->details.pdf.docs[rc->details.pdf.off].form_name + = NULL; /* inline PDF */ + rc->details.pdf.docs[rc->details.pdf.off].data + = rc->details.pdf.jdata[rc->details.pdf.off]; + rc->details.pdf.off++; + return true; +} + + +/** + * Recursively scan @a attrs for ".FILE" members and dump those + * attachments into @a rc. + * + * @param[in,out] rc response context to update + * @param attrs attributes to scan recursively + * @return true on success + */ +static bool +dump_attachments (struct ResponseContext *rc, + const json_t *attrs) +{ + bool ret = true; + + if (json_is_array (attrs)) + { + const json_t *e; + size_t i; + + json_array_foreach ((json_t *) attrs, i, e) + if (! dump_attachments (rc, + e)) + ret = false; + } + if (json_is_object (attrs)) + { + const json_t *e; + const char *k; + + json_object_foreach ((json_t *) attrs, k, e) + { + if (0 == strcmp (k, + "FILE")) + { + if (! dump_attachment (rc, + e)) + ret = false; + } + else if (! dump_attachments (rc, + e)) + ret = false; + } + } + return ret; +} + + +/** * Return AML account attributes. * * @param cls closure @@ -249,7 +325,11 @@ detail_cb ( ))); break; case RCF_PDF: - GNUNET_assert (rc->details.pdf.off < MAX_RECORDS); + if (rc->details.pdf.off >= MAX_RECORDS) + { + GNUNET_break (0); + return; + } GNUNET_assert (0 == json_object_set_new (attrs, "DATADIR", @@ -270,8 +350,6 @@ detail_cb ( json_true ())); GNUNET_free (have); } - // FIXME: probably should move this into a helper function, - // we'll need it in various places: rc->details.pdf.jdata[rc->details.pdf.off] = attrs; rc->details.pdf.docs[rc->details.pdf.off].form_name @@ -279,8 +357,12 @@ detail_cb ( rc->details.pdf.docs[rc->details.pdf.off].data = rc->details.pdf.jdata[rc->details.pdf.off]; rc->details.pdf.off++; - // All attachments are under "SOMETHING.FILE", so look for ".FILE"! - // FIXME: deal with attachments, use extra slots for those! + if (! dump_attachments (rc, + attrs)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to dump some attachment!\n"); + } break; } } @@ -320,6 +402,66 @@ pdf_cb (void *cls, } +/** + * Function called with the latest AML decision on an + * account. Used to build the cover page. + * + * @param cls a `struct ResponseContext *` + * @param outcome_serial_id row ID of the decision + * @param decision_time when was the decision taken + * @param justification what was the given justification + * @param decider_pub which key signed the decision + * @param jproperties what are the new account properties + * @param jnew_rules what are the new account rules + * @param to_investigate should AML staff investigate + * after the decision + * @param is_active is this the active decision + */ +static void +build_cover_page ( + void *cls, + uint64_t outcome_serial_id, + struct GNUNET_TIME_Timestamp decision_time, + const char *justification, + const struct TALER_AmlOfficerPublicKeyP *decider_pub, + const json_t *jproperties, + const json_t *jnew_rules, + bool to_investigate, + bool is_active) +{ + struct ResponseContext *rc = cls; + json_t *cover; + + cover = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("properties", + (json_t *) jproperties)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_object_incref ("rules", + (json_t *) jnew_rules)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("last_justification", + justification)), + GNUNET_JSON_pack_bool ("is_active", + is_active), + GNUNET_JSON_pack_bool ("to_investigate", + to_investigate)); + /* first page, so we really cannot have hit the maximum yet */ + GNUNET_assert (rc->details.pdf.off < MAX_RECORDS); + { + GNUNET_break (0); + return; + } + rc->details.pdf.jdata[rc->details.pdf.off] + = cover; + rc->details.pdf.docs[rc->details.pdf.off].form_name + = "_cover_.typst"; + rc->details.pdf.docs[rc->details.pdf.off].data + = rc->details.pdf.jdata[rc->details.pdf.off]; + rc->details.pdf.off++; +} + + MHD_RESULT TEH_handler_aml_attributes_get ( struct TEH_RequestContext *rc, @@ -330,6 +472,7 @@ TEH_handler_aml_attributes_get ( int64_t limit = -20; uint64_t offset; struct TALER_NormalizedPaytoHashP h_payto; + uint64_t file_number; if (NULL == rctx) { @@ -385,6 +528,34 @@ TEH_handler_aml_attributes_get ( &offset); { + enum GNUNET_DB_QueryStatus qs; + + qs = TEH_plugin->lookup_aml_file_number (TEH_plugin->cls, + &h_payto, + &file_number); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + return TALER_MHD_reply_with_error ( + rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "lookup_aml_file_number"); + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + /* Account unknown, return 404 */ + return TALER_MHD_reply_with_error ( + rc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_EXCHANGE_GENERIC_TARGET_ACCOUNT_UNKNOWN, + args[0]); + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; + } + } + + { const char *mime; mime = MHD_lookup_connection_value (rc->connection, @@ -402,6 +573,8 @@ TEH_handler_aml_attributes_get ( else if (0 == strcmp (mime, "application/pdf")) { + enum GNUNET_DB_QueryStatus qs; + rctx->format = RCF_PDF; rctx->details.pdf.global_attrs = json_object (); GNUNET_assert (NULL != rctx->details.pdf.global_attrs); @@ -409,10 +582,43 @@ TEH_handler_aml_attributes_get ( GNUNET_assert (0 == json_object_update (rctx->details.pdf.global_attrs, TEH_global_pdf_form_data)); - // FIXME: need to obtain globals about the - // account here first, like the FILE_NUMBER. - // Might just as well select current rules/etc - // at the same time and generate a cover page! + GNUNET_assert (0 == + json_object_set_new (rctx->details.pdf.global_attrs, + "FILE_NUMBER", + json_integer (file_number))); + /* Kookup latest AML decision & account rules & properties + to build the cover page */ + qs = TEH_plugin->lookup_aml_history (TEH_plugin->cls, + &h_payto, + UINT64_MAX, /* offset */ + -1, /* latest decision only */ + &build_cover_page, + rctx); + switch (qs) + { + case GNUNET_DB_STATUS_HARD_ERROR: + case GNUNET_DB_STATUS_SOFT_ERROR: + GNUNET_break (0); + return TALER_MHD_reply_with_error ( + rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "select_aml_attributes"); + case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + /* no decision was ever taken, build empty cover page */ + build_cover_page (rctx, + 0, + GNUNET_TIME_UNIT_ZERO_TS, + NULL, + NULL, + NULL, + NULL, + false, + false); + break; + case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: + break; + } } else { diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am @@ -207,6 +207,7 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \ pg_kyc_provider_account_lookup.h pg_kyc_provider_account_lookup.c \ pg_kycauth_in_insert.h pg_kycauth_in_insert.c \ pg_lookup_active_legitimization.h pg_lookup_active_legitimization.c \ + pg_lookup_aml_file_number.h pg_lookup_aml_file_number.c \ pg_lookup_aml_history.h pg_lookup_aml_history.c \ pg_lookup_aml_officer.h pg_lookup_aml_officer.c \ pg_lookup_auditor_status.h pg_lookup_auditor_status.c \ diff --git a/src/exchangedb/exchangedb_history.c b/src/exchangedb/exchangedb_history.c @@ -28,6 +28,7 @@ * Function called to expand AML history for the account. * * @param cls a `json_t *` array to build + * @param outcome_serial_id row ID of the decision * @param decision_time when was the decision taken * @param justification what was the given justification * @param decider_pub which key signed the decision @@ -40,6 +41,7 @@ static void add_aml_history_entry ( void *cls, + uint64_t outcome_serial_id, struct GNUNET_TIME_Timestamp decision_time, const char *justification, const struct TALER_AmlOfficerPublicKeyP *decider_pub, @@ -86,6 +88,8 @@ TALER_EXCHANGEDB_aml_history_builder (void *cls) qs = hbc->db_plugin->lookup_aml_history ( hbc->db_plugin->cls, acc, + UINT64_MAX, /* offset */ + -16 * 1024, /* limit: none for all practical purposes (for now) */ &add_aml_history_entry, aml_history); switch (qs) diff --git a/src/exchangedb/pg_lookup_aml_file_number.c b/src/exchangedb/pg_lookup_aml_file_number.c @@ -0,0 +1,58 @@ +/* + 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 exchangedb/pg_lookup_aml_file_number.c + * @brief Implementation of the lookup_aml_file_number function for Postgres + * @author Christian Grothoff + */ +#include "taler/platform.h" +#include "taler/taler_error_codes.h" +#include "taler/taler_dbevents.h" +#include "taler/taler_pq_lib.h" +#include "pg_lookup_aml_file_number.h" +#include "pg_helper.h" + + +enum GNUNET_DB_QueryStatus +TEH_PG_lookup_aml_file_number ( + void *cls, + const struct TALER_NormalizedPaytoHashP *h_payto, + uint64_t *kyc_target_row) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (h_payto), + GNUNET_PQ_query_param_end + }; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_uint64 ( + "kyc_target_serial_id", + kyc_target_row), + GNUNET_PQ_result_spec_end + }; + + PREPARE (pg, + "lookup_aml_file_number", + "SELECT " + " kyc_target_serial_id" + " FROM kyc_targets" + " WHERE h_normalized_payto=$1"); + return GNUNET_PQ_eval_prepared_singleton_select ( + pg->conn, + "lookup_aml_file_number", + params, + rs); +} diff --git a/src/exchangedb/pg_lookup_aml_file_number.h b/src/exchangedb/pg_lookup_aml_file_number.h @@ -0,0 +1,44 @@ +/* + 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 exchangedb/pg_lookup_aml_file_number.h + * @brief implementation of the lookup_aml_file_number function for Postgres + * @author Christian Grothoff + */ +#ifndef PG_LOOKUP_AML_FILE_NUMBER_H +#define PG_LOOKUP_AML_FILE_NUMBER_H + +#include "taler/taler_util.h" +#include "taler/taler_json_lib.h" +#include "taler/taler_exchangedb_plugin.h" + + +/** + * Lookup AML file number by the payto address. + * + * @param cls closure + * @param h_payto account for which to find the row ID + * @param[out] kyc_target_row set to row in the kyc_targets table for @a h_payto + * @return database transaction status + */ +enum GNUNET_DB_QueryStatus +TEH_PG_lookup_aml_file_number ( + void *cls, + const struct TALER_NormalizedPaytoHashP *h_payto, + uint64_t *kyc_target_row); + + +#endif diff --git a/src/exchangedb/pg_lookup_aml_history.c b/src/exchangedb/pg_lookup_aml_history.c @@ -72,6 +72,7 @@ handle_aml_entry (void *cls, for (unsigned int i = 0; i < num_results; i++) { + uint64_t outcome_serial_id; struct GNUNET_TIME_Timestamp decision_time; char *justification; struct TALER_AmlOfficerPublicKeyP decider_pub; @@ -80,6 +81,8 @@ handle_aml_entry (void *cls, bool to_investigate; bool is_active; struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_uint64 ("outcome_serial_id", + &outcome_serial_id), GNUNET_PQ_result_spec_timestamp ("decision_time", &decision_time), GNUNET_PQ_result_spec_string ("justification", @@ -109,6 +112,7 @@ handle_aml_entry (void *cls, return; } ahc->cb (ahc->cb_cls, + outcome_serial_id, decision_time, justification, &decider_pub, @@ -125,6 +129,8 @@ enum GNUNET_DB_QueryStatus TEH_PG_lookup_aml_history ( void *cls, const struct TALER_NormalizedPaytoHashP *h_payto, + uint64_t offset, + int64_t limit, TALER_EXCHANGEDB_AmlHistoryCallback cb, void *cb_cls) { @@ -134,16 +140,20 @@ TEH_PG_lookup_aml_history ( .cb = cb, .cb_cls = cb_cls }; + uint64_t ulimit = (limit < 0) ? (-limit) : limit; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_auto_from_type (h_payto), + GNUNET_PQ_query_param_uint64 (&offset), + GNUNET_PQ_query_param_uint64 (&ulimit), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; PREPARE (pg, - "lookup_aml_history", + "lookup_aml_history_desc", "SELECT" " lo.decision_time" + ",lo.outcome_serial_id" ",ah.justification" ",ah.decider_pub" ",lo.jproperties::TEXT" @@ -154,10 +164,32 @@ TEH_PG_lookup_aml_history ( " JOIN legitimization_outcomes lo" " USING (outcome_serial_id)" " WHERE ah.h_payto=$1" - " ORDER BY decision_time DESC, outcome_serial_id DESC;"); + " AND lo.outcome_serial_id < $2" + " ORDER BY outcome_serial_id DESC" + " LIMIT $3;"); + PREPARE (pg, + "lookup_aml_history_desc", + "SELECT" + " lo.decision_time" + ",lo.outcome_serial_id" + ",ah.justification" + ",ah.decider_pub" + ",lo.jproperties::TEXT" + ",lo.jnew_rules::TEXT" + ",lo.to_investigate" + ",lo.is_active" + " FROM aml_history ah" + " JOIN legitimization_outcomes lo" + " USING (outcome_serial_id)" + " WHERE ah.h_payto=$1" + " AND lo.outcome_serial_id > $2" + " ORDER BY outcome_serial_id ASC" + " LIMIT $3;"); qs = GNUNET_PQ_eval_prepared_multi_select ( pg->conn, - "lookup_aml_history", + (limit < 0) + ? "lookup_aml_history_desc" + : "lookup_aml_history_asc", params, &handle_aml_entry, &ahc); diff --git a/src/exchangedb/pg_lookup_aml_history.h b/src/exchangedb/pg_lookup_aml_history.h @@ -32,6 +32,8 @@ * * @param cls closure * @param h_payto hash of account to lookup history for + * @param offset row ID to start returning results from + * @param limit how many results to return, negative for descending order * @param cb function to call on results * @param cb_cls closure for @a cb * @return database transaction status @@ -40,6 +42,8 @@ enum GNUNET_DB_QueryStatus TEH_PG_lookup_aml_history ( void *cls, const struct TALER_NormalizedPaytoHashP *h_payto, + uint64_t offset, + int64_t limit, TALER_EXCHANGEDB_AmlHistoryCallback cb, void *cb_cls); diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c @@ -136,6 +136,7 @@ #include "pg_kyc_provider_account_lookup.h" #include "pg_kycauth_in_insert.h" #include "pg_lookup_active_legitimization.h" +#include "pg_lookup_aml_file_number.h" #include "pg_lookup_aml_history.h" #include "pg_lookup_aml_officer.h" #include "pg_lookup_auditor_status.h" @@ -629,6 +630,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) = &TEH_PG_gc; plugin->select_coin_deposits_above_serial_id = &TEH_PG_select_coin_deposits_above_serial_id; + plugin->lookup_aml_file_number + = &TEH_PG_lookup_aml_file_number; plugin->lookup_aml_history = &TEH_PG_lookup_aml_history; plugin->lookup_kyc_history diff --git a/src/include/taler/taler_error_codes.h b/src/include/taler/taler_error_codes.h @@ -808,6 +808,14 @@ enum TALER_ErrorCode /** + * The exchange is not aware of the given target account. The specified account is not a customer of this service. + * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). + * (A value of 0 indicates that the error is generated client-side). + */ + TALER_EC_EXCHANGE_GENERIC_TARGET_ACCOUNT_UNKNOWN = 1049, + + + /** * The exchange did not find information about the specified transaction in the database. * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). * (A value of 0 indicates that the error is generated client-side). diff --git a/src/include/taler/taler_exchangedb_plugin.h b/src/include/taler/taler_exchangedb_plugin.h @@ -3321,6 +3321,7 @@ typedef void * account. * * @param cls closure + * @param outcome_serial_id row ID of the decision * @param decision_time when was the decision taken * @param justification what was the given justification * @param decider_pub which key signed the decision @@ -3333,6 +3334,7 @@ typedef void typedef void (*TALER_EXCHANGEDB_AmlHistoryCallback) ( void *cls, + uint64_t outcome_serial_id, struct GNUNET_TIME_Timestamp decision_time, const char *justification, const struct TALER_AmlOfficerPublicKeyP *decider_pub, @@ -7852,6 +7854,8 @@ struct TALER_EXCHANGEDB_Plugin * * @param cls closure * @param h_payto hash of account to lookup history for + * @param offset row ID to start returning results from + * @param limit how many results to return, negative for descending order * @param cb function to call on results * @param cb_cls closure for @a cb * @return database transaction status @@ -7860,6 +7864,8 @@ struct TALER_EXCHANGEDB_Plugin (*lookup_aml_history)( void *cls, const struct TALER_NormalizedPaytoHashP *h_payto, + uint64_t offset, + int64_t limit, TALER_EXCHANGEDB_AmlHistoryCallback cb, void *cb_cls); @@ -8185,6 +8191,20 @@ struct TALER_EXCHANGEDB_Plugin void *cls, const char *schema); + /** + * Lookup AML file number by the payto address. + * + * @param cls closure + * @param h_payto account for which to find the row ID + * @param[out] kyw_target_row set to row in the kyc_targets table for @a h_payto + * @return database transaction status + */ + enum GNUNET_DB_QueryStatus + (*lookup_aml_file_number) ( + void *cls, + const struct TALER_NormalizedPaytoHashP *h_payto, + uint64_t *kyc_target_row); + }; #endif /* _TALER_EXCHANGE_DB_H */ diff --git a/src/util/taler_error_codes.c b/src/util/taler_error_codes.c @@ -811,6 +811,14 @@ static const struct ErrorCodeAndHint code_hint_pairs[] = { }, { + /* 1049 */ + .ec = TALER_EC_EXCHANGE_GENERIC_TARGET_ACCOUNT_UNKNOWN, + .hint = gettext_noop ( + "The exchange is not aware of the given target account. The specified account is not a customer of this service."), + .http_code = MHD_HTTP_NOT_FOUND + }, + + { /* 1100 */ .ec = TALER_EC_EXCHANGE_DEPOSITS_GET_NOT_FOUND, .hint = gettext_noop ( @@ -5491,7 +5499,7 @@ static const struct ErrorCodeAndHint code_hint_pairs[] = { /** * The length of @e code_hint_pairs. */ -static const unsigned int code_hint_pairs_length = 687; +static const unsigned int code_hint_pairs_length = 688; const char *