merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit fd6e459cc7fcad3703c993e812f25a09e1ca9d36
parent aa29da994abd4b4dcb0648e67484589db7351ae9
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun,  8 Sep 2024 23:39:50 +0200

towards new /kyc API: test_kyc_api still fails, but getting close

Diffstat:
Msrc/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c | 982++++++++++++++++++++++++++++++-------------------------------------------------
Msrc/backend/taler-merchant-kyccheck.c | 32+++++++++++++++++++++++++++++---
Msrc/backenddb/pg_account_kyc_get_status.c | 91++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Msrc/backenddb/pg_account_kyc_set_status.c | 10+++++++++-
Msrc/backenddb/pg_account_kyc_set_status.sql | 5+++++
Msrc/backenddb/pg_get_kyc_status.c | 28++++++++++++----------------
Msrc/backenddb/test_merchantdb.c | 38++++++++++++++++++++++++--------------
Msrc/include/taler_merchant_service.h | 68+++++++++++++++++++++++++-------------------------------------------
Msrc/include/taler_merchant_testing_lib.h | 2++
Msrc/include/taler_merchantdb_plugin.h | 13+++++++++++--
Msrc/lib/merchant_api_get_kyc.c | 237+++++++++++++++++++++++++++++++++++++------------------------------------------
Msrc/testing/test_kyc_api.c | 20++++++++++----------
Msrc/testing/test_merchant_api.c | 1+
Msrc/testing/testing_api_cmd_kyc_get.c | 58++++++++++++++++++++++++++++++++++++++++------------------
Msrc/testing/testing_api_cmd_post_account.c | 4+++-
15 files changed, 713 insertions(+), 876 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c b/src/backend/taler-merchant-httpd_private-get-instances-ID-kyc.c @@ -27,6 +27,7 @@ #include "taler-merchant-httpd_helper.h" #include "taler-merchant-httpd_exchanges.h" #include <taler/taler_json_lib.h> +#include <taler/taler_dbevents.h> #include <regex.h> /** @@ -77,7 +78,8 @@ struct ExchangeKycRequest /** * JSON array of payto-URIs with KYC auth wire transfer - * instructions. + * instructions. Provided if @e auth_ok is false and + * @e kyc_auth_conflict is false. */ json_t *pkaa; @@ -92,14 +94,10 @@ struct ExchangeKycRequest struct KycContext *kc; /** - * Hash of the wire account (with salt) we are checking. - */ - struct TALER_MerchantWireHashP h_wire; - - /** - * Handle for the actual HTTP request to the exchange. + * JSON array of AccountLimits that apply, NULL if + * unknown (and likely defaults apply). */ - struct TALER_EXCHANGE_KycCheckHandle *kyc; + json_t *jlimits; /** * Our account's payto URI. @@ -112,10 +110,62 @@ struct ExchangeKycRequest char *exchange_url; /** + * Hash of the wire account (with salt) we are checking. + */ + struct TALER_MerchantWireHashP h_wire; + + /** + * Current access token for the KYC SPA. Only set + * if @e auth_ok is true. + */ + struct TALER_AccountAccessTokenP access_token; + + /** * Timestamp when we last got a reply from the exchange. */ struct GNUNET_TIME_Timestamp last_check; + /** + * Last HTTP status code obtained via /kyc-check from + * the exchange. + */ + unsigned int last_http_status; + + /** + * Last Taler error code returned from /kyc-check. + */ + enum TALER_ErrorCode last_ec; + + /** + * True if this account + * cannot work at this exchange because KYC auth is + * impossible. + */ + bool kyc_auth_conflict; + + /** + * We could not get /keys from the exchange. + */ + bool no_keys; + + /** + * True if @e access_token is available. + */ + bool auth_ok; + + /** + * True if we believe no KYC is currently required + * for this account at this exchange. + */ + bool kyc_ok; + + /** + * True if the exchange exposed to us that the account + * is currently under AML review. + */ + bool in_aml_review; + + }; @@ -150,11 +200,6 @@ struct KycContext struct TMH_HandlerContext *hc; /** - * Task to trigger on request timeout, or NULL. - */ - struct GNUNET_SCHEDULER_Task *timeout_task; - - /** * Response to return, NULL if we don't have one yet. */ struct MHD_Response *response; @@ -163,19 +208,7 @@ struct KycContext * JSON array where we are building up the array with * pending KYC operations. */ - json_t *pending_kycs; - - /** - * JSON array where we are building up the array with - * pending KYC operations. - */ - json_t *voluntary_kycs; - - /** - * JSON array where we are building up the array with - * troubled KYC operations. - */ - json_t *timeout_kycs; + json_t *kycs_data; /** * Head of DLL of requests we are making to an @@ -196,6 +229,12 @@ struct KycContext const char *exchange_url; /** + * Notification handler from database on changes + * to the KYC status. + */ + struct GNUNET_DB_EventHandler *eh; + + /** * Set to the h_wire of the merchant account if * @a have_h_wire is true, used to filter by account. */ @@ -222,6 +261,11 @@ struct KycContext enum GNUNET_GenericReturnValue suspended; /** + * What state are we long-polling for? + */ + enum TALER_EXCHANGE_KycLongPollTarget lpt; + + /** * True if @e h_wire was given. */ bool have_h_wire; @@ -230,7 +274,7 @@ struct KycContext * We're still waiting on the exchange to determine * the KYC status of our deposit(s). */ - bool kyc_serial_pending; + bool return_immediately; }; @@ -253,11 +297,6 @@ TMH_force_kyc_resume () NULL != kc; kc = kc->next) { - if (NULL != kc->timeout_task) - { - GNUNET_SCHEDULER_cancel (kc->timeout_task); - kc->timeout_task = NULL; - } if (GNUNET_YES == kc->suspended) { kc->suspended = GNUNET_SYSERR; @@ -283,27 +322,23 @@ kyc_context_cleanup (void *cls) GNUNET_CONTAINER_DLL_remove (kc->exchange_pending_head, kc->exchange_pending_tail, ekr); - if (NULL != ekr->kyc) - { - TALER_EXCHANGE_kyc_check_cancel (ekr->kyc); - ekr->kyc = NULL; - } if (NULL != ekr->fo) { TMH_EXCHANGES_keys4exchange_cancel (ekr->fo); ekr->fo = NULL; } json_decref (ekr->pkaa); + json_decref (ekr->jlimits); if (NULL != ekr->keys) TALER_EXCHANGE_keys_decref (ekr->keys); GNUNET_free (ekr->exchange_url); GNUNET_free (ekr->payto_uri); GNUNET_free (ekr); } - if (NULL != kc->timeout_task) + if (NULL != kc->eh) { - GNUNET_SCHEDULER_cancel (kc->timeout_task); - kc->timeout_task = NULL; + TMH_db->event_listen_cancel (kc->eh); + kc->eh = NULL; } if (NULL != kc->response) { @@ -313,73 +348,42 @@ kyc_context_cleanup (void *cls) GNUNET_CONTAINER_DLL_remove (kc_head, kc_tail, kc); - json_decref (kc->voluntary_kycs); - json_decref (kc->pending_kycs); - json_decref (kc->timeout_kycs); + json_decref (kc->kycs_data); GNUNET_free (kc); } /** - * Resume the given KYC context and send the given response. Stores the + * Resume the given KYC context and send the final response. Stores the * response in the @a kc and signals MHD to resume the connection. Also * ensures MHD runs immediately. * * @param kc KYC context - * @param response_code response code to use - * @param response response data to send back */ static void -resume_kyc_with_response (struct KycContext *kc, - unsigned int response_code, - struct MHD_Response *response) +resume_kyc_with_response (struct KycContext *kc) { char dat[128]; - kc->response_code = response_code; - kc->response = response; - switch (response_code) - { - case MHD_HTTP_OK: - /* KYC failed, cache briefly */ - TALER_MHD_get_date_string (GNUNET_TIME_relative_to_absolute ( - EXPIRATION_KYC_FAILURE), - dat); - GNUNET_break (MHD_YES == - MHD_add_response_header (response, - MHD_HTTP_HEADER_EXPIRES, - dat)); - GNUNET_break (MHD_YES == - MHD_add_response_header (response, - MHD_HTTP_HEADER_CACHE_CONTROL, - "max-age=300")); - break; - case MHD_HTTP_NO_CONTENT: - /* KYC passed, cache for a long time! */ - TALER_MHD_get_date_string (GNUNET_TIME_relative_to_absolute ( - EXPIRATION_KYC_SUCCESS), - dat); - GNUNET_break (MHD_YES == - MHD_add_response_header (response, - MHD_HTTP_HEADER_EXPIRES, - dat)); - GNUNET_break (MHD_YES == - MHD_add_response_header (response, - MHD_HTTP_HEADER_CACHE_CONTROL, - "max-age=3600")); - break; - case MHD_HTTP_BAD_GATEWAY: - case MHD_HTTP_GATEWAY_TIMEOUT: - break; /* no caching */ - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + kc->response_code = MHD_HTTP_OK; + kc->response = TALER_MHD_MAKE_JSON_PACK ( + GNUNET_JSON_pack_array_incref ("kyc_data", + kc->kycs_data)); + /* KYC failed, cache briefly */ + TALER_MHD_get_date_string (GNUNET_TIME_relative_to_absolute ( + EXPIRATION_KYC_FAILURE), + dat); + GNUNET_break (MHD_YES == + MHD_add_response_header (kc->response, + MHD_HTTP_HEADER_EXPIRES, + dat)); + GNUNET_break (MHD_YES == + MHD_add_response_header (kc->response, + MHD_HTTP_HEADER_CACHE_CONTROL, + "max-age=300")); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Resuming /kyc handling as exchange interaction is done (%u)\n", - response_code); - if (NULL != kc->timeout_task) - { - GNUNET_SCHEDULER_cancel (kc->timeout_task); - kc->timeout_task = NULL; - } + MHD_HTTP_OK); GNUNET_assert (GNUNET_YES == kc->suspended); kc->suspended = GNUNET_NO; MHD_resume_connection (kc->connection); @@ -388,58 +392,26 @@ resume_kyc_with_response (struct KycContext *kc, /** - * Handle a timeout for the processing of the kyc request. + * Handle a DB event about an update relevant + * for the processing of the kyc request. * * @param cls our `struct KycContext` + * @param extra additional event data provided + * @param extra_size number of bytes in @a extra */ static void -handle_kyc_timeout (void *cls) +kyc_change_cb (void *cls, + const void *extra, + size_t extra_size) { struct KycContext *kc = cls; - struct ExchangeKycRequest *ekr; - kc->timeout_task = NULL; - while (NULL != (ekr = kc->exchange_pending_head)) - { - GNUNET_CONTAINER_DLL_remove (kc->exchange_pending_head, - kc->exchange_pending_tail, - ekr); - if (NULL != ekr->kyc) - { - TALER_EXCHANGE_kyc_check_cancel (ekr->kyc); - ekr->kyc = NULL; - } - if (NULL != ekr->fo) - { - TMH_EXCHANGES_keys4exchange_cancel (ekr->fo); - ekr->fo = NULL; - } - GNUNET_assert ( - 0 == - json_array_append_new ( - kc->timeout_kycs, - GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("exchange_url", - ekr->exchange_url), - GNUNET_JSON_pack_uint64 ("exchange_code", - TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT), - GNUNET_JSON_pack_uint64 ("exchange_http_status", - 0)))); - GNUNET_free (ekr->exchange_url); - GNUNET_free (ekr->payto_uri); - GNUNET_free (ekr); - } GNUNET_assert (GNUNET_YES == kc->suspended); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Resuming KYC with gateway timeout\n"); - resume_kyc_with_response ( - kc, - MHD_HTTP_GATEWAY_TIMEOUT, - TALER_MHD_MAKE_JSON_PACK ( - GNUNET_JSON_pack_array_incref ("pending_kycs", - kc->pending_kycs), - GNUNET_JSON_pack_array_incref ("timeout_kycs", - kc->timeout_kycs))); + kc->suspended = GNUNET_NO; + MHD_resume_connection (kc->connection); + TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ } @@ -472,107 +444,6 @@ pack_limit (const struct TALER_EXCHANGE_AccountLimit *limit, /** - * We are done with the KYC request @a ekr. Remove it from the work list and - * check if we are done overall. - * - * @param[in] ekr key request that is done (and will be freed) - */ -static void -ekr_finished (struct ExchangeKycRequest *ekr) -{ - struct KycContext *kc = ekr->kc; - - GNUNET_CONTAINER_DLL_remove (kc->exchange_pending_head, - kc->exchange_pending_tail, - ekr); - json_decref (ekr->pkaa); - if (NULL != ekr->keys) - TALER_EXCHANGE_keys_decref (ekr->keys); - GNUNET_free (ekr->exchange_url); - GNUNET_free (ekr->payto_uri); - GNUNET_free (ekr); - if (NULL != kc->exchange_pending_head) - return; /* wait for more */ - /* All exchange requests done, create final - big response from cumulated replies */ - if ( (0 == json_array_size (kc->pending_kycs)) && - (0 == json_array_size (kc->timeout_kycs)) ) - { - /* special case: all KYC operations did succeed - after we asked at the exchanges => 204 */ - struct MHD_Response *response; - - response = MHD_create_response_from_buffer_static (0, - ""); - resume_kyc_with_response (kc, - MHD_HTTP_NO_CONTENT, - response); - return; - } - resume_kyc_with_response ( - kc, - kc->response_code, /* MHD_HTTP_OK or MHD_HTTP_ACCEPTED */ - TALER_MHD_MAKE_JSON_PACK ( - GNUNET_JSON_pack_array_incref ("pending_kycs", - kc->pending_kycs), - GNUNET_JSON_pack_array_incref ("timeout_kycs", - kc->timeout_kycs))); -} - - -/** - * Store KYC response from the exchange in the - * local database. - * - * @param ekr request context - * @param ks HTTP response details - * @param account_kyc_status account KYC status details - * @return true if the operation was successful - */ -static bool -store_kyc_status ( - const struct ExchangeKycRequest *ekr, - const struct TALER_EXCHANGE_KycStatus *ks, - const struct TALER_EXCHANGE_AccountKycStatus *account_kyc_status) -{ - json_t *jlimits; - enum GNUNET_DB_QueryStatus qs; - - jlimits = json_array (); - GNUNET_assert (NULL != jlimits); - for (unsigned int i = 0; i<account_kyc_status->limits_length; i++) - { - const struct TALER_EXCHANGE_AccountLimit *limit - = &account_kyc_status->limits[i]; - - pack_limit (limit, - jlimits); - } - - qs = TMH_db->account_kyc_set_status ( - TMH_db->cls, - ekr->kc->mi->settings.id, - &ekr->h_wire, - ekr->exchange_url, - GNUNET_TIME_timestamp_get (), - ks->hr.http_status, - ks->hr.ec, - &account_kyc_status->access_token, - jlimits, - account_kyc_status->aml_review, - false); - json_decref (jlimits); - if (qs < 0) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to store KYC status in database!\n"); - return false; - } - return true; -} - - -/** * Return JSON array with AccountLimit objects giving * the current limits for this exchange. * @@ -581,297 +452,141 @@ store_kyc_status ( */ static json_t * get_exchange_limits ( - struct ExchangeKycRequest *ekr, - const struct TALER_EXCHANGE_AccountKycStatus *account_kyc_status) + struct ExchangeKycRequest *ekr) { + const struct TALER_EXCHANGE_Keys *keys = ekr->keys; json_t *limits; + if (NULL != ekr->jlimits) + return json_incref (ekr->jlimits); + if (NULL == keys) + return NULL; limits = json_array (); GNUNET_assert (NULL != limits); - if (NULL == account_kyc_status) + for (unsigned int i = 0; i<keys->hard_limits_length; i++) { - const struct TALER_EXCHANGE_Keys *keys = ekr->keys; - - GNUNET_assert (NULL != keys); - for (unsigned int i = 0; i<keys->hard_limits_length; i++) - { - const struct TALER_EXCHANGE_AccountLimit *limit - = &keys->hard_limits[i]; - - pack_limit (limit, - limits); - } - for (unsigned int i = 0; i<keys->zero_limits_length; i++) - { - const struct TALER_EXCHANGE_ZeroLimitedOperation *zlimit - = &keys->zero_limits[i]; - json_t *jl; - struct TALER_Amount zero; + const struct TALER_EXCHANGE_AccountLimit *limit + = &keys->hard_limits[i]; - GNUNET_assert (GNUNET_OK == - TALER_amount_set_zero (keys->currency, - &zero)); - jl = GNUNET_JSON_PACK ( - TALER_JSON_pack_kycte ("operation_type", - zlimit->operation_type), - GNUNET_JSON_pack_time_rel ("timeframe", - GNUNET_TIME_UNIT_ZERO), - TALER_JSON_pack_amount ("threshold", - &zero), - GNUNET_JSON_pack_bool ("soft_limit", - true) - ); - GNUNET_assert (0 == - json_array_append_new (limits, - jl)); - } + pack_limit (limit, + limits); } - else + for (unsigned int i = 0; i<keys->zero_limits_length; i++) { - for (unsigned int i = 0; i<account_kyc_status->limits_length; i++) - { - const struct TALER_EXCHANGE_AccountLimit *limit - = &account_kyc_status->limits[i]; - - pack_limit (limit, - limits); - } + const struct TALER_EXCHANGE_ZeroLimitedOperation *zlimit + = &keys->zero_limits[i]; + json_t *jl; + struct TALER_Amount zero; + + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (keys->currency, + &zero)); + jl = GNUNET_JSON_PACK ( + TALER_JSON_pack_kycte ("operation_type", + zlimit->operation_type), + GNUNET_JSON_pack_time_rel ("timeframe", + GNUNET_TIME_UNIT_ZERO), + TALER_JSON_pack_amount ("threshold", + &zero), + GNUNET_JSON_pack_bool ("soft_limit", + true) + ); + GNUNET_assert (0 == + json_array_append_new (limits, + jl)); } return limits; } /** - * Update exchange KYC account status, storing it - * in the database and returning it in the response. + * Take data from @a ekr to expand our response. * - * @param[in,out] ekr our request context - * @param ks overall HTTP response - * @param account_kyc_status specific KYC status + * @param ekr exchange we are done inspecting */ static void -update_account_status ( - struct ExchangeKycRequest *ekr, - const struct TALER_EXCHANGE_KycStatus *ks, - const struct TALER_EXCHANGE_AccountKycStatus *account_kyc_status) +ekr_expand_response (struct ExchangeKycRequest *ekr) { - char *kyc_url; - - GNUNET_break (store_kyc_status (ekr, - ks, - account_kyc_status)); - { - char *ats; - - ats = GNUNET_STRINGS_data_to_string_alloc ( - &account_kyc_status->access_token, - sizeof (account_kyc_status->access_token)); - GNUNET_asprintf (&kyc_url, - "%s/kyc-spa/%s", - ekr->exchange_url, - ats); - GNUNET_free (ats); - } - GNUNET_assert ( 0 == json_array_append_new ( - ekr->kc->pending_kycs, + ekr->kc->kycs_data, GNUNET_JSON_PACK ( - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_array_steal ( - "limits", - get_exchange_limits (ekr, - account_kyc_status))), - (TALER_EC_NONE == ks->hr.ec) - ? GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ( - "dummy", - NULL)) - : GNUNET_JSON_pack_uint64 ("exchange_code", - ks->hr.ec), - GNUNET_JSON_pack_uint64 ("exchange_http_status", - ks->hr.http_status), - GNUNET_JSON_pack_data_auto ( - "access_token", - &account_kyc_status->access_token), GNUNET_JSON_pack_string ( - "kyc_url", - kyc_url), + "payto_uri", + ekr->payto_uri), GNUNET_JSON_pack_string ( "exchange_url", ekr->exchange_url), - GNUNET_JSON_pack_string ( - "payto_uri", - ekr->payto_uri)))); - GNUNET_free (kyc_url); -} - - -/** - * Return exchange KYC account status when KYC auth - * is required for authorization to the KYC state. - * - * @param[in,out] ekr our request context - * @param ks overall HTTP response - */ -static void -return_auth_required ( - struct ExchangeKycRequest *ekr, - const struct TALER_EXCHANGE_KycStatus *ks) -{ - struct KycContext *kc = ekr->kc; - enum GNUNET_DB_QueryStatus qs; - - qs = TMH_db->account_kyc_set_status ( - TMH_db->cls, - kc->mi->settings.id, - &ekr->h_wire, - ekr->exchange_url, - GNUNET_TIME_timestamp_get (), - ks->hr.http_status, - ks->hr.ec, - NULL, - NULL, - false, - true); - if (qs < 0) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to store KYC status in database!\n"); - } - - GNUNET_assert ( - 0 == - json_array_append_new ( - kc->pending_kycs, - GNUNET_JSON_PACK ( - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_array_steal ( - "limits", - get_exchange_limits (ekr, - NULL))), - (TALER_EC_NONE == ks->hr.ec) + GNUNET_JSON_pack_bool ("no_keys", + ekr->no_keys), + GNUNET_JSON_pack_bool ("auth_conflict", + ekr->kyc_auth_conflict), + GNUNET_JSON_pack_uint64 ("exchange_http_status", + ekr->last_http_status), + (TALER_EC_NONE == ekr->last_ec) ? GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ( "dummy", NULL)) : GNUNET_JSON_pack_uint64 ("exchange_code", - ks->hr.ec), - GNUNET_JSON_pack_uint64 ("exchange_http_status", - ks->hr.http_status), - GNUNET_JSON_pack_array_incref ("payto_kycauths", - ekr->pkaa), - GNUNET_JSON_pack_string ("exchange_url", - ekr->exchange_url), - GNUNET_JSON_pack_string ("payto_uri", - ekr->payto_uri)))); + ekr->last_ec), + ekr->auth_ok + ? GNUNET_JSON_pack_data_auto ( + "access_token", + &ekr->access_token) + : GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ( + "dummy", + NULL)), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_array_steal ( + "limits", + get_exchange_limits (ekr))), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_array_incref ("payto_kycauths", + ekr->pkaa)) + ))); } /** - * Function called with the result of a KYC check. + * We are done with the KYC request @a ekr. Remove it from the work list and + * check if we are done overall. * - * @param cls a `struct ExchangeKycRequest *` - * @param ks the account's KYC status details + * @param[in] ekr key request that is done (and will be freed) */ static void -exchange_check_cb ( - void *cls, - const struct TALER_EXCHANGE_KycStatus *ks) +ekr_finished (struct ExchangeKycRequest *ekr) { - struct ExchangeKycRequest *ekr = cls; struct KycContext *kc = ekr->kc; - ekr->kyc = NULL; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Checking KYC status of `%s' at `%s' is %u\n", - ekr->payto_uri, - ekr->exchange_url, - ks->hr.http_status); - switch (ks->hr.http_status) + ekr_expand_response (ekr); + GNUNET_CONTAINER_DLL_remove (kc->exchange_pending_head, + kc->exchange_pending_tail, + ekr); + json_decref (ekr->jlimits); + json_decref (ekr->pkaa); + if (NULL != ekr->keys) + TALER_EXCHANGE_keys_decref (ekr->keys); + GNUNET_free (ekr->exchange_url); + GNUNET_free (ekr->payto_uri); + GNUNET_free (ekr); + + if (NULL != kc->exchange_pending_head) + return; /* wait for more */ + + if ( (! kc->return_immediately) && + (! GNUNET_TIME_absolute_is_past (kc->timeout)) ) { - case MHD_HTTP_OK: - update_account_status (ekr, - ks, - &ks->details.ok); - break; - case MHD_HTTP_ACCEPTED: - kc->response_code = MHD_HTTP_ACCEPTED; - update_account_status (ekr, - ks, - &ks->details.accepted); - break; - case MHD_HTTP_NO_CONTENT: - { - enum GNUNET_DB_QueryStatus qs; - - qs = TMH_db->account_kyc_set_status ( - TMH_db->cls, - kc->mi->settings.id, - &ekr->h_wire, - ekr->exchange_url, - GNUNET_TIME_timestamp_get (), - MHD_HTTP_NO_CONTENT, - TALER_EC_NONE, - NULL, - NULL, - false, - true); - if (qs < 0) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to store KYC status in database!\n"); - } - } - break; - case MHD_HTTP_FORBIDDEN: /* bad signature */ - case MHD_HTTP_NOT_FOUND: /* account unknown */ - case MHD_HTTP_CONFLICT: /* no account_pub known */ - return_auth_required (ekr, - ks); - break; - default: - { - enum GNUNET_DB_QueryStatus qs; - - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Exchange responded with HTTP status %u (%d) to /kyc-check request!\n", - ks->hr.http_status, - ks->hr.ec); - kc->response_code = MHD_HTTP_BAD_GATEWAY; - GNUNET_assert ( - 0 == - json_array_append_new ( - kc->timeout_kycs, - GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("exchange_url", - ekr->exchange_url), - GNUNET_JSON_pack_uint64 ("exchange_code", - ks->hr.ec), - GNUNET_JSON_pack_uint64 ("exchange_http_status", - ks->hr.http_status)))); - qs = TMH_db->account_kyc_set_status ( - TMH_db->cls, - kc->mi->settings.id, - &ekr->h_wire, - ekr->exchange_url, - GNUNET_TIME_timestamp_get (), - ks->hr.http_status, - ks->hr.ec, - NULL, - NULL, - false, - true); - if (qs < 0) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to store KYC status in database!\n"); - } - break; - } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Remaining suspended: long poll target %d not reached\n", + kc->lpt); + return; } - ekr_finished (ekr); + /* All exchange requests done, create final + big response from cumulated replies */ + resume_kyc_with_response (kc); } @@ -882,14 +597,13 @@ exchange_check_cb ( * in @a ekr. * * @param[in,out] request we are processing - * @param keys exchange keys */ static void determine_eligible_accounts ( - struct ExchangeKycRequest *ekr, - const struct TALER_EXCHANGE_Keys *keys) + struct ExchangeKycRequest *ekr) { struct KycContext *kc = ekr->kc; + const struct TALER_EXCHANGE_Keys *keys = ekr->keys; struct TALER_Amount kyc_amount; char *merchant_pub_str; @@ -1022,58 +736,35 @@ kyc_with_exchange (void *cls, struct TMH_Exchange *exchange) { struct ExchangeKycRequest *ekr = cls; - struct KycContext *kc = ekr->kc; - struct TALER_PaytoHashP h_payto; - union TALER_AccountPrivateKeyP ap; (void) exchange; ekr->fo = NULL; if (NULL == keys) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Failed to download `%s/keys`\n", + "Failed to download `%skeys`\n", ekr->exchange_url); - kc->response_code = MHD_HTTP_BAD_GATEWAY; - GNUNET_assert ( - 0 == - json_array_append_new ( - kc->timeout_kycs, - GNUNET_JSON_PACK ( - TALER_JSON_pack_ec ( - TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE)))); + ekr->no_keys = true; ekr_finished (ekr); return; } - determine_eligible_accounts (ekr, - keys); - if (0 == json_array_size (ekr->pkaa)) + ekr->keys = TALER_EXCHANGE_keys_incref (keys); + if (! ekr->auth_ok) { - /* No KYC auth wire transfers are possible to this exchange from - our merchant bank account, so we cannot use this account with - this exchange if it has any KYC requirements! */ - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "KYC auth to `%s' impossible for merchant account `%s'\n", - ekr->exchange_url, - ekr->payto_uri); - ekr_finished (ekr); - return; + determine_eligible_accounts (ekr); + if (0 == json_array_size (ekr->pkaa)) + { + /* No KYC auth wire transfers are possible to this exchange from + our merchant bank account, so we cannot use this account with + this exchange if it has any KYC requirements! */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "KYC auth to `%s' impossible for merchant account `%s'\n", + ekr->exchange_url, + ekr->payto_uri); + ekr->kyc_auth_conflict = true; + } } - ekr->keys = TALER_EXCHANGE_keys_incref (keys); - TALER_payto_hash (ekr->payto_uri, - &h_payto); - ap.merchant_priv = kc->mi->merchant_priv; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Checking KYC status of `%s' at `%s'\n", - ekr->payto_uri, - ekr->exchange_url); - ekr->kyc = TALER_EXCHANGE_kyc_check ( - TMH_curl_ctx, - ekr->exchange_url, - &h_payto, - &ap, - GNUNET_TIME_absolute_get_remaining (kc->timeout), - &exchange_check_cb, - ekr); + ekr_finished (ekr); } @@ -1083,46 +774,84 @@ kyc_with_exchange (void *cls, * * @param cls our `struct KycContext *` * @param h_wire hash of the wire account - * @param exchange_kyc_serial serial number for the KYC process at the exchange, 0 if unknown * @param payto_uri payto:// URI of the merchant's bank account * @param exchange_url base URL of the exchange for which this is a status * @param last_check when did we last get an update on our KYC status from the exchange * @param kyc_ok true if we satisfied the KYC requirements + * @param access_token access token for the KYC SPA, NULL if we cannot access it yet (need KYC auth wire transfer) + * @param last_http_status last HTTP status from /kyc-check + * @param last_ec last Taler error code from /kyc-check + * @param in_aml_review true if the account is pending review + * @param jlimits JSON array of applicable AccountLimits, or NULL if unknown (like defaults apply) */ static void kyc_status_cb ( void *cls, const struct TALER_MerchantWireHashP *h_wire, - uint64_t exchange_kyc_serial, const char *payto_uri, const char *exchange_url, struct GNUNET_TIME_Timestamp last_check, - bool kyc_ok) + bool kyc_ok, + const struct TALER_AccountAccessTokenP *access_token, + unsigned int last_http_status, + enum TALER_ErrorCode last_ec, + bool in_aml_review, + const json_t *jlimits) { struct KycContext *kc = cls; struct ExchangeKycRequest *ekr; - if (kyc_ok && - (GNUNET_TIME_relative_cmp ( - GNUNET_TIME_absolute_get_duration (last_check.abs_time), - <, - STALE_KYC_TIMEOUT)) ) - return; /* KYC ok, ignore! */ - kc->response_code = MHD_HTTP_OK; + switch (kc->lpt) + { + case TALER_EXCHANGE_KLPT_NONE: + break; + case TALER_EXCHANGE_KLPT_KYC_AUTH_TRANSFER: + if (NULL != access_token) + kc->return_immediately = true; + break; + case TALER_EXCHANGE_KLPT_INVESTIGATION_DONE: + if (! in_aml_review) + kc->return_immediately = true; + break; + case TALER_EXCHANGE_KLPT_KYC_OK: + if (kyc_ok) + kc->return_immediately = true; + break; + } ekr = GNUNET_new (struct ExchangeKycRequest); GNUNET_CONTAINER_DLL_insert (kc->exchange_pending_head, kc->exchange_pending_tail, ekr); + ekr->last_http_status = last_http_status; + ekr->last_ec = last_ec; + if (NULL != jlimits) + ekr->jlimits = json_incref ((json_t *) jlimits); ekr->h_wire = *h_wire; ekr->exchange_url = GNUNET_strdup (exchange_url); ekr->payto_uri = GNUNET_strdup (payto_uri); ekr->last_check = last_check; + ekr->kyc_ok = kyc_ok; ekr->kc = kc; - ekr->fo = TMH_EXCHANGES_keys4exchange ( - exchange_url, - false, - &kyc_with_exchange, - ekr); + ekr->in_aml_review = in_aml_review; + ekr->auth_ok = (NULL != access_token); + if ( (! ekr->auth_ok) || + (NULL == ekr->jlimits) ) + { + /* Figure out wire transfer instructions */ + if (GNUNET_NO == kc->suspended) + { + MHD_suspend_connection (kc->connection); + kc->suspended = GNUNET_YES; + } + ekr->fo = TMH_EXCHANGES_keys4exchange ( + exchange_url, + false, + &kyc_with_exchange, + ekr); + return; + } + ekr->access_token = *access_token; + ekr_finished (ekr); } @@ -1153,32 +882,38 @@ get_instances_ID_kyc ( kc); kc->connection = connection; kc->hc = hc; - kc->pending_kycs = json_array (); - GNUNET_assert (NULL != kc->pending_kycs); - kc->timeout_kycs = json_array (); - GNUNET_assert (NULL != kc->timeout_kycs); - + kc->kycs_data = json_array (); + GNUNET_assert (NULL != kc->kycs_data); TALER_MHD_parse_request_timeout (connection, &kc->timeout); - if (! GNUNET_TIME_absolute_is_past (kc->timeout)) - kc->timeout_task - = GNUNET_SCHEDULER_add_at (kc->timeout, - &handle_kyc_timeout, - kc); - + { + uint64_t num = 0; + int val; + + TALER_MHD_parse_request_number (connection, + "lpt", + &num); + val = (int) num; + if ( (val < 0) || + (val > TALER_EXCHANGE_KLPT_MAX) ) + { + /* Protocol violation, but we can be graceful and + just ignore the long polling! */ + GNUNET_break_op (0); + val = TALER_EXCHANGE_KLPT_NONE; + } + kc->lpt = (enum TALER_EXCHANGE_KycLongPollTarget) val; + } + kc->return_immediately + = (TALER_EXCHANGE_KLPT_NONE == kc->lpt); /* process 'exchange_url' argument */ kc->exchange_url = MHD_lookup_connection_value ( connection, MHD_GET_ARGUMENT_KIND, "exchange_url"); if ( (NULL != kc->exchange_url) && - (! TALER_url_valid_charset (kc->exchange_url) || - ( (0 != strncasecmp (kc->exchange_url, - "http://", - strlen ("http://"))) && - (0 != strncasecmp (kc->exchange_url, - "https://", - strlen ("https://"))) ) ) ) + ( (! TALER_url_valid_charset (kc->exchange_url)) || + (! TALER_is_web_url (kc->exchange_url)) ) ) { GNUNET_break_op (0); return TALER_MHD_reply_with_error ( @@ -1192,68 +927,101 @@ get_instances_ID_kyc ( "h_wire", &kc->h_wire, kc->have_h_wire); - /* Check our database */ + + if ( (TALER_EXCHANGE_KLPT_NONE != kc->lpt) && + (! GNUNET_TIME_absolute_is_past (kc->timeout)) ) { - enum GNUNET_DB_QueryStatus qs; - - qs = TMH_db->account_kyc_get_status ( - TMH_db->cls, - mi->settings.id, - kc->have_h_wire - ? &kc->h_wire - : NULL, - kc->exchange_url, - &kyc_status_cb, - kc); - if (qs < 0) + if (kc->have_h_wire) { - GNUNET_break (0); - return TALER_MHD_reply_with_ec ( - connection, - TALER_EC_GENERIC_DB_FETCH_FAILED, - "account_kyc_get_status"); + struct TALER_MERCHANTDB_MerchantKycStatusChangeEventP ev = { + .header.size = htons (sizeof (ev)), + .header.type = htons ( + TALER_DBEVENT_MERCHANT_EXCHANGE_KYC_STATUS_CHANGED + ), + .h_wire = kc->h_wire + }; + + kc->eh = TMH_db->event_listen ( + TMH_db->cls, + &ev.header, + GNUNET_TIME_absolute_get_remaining (kc->timeout), + &kyc_change_cb, + kc); } - } - if (kc->kyc_serial_pending) + else + { + struct GNUNET_DB_EventHeaderP hdr = { + .size = htons (sizeof (hdr)), + .type = htons (TALER_DBEVENT_MERCHANT_KYC_STATUS_CHANGED) + }; + + kc->eh = TMH_db->event_listen ( + TMH_db->cls, + &hdr, + GNUNET_TIME_absolute_get_remaining (kc->timeout), + &kyc_change_cb, + kc); + } + } /* end register LISTEN hooks */ + } /* end 1st time initialization */ + + if (GNUNET_SYSERR == kc->suspended) + return MHD_NO; /* during shutdown, we don't generate any more replies */ + GNUNET_assert (GNUNET_NO == kc->suspended); + + if (NULL != kc->response) + return MHD_queue_response (connection, + kc->response_code, + kc->response); + + /* Check our database */ + { + enum GNUNET_DB_QueryStatus qs; + + GNUNET_break (0 == + json_array_clear (kc->kycs_data)); + qs = TMH_db->account_kyc_get_status ( + TMH_db->cls, + mi->settings.id, + kc->have_h_wire + ? &kc->h_wire + : NULL, + kc->exchange_url, + &kyc_status_cb, + kc); + if (qs < 0) { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Exchange legitimization UUID unknown, assuming KYC pending\n"); - return TALER_MHD_REPLY_JSON_PACK ( + /* Database error */ + GNUNET_break (0); + if (GNUNET_YES == kc->suspended) + { + /* must have suspended before DB error, resume! */ + MHD_resume_connection (connection); + kc->suspended = GNUNET_NO; + } + return TALER_MHD_reply_with_ec ( connection, - MHD_HTTP_SERVICE_UNAVAILABLE, - GNUNET_JSON_pack_string ("hint", - "awaiting legitimization UUID")); + TALER_EC_GENERIC_DB_FETCH_FAILED, + "account_kyc_get_status"); } - if (NULL == kc->exchange_pending_head) + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + /* no matching accounts, could not have suspended */ + GNUNET_assert (GNUNET_NO == kc->suspended); return TALER_MHD_reply_static (connection, MHD_HTTP_NO_CONTENT, NULL, NULL, 0); - MHD_suspend_connection (connection); - kc->suspended = GNUNET_YES; - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Suspending KYC request handling while checking with the exchange(s)\n"); - return MHD_YES; - } - if (GNUNET_SYSERR == kc->suspended) - return MHD_NO; /* during shutdown, we don't generate any more replies */ - GNUNET_assert (GNUNET_NO == kc->suspended); - if (0 != kc->response_code) - { - /* We are *done* processing the request, just queue the response (!) */ - if (UINT_MAX == kc->response_code) - { - GNUNET_break (0); - return MHD_NO; /* hard error */ } - return MHD_queue_response (connection, - kc->response_code, - kc->response); } - /* we should never get here */ - GNUNET_break (0); - return MHD_NO; + if (GNUNET_YES == kc->suspended) + return MHD_YES; + /* Should have generated a response */ + GNUNET_break (NULL != kc->response); + return MHD_queue_response (connection, + kc->response_code, + kc->response); } diff --git a/src/backend/taler-merchant-kyccheck.c b/src/backend/taler-merchant-kyccheck.c @@ -39,6 +39,14 @@ 30) /** + * How long do we wait between requests if all we wait + * for is a change in the AML investigation status? + */ +#define AML_FREQ GNUNET_TIME_relative_multiply ( \ + GNUNET_TIME_UNIT_HOURS, \ + 6) + +/** * How many inquiries do we process concurrently at most. */ #define OPEN_INQUIRY_LIMIT 1024 @@ -467,8 +475,15 @@ exchange_check_cb ( store_kyc_status (i, &ks->details.ok); i->backoff = GNUNET_TIME_UNIT_ZERO; - /* KYC is OK, only check again if triggered */ - i->due = GNUNET_TIME_UNIT_FOREVER_ABS; + if (i->aml_review) + { + i->due = GNUNET_TIME_relative_to_absolute (AML_FREQ); + } + else + { + /* KYC is OK, only check again if triggered */ + i->due = GNUNET_TIME_UNIT_FOREVER_ABS; + } break; case MHD_HTTP_ACCEPTED: i->last_kyc_check = GNUNET_TIME_timestamp_get (); @@ -573,6 +588,7 @@ static void inquiry_work (void *cls) { struct Inquiry *i = cls; + enum TALER_EXCHANGE_KycLongPollTarget lpt; i->task = NULL; if (! GNUNET_TIME_absolute_is_past (i->due)) @@ -600,11 +616,19 @@ inquiry_work (void *cls) i->e->keys->exchange_url); i->timeout = GNUNET_TIME_relative_to_absolute (EXCHANGE_TIMEOUT); + lpt = TALER_EXCHANGE_KLPT_NONE; + if (! i->auth_ok) + lpt = TALER_EXCHANGE_KLPT_NONE; + else if (! i->kyc_ok) + lpt = TALER_EXCHANGE_KLPT_KYC_OK; + else if (i->aml_review) + lpt = TALER_EXCHANGE_KLPT_INVESTIGATION_DONE; i->kyc = TALER_EXCHANGE_kyc_check ( ctx, i->e->keys->exchange_url, &i->a->h_payto, &i->a->ap, + lpt, EXCHANGE_TIMEOUT, &exchange_check_cb, i); @@ -997,7 +1021,9 @@ find_keys (const char *exchange_url) } if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) { - GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "No %s/keys yet!\n", + exchange_url); return; } for (e = e_head; NULL != e; e = e->next) diff --git a/src/backenddb/pg_account_kyc_get_status.c b/src/backenddb/pg_account_kyc_get_status.c @@ -41,16 +41,6 @@ struct KycStatusContext void *kyc_cb_cls; /** - * Filter, NULL to not filter. - */ - const struct TALER_MerchantWireHashP *h_wire; - - /** - * Filter, NULL to not filter. - */ - const char *exchange_url; - - /** * Number of results found. */ unsigned int count; @@ -80,11 +70,16 @@ kyc_status_cb (void *cls, for (unsigned int i = 0; i < num_results; i++) { struct TALER_MerchantWireHashP h_wire; - uint64_t kyc_serial = 0; /* deprecated */ char *exchange_url; char *payto_uri; struct GNUNET_TIME_Timestamp last_check; bool kyc_ok; + struct TALER_AccountAccessTokenP access_token; + bool no_auth; + uint32_t h32; + uint32_t e32; + bool in_aml_review; + json_t *jlimits = NULL; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_auto_from_type ("h_wire", &h_wire), @@ -96,8 +91,24 @@ kyc_status_cb (void *cls, &last_check), GNUNET_PQ_result_spec_bool ("kyc_ok", &kyc_ok), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_auto_from_type ("access_token", + &access_token), + &no_auth), + GNUNET_PQ_result_spec_uint32 ("exchange_http_status", + &h32), + GNUNET_PQ_result_spec_uint32 ("exchange_ec_code", + &e32), + GNUNET_PQ_result_spec_bool ("aml_review", + &in_aml_review), + GNUNET_PQ_result_spec_allow_null ( + TALER_PQ_result_spec_json ("jaccount_limits", + &jlimits), + NULL), GNUNET_PQ_result_spec_end }; + unsigned int last_http_status; + enum TALER_ErrorCode last_ec; if (GNUNET_OK != GNUNET_PQ_extract_result (result, @@ -108,28 +119,22 @@ kyc_status_cb (void *cls, ksc->failure = true; return; } - if ( (NULL != ksc->exchange_url) && - (0 != strcmp (ksc->exchange_url, - exchange_url)) ) - { - GNUNET_PQ_cleanup_result (rs); - continue; - } - if ( (NULL != ksc->h_wire) && - (0 != GNUNET_memcmp (ksc->h_wire, - &h_wire)) ) - { - GNUNET_PQ_cleanup_result (rs); - continue; - } + last_http_status = (unsigned int) h32; + last_ec = (enum TALER_ErrorCode) (int) e32; ksc->count++; ksc->kyc_cb (ksc->kyc_cb_cls, &h_wire, - kyc_serial, payto_uri, exchange_url, last_check, - kyc_ok); + kyc_ok, + (no_auth) + ? NULL + : &access_token, + last_http_status, + last_ec, + in_aml_review, + jlimits); GNUNET_PQ_cleanup_result (rs); } } @@ -147,12 +152,16 @@ TMH_PG_account_kyc_get_status ( struct PostgresClosure *pg = cls; struct KycStatusContext ksc = { .kyc_cb = kyc_cb, - .kyc_cb_cls = kyc_cb_cls, - .exchange_url = exchange_url, - .h_wire = h_wire + .kyc_cb_cls = kyc_cb_cls }; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (merchant_id), + NULL == exchange_url + ? GNUNET_PQ_query_param_null () + : GNUNET_PQ_query_param_string (exchange_url), + NULL == h_wire + ? GNUNET_PQ_query_param_null () + : GNUNET_PQ_query_param_auto_from_type (h_wire), GNUNET_PQ_query_param_end }; enum GNUNET_DB_QueryStatus qs; @@ -166,17 +175,27 @@ TMH_PG_account_kyc_get_status ( ",exchange_url" ",kyc_timestamp" ",kyc_ok" + ",access_token" + ",exchange_http_status" + ",exchange_ec_code" + ",aml_review" + ",jaccount_limits" " FROM merchant_instances" " JOIN merchant_accounts" " USING (merchant_serial)" " JOIN merchant_kyc" " USING (account_serial)" - " WHERE merchant_instances.merchant_id=$1"); - qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, - "lookup_kyc_status", - params, - &kyc_status_cb, - &ksc); + " WHERE (merchant_instances.merchant_id=$1)" + " AND ( ($2::TEXT IS NULL)" + " OR (exchange_url=$2) )" + " AND ( ($3::BYTEA IS NULL)" + " OR (h_wire=$3) );"); + qs = GNUNET_PQ_eval_prepared_multi_select ( + pg->conn, + "lookup_kyc_status", + params, + &kyc_status_cb, + &ksc); if (ksc.failure) { GNUNET_break (0); diff --git a/src/backenddb/pg_account_kyc_set_status.c b/src/backenddb/pg_account_kyc_set_status.c @@ -47,8 +47,14 @@ TMH_PG_account_kyc_set_status ( .header.type = htons (TALER_DBEVENT_MERCHANT_EXCHANGE_KYC_STATUS_CHANGED), .h_wire = *h_wire }; + struct GNUNET_DB_EventHeaderP hdr = { + .size = htons (sizeof (hdr)), + .type = htons (TALER_DBEVENT_MERCHANT_KYC_STATUS_CHANGED) + }; char *notify_s = GNUNET_PQ_get_event_notify_channel (&ev.header); + char *notify2_s + = GNUNET_PQ_get_event_notify_channel (&hdr); uint32_t http_status32 = (uint32_t) exchange_http_status; uint32_t ec_code32 = (uint32_t) exchange_ec_code; struct GNUNET_PQ_QueryParam params[] = { @@ -67,6 +73,7 @@ TMH_PG_account_kyc_set_status ( GNUNET_PQ_query_param_bool (in_aml_review), GNUNET_PQ_query_param_bool (kyc_ok), GNUNET_PQ_query_param_string (notify_s), + GNUNET_PQ_query_param_string (notify2_s), GNUNET_PQ_query_param_end }; bool no_instance; @@ -87,13 +94,14 @@ TMH_PG_account_kyc_set_status ( " out_no_instance AS no_instance" " ,out_no_account AS no_account" " FROM merchant_do_account_kyc_set_status" - "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11);"); + "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12);"); qs = GNUNET_PQ_eval_prepared_singleton_select ( pg->conn, "account_kyc_set_status", params, rs); GNUNET_free (notify_s); + GNUNET_free (notify2_s); if (qs <= 0) { GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); diff --git a/src/backenddb/pg_account_kyc_set_status.sql b/src/backenddb/pg_account_kyc_set_status.sql @@ -29,6 +29,7 @@ CREATE FUNCTION merchant_do_account_kyc_set_status ( IN in_aml_active BOOL, IN in_kyc_ok BOOL, IN in_notify_str TEXT, + IN in_notify2_str TEXT, OUT out_no_instance BOOL, OUT out_no_account BOOL) LANGUAGE plpgsql @@ -105,6 +106,10 @@ EXECUTE FORMAT ( 'NOTIFY %s' ,in_notify_str); +EXECUTE FORMAT ( + 'NOTIFY %s' + ,in_notify2_str); + -- Success! END $$; diff --git a/src/backenddb/pg_get_kyc_status.c b/src/backenddb/pg_get_kyc_status.c @@ -77,27 +77,23 @@ TMH_PG_get_kyc_status ( PREPARE (pg, "get_kyc_status", "SELECT" - " access_token" - ",exchange_http_status" - ",exchange_ec_code" - ",kyc_ok" - ",kyc_timestamp" - ",aml_review" - ",jaccount_limits" + " mk.access_token" + ",mk.exchange_http_status" + ",mk.exchange_ec_code" + ",mk.kyc_ok" + ",mk.kyc_timestamp" + ",mk.aml_review" + ",mk.jaccount_limits" " FROM merchant_kyc mk" - " JOIN merchant_accounts" - " USING (merchant_serial)" - " JOIN merchant_kyc" - " USING (account_serial)" - " WHERE exchange_url=$3" - " AND account_serial=" + " WHERE mk.exchange_url=$3" + " AND mk.account_serial=" " (SELECT account_serial" " FROM merchant_accounts" " WHERE payto_uri=$1" " AND merchant_serial=" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id=$2));"); + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$2));"); *jlimits = NULL; qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "get_kyc_status", diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c @@ -5570,13 +5570,18 @@ test_lookup_orders_all_filters (void) static void -kyc_status_ok (void *cls, - const struct TALER_MerchantWireHashP *h_wire, - uint64_t exchange_kyc_serial, - const char *payto_uri, - const char *exchange_url, - struct GNUNET_TIME_Timestamp last_check, - bool kyc_ok) +kyc_status_ok ( + void *cls, + const struct TALER_MerchantWireHashP *h_wire, + const char *payto_uri, + const char *exchange_url, + struct GNUNET_TIME_Timestamp last_check, + bool kyc_ok, + const struct TALER_AccountAccessTokenP *access_token, + unsigned int last_http_status, + enum TALER_ErrorCode last_ec, + bool in_aml_review, + const json_t *jlimits) { bool *fail = cls; @@ -5586,13 +5591,18 @@ kyc_status_ok (void *cls, static void -kyc_status_fail (void *cls, - const struct TALER_MerchantWireHashP *h_wire, - uint64_t exchange_kyc_serial, - const char *payto_uri, - const char *exchange_url, - struct GNUNET_TIME_Timestamp last_check, - bool kyc_ok) +kyc_status_fail ( + void *cls, + const struct TALER_MerchantWireHashP *h_wire, + const char *payto_uri, + const char *exchange_url, + struct GNUNET_TIME_Timestamp last_check, + bool kyc_ok, + const struct TALER_AccountAccessTokenP *access_token, + unsigned int last_http_status, + enum TALER_ErrorCode last_ec, + bool in_aml_review, + const json_t *jlimits) { bool *fail = cls; diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h @@ -4387,15 +4387,6 @@ struct TALER_MERCHANT_AccountKycRedirectDetail struct TALER_AccountAccessTokenP access_token; /** - * URL that the user should open in a browser to - * proceed with the KYC process (as returned - * by the exchange's /kyc-check/ endpoint). Can - * be NULL, specifically if KYC is satisfied but - * the transactions are hanging in AML. - */ - const char *kyc_url; - - /** * Base URL of the exchange this is about. */ const char *exchange_url; @@ -4442,31 +4433,25 @@ struct TALER_MERCHANT_AccountKycRedirectDetail */ enum TALER_ErrorCode exchange_code; -}; - - -/** - * Information about KYC status failures at the exchange. - */ -struct TALER_MERCHANT_ExchangeKycFailureDetail -{ /** - * Base URL of the exchange this is about. + * Set to true if @e access_token was not given. */ - const char *exchange_url; + bool no_access_token; /** - * Error code indicating errors the exchange - * returned, or #TALER_EC_INVALID for none. + * Set to true if the merchant backend could not + * get the exchanges ``/keys`` and thus could not + * determine default limits or determine an + * @e auth_conflict. */ - enum TALER_ErrorCode exchange_code; + bool no_keys; /** - * HTTP status code returned by the exchange when we asked for - * information about the KYC status. - * 0 if there was no response at all. + * Set to true if the given account cannot to KYC at the given exchange + * because no wire method exists that could be used to do the KYC auth wire + * transfer. */ - unsigned int exchange_http_status; + bool auth_conflict; }; @@ -4486,32 +4471,22 @@ struct TALER_MERCHANT_KycResponse union { /** - * Information returned if the status was #MHD_HTTP_ACCEPTED, - * #MHD_HTTP_BAD_GATEWAY or #MHD_HTTP_GATEWAY_TIMEOUT. + * Information returned if the status was #MHD_HTTP_OK. */ struct { /** - * Array with information about KYC actions the merchant still must perform. + * Array with information about KYC actions the merchant may perform. */ - struct TALER_MERCHANT_AccountKycRedirectDetail *pending_kycs; - - /** - * Array with information about KYC failures at the exchange. - */ - struct TALER_MERCHANT_ExchangeKycFailureDetail *timeout_kycs; + struct TALER_MERCHANT_AccountKycRedirectDetail *kycs; /** * Length of the @e pending_kycs array. */ - unsigned int pending_kycs_length; + unsigned int kycs_length; - /** - * Length of the @e timeout_kycs array. - */ - unsigned int timeout_kycs_length; - } kyc_status; + } ok; } details; @@ -4538,7 +4513,11 @@ typedef void * @param backend_url base URL of the merchant backend * @param h_wire which bank account to query, NULL for all * @param exchange_url which exchange to query, NULL for all - * @param timeout how long to wait for a (positive) reply + * @param lpt target for long polling + * @param timeout how long to wait for an answer, including possibly long polling for the desired @a lpt status; + * note that when pulling for multiple accounts, any + * account reaching this status will cause the + * response to be returned * @param cb function to call with the result * @param cb_cls closure for @a cb * @return handle for this operation, NULL upon errors @@ -4549,6 +4528,7 @@ TALER_MERCHANT_kyc_get ( const char *backend_url, const struct TALER_MerchantWireHashP *h_wire, const char *exchange_url, + enum TALER_EXCHANGE_KycLongPollTarget lpt, struct GNUNET_TIME_Relative timeout, TALER_MERCHANT_KycGetCallback cb, void *cb_cls); @@ -4563,7 +4543,8 @@ TALER_MERCHANT_kyc_get ( * @param instance_id specific instance to query * @param h_wire which bank account to query, NULL for all * @param exchange_url which exchange to query, NULL for all - * @param timeout how long to wait for a (positive) reply + * @param lpt target for long polling + * @param timeout how long to wait for a reply * @param cb function to call with the result * @param cb_cls closure for @a cb * @return handle for this operation, NULL upon errors @@ -4575,6 +4556,7 @@ TALER_MERCHANT_management_kyc_get ( const char *instance_id, const struct TALER_MerchantWireHashP *h_wire, const char *exchange_url, + enum TALER_EXCHANGE_KycLongPollTarget lpt, struct GNUNET_TIME_Relative timeout, TALER_MERCHANT_KycGetCallback cb, void *cb_cls); diff --git a/src/include/taler_merchant_testing_lib.h b/src/include/taler_merchant_testing_lib.h @@ -1259,6 +1259,7 @@ TALER_TESTING_cmd_merchant_delete_transfer (const char *label, * @param h_wire_ref label of command with a merchant wire hash trait * of the bank account to check KYC for; NULL to check all accounts * @param exchange_url base URL of the exchange to check KYC status for + * @param lpt target for long polling * @param expected_http_status expected HTTP status * @param expected_kyc_state expected KYC state (only effective if @e expected_http_status is #MHD_HTTP_OK/#MHD_HTTP_ACCEPTED) * @return the command @@ -1270,6 +1271,7 @@ TALER_TESTING_cmd_merchant_kyc_get ( const char *instance_id, const char *h_wire_ref, const char *exchange_url, + enum TALER_EXCHANGE_KycLongPollTarget lpt, unsigned int expected_http_status, bool expected_kyc_state); diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h @@ -854,16 +854,25 @@ typedef void * @param exchange_url base URL of the exchange for which this is a status * @param last_check when did we last get an update on our KYC status from the exchange * @param kyc_ok true if we satisfied the KYC requirements + * @param access_token access token for the KYC SPA, NULL if we cannot access it yet (need KYC auth wire transfer) + * @param last_http_status last HTTP status from /kyc-check + * @param last_ec last Taler error code from /kyc-check + * @param in_aml_review true if the account is pending review + * @param jlimits JSON array of applicable AccountLimits, or NULL if unknown (like defaults apply) */ typedef void (*TALER_MERCHANTDB_KycCallback)( void *cls, const struct TALER_MerchantWireHashP *h_wire, - uint64_t exchange_kyc_serial, const char *payto_uri, const char *exchange_url, struct GNUNET_TIME_Timestamp last_check, - bool kyc_ok); + bool kyc_ok, + const struct TALER_AccountAccessTokenP *access_token, + unsigned int last_http_status, + enum TALER_ErrorCode last_ec, + bool in_aml_review, + const json_t *jlimits); /** diff --git a/src/lib/merchant_api_get_kyc.c b/src/lib/merchant_api_get_kyc.c @@ -75,36 +75,27 @@ struct TALER_MERCHANT_KycGetHandle * @param kyc operation handle * @param[in,out] kr response details * @param pends pending_kycs array from the reply - * @param touts timeout_kycs array from the reply * @return #GNUNET_OK on success (callback was called) */ static enum GNUNET_GenericReturnValue parse_kyc (struct TALER_MERCHANT_KycGetHandle *kyc, struct TALER_MERCHANT_KycResponse *kr, - const json_t *pends, - const json_t *touts) + const json_t *jkyc) { - unsigned int num_pends = (unsigned int) json_array_size (pends); - unsigned int num_touts = (unsigned int) json_array_size (touts); + unsigned int num_kycs = (unsigned int) json_array_size (jkyc); unsigned int num_limits = 0; unsigned int num_kycauths = 0; unsigned int pos_limits = 0; unsigned int pos_kycauths = 0; - if ( (json_array_size (pends) != (size_t) num_pends) || - (num_pends > MAX_KYC) ) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - if ( (json_array_size (touts) != (size_t) num_touts) || - (num_touts > MAX_KYC) ) + if ( (json_array_size (jkyc) != (size_t) num_kycs) || + (num_kycs > MAX_KYC) ) { GNUNET_break (0); return GNUNET_SYSERR; } - for (unsigned int i = 0; i<num_pends; i++) + for (unsigned int i = 0; i<num_kycs; i++) { const json_t *jlimits = NULL; const json_t *jkycauths = NULL; @@ -123,13 +114,13 @@ parse_kyc (struct TALER_MERCHANT_KycGetHandle *kyc, }; if (GNUNET_OK != - GNUNET_JSON_parse (json_array_get (pends, + GNUNET_JSON_parse (json_array_get (jkyc, i), spec, NULL, NULL)) { GNUNET_break (0); - json_dumpf (json_array_get (pends, + json_dumpf (json_array_get (jkyc, i), stderr, JSON_INDENT (2)); @@ -141,25 +132,49 @@ parse_kyc (struct TALER_MERCHANT_KycGetHandle *kyc, { - struct TALER_MERCHANT_AccountKycRedirectDetail pending_kycs[ - GNUNET_NZL (num_pends)]; - struct TALER_MERCHANT_ExchangeKycFailureDetail timeout_kycs[ - GNUNET_NZL (num_touts)]; + struct TALER_MERCHANT_AccountKycRedirectDetail kycs[ + GNUNET_NZL (num_kycs)]; struct TALER_EXCHANGE_AccountLimit limits[ GNUNET_NZL (num_limits)]; const char *payto_kycauths[ GNUNET_NZL (num_kycauths)]; - memset (pending_kycs, + memset (kycs, 0, - sizeof (pending_kycs)); - for (unsigned int i = 0; i<num_pends; i++) + sizeof (kycs)); + for (unsigned int i = 0; i<num_kycs; i++) { struct TALER_MERCHANT_AccountKycRedirectDetail *rd - = &pending_kycs[i]; + = &kycs[i]; const json_t *jlimits = NULL; const json_t *jkycauths = NULL; + uint32_t hs; struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_payto_uri ( + "payto_uri", + &rd->payto_uri), + TALER_JSON_spec_web_url ( + "exchange_url", + &rd->exchange_url), + GNUNET_JSON_spec_uint32 ( + "exchange_http_status", + &hs), + GNUNET_JSON_spec_bool ( + "no_keys", + &rd->no_keys), + GNUNET_JSON_spec_bool ( + "auth_conflict", + &rd->auth_conflict), + GNUNET_JSON_spec_mark_optional ( + TALER_JSON_spec_ec ( + "exchange_code", + &rd->exchange_code), + NULL), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_fixed_auto ( + "access_token", + &rd->access_token), + &rd->no_access_token), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_array_const ( "limits", @@ -170,18 +185,6 @@ parse_kyc (struct TALER_MERCHANT_KycGetHandle *kyc, "payto_kycauths", &jkycauths), NULL), - GNUNET_JSON_spec_fixed_auto ( - "access_token", - &rd->access_token), - TALER_JSON_spec_web_url ( - "kyc_url", - &rd->kyc_url), - TALER_JSON_spec_web_url ( - "exchange_url", - &rd->exchange_url), - TALER_JSON_spec_payto_uri ( - "payto_uri", - &rd->payto_uri), GNUNET_JSON_spec_end () }; size_t j; @@ -189,18 +192,19 @@ parse_kyc (struct TALER_MERCHANT_KycGetHandle *kyc, json_t *jkycauth; if (GNUNET_OK != - GNUNET_JSON_parse (json_array_get (pends, + GNUNET_JSON_parse (json_array_get (jkyc, i), spec, NULL, NULL)) { GNUNET_break (0); - json_dumpf (json_array_get (pends, + json_dumpf (json_array_get (jkyc, i), stderr, JSON_INDENT (2)); return GNUNET_SYSERR; } + rd->exchange_http_status = (unsigned int) hs; rd->limits = &limits[pos_limits]; rd->limits_length = json_array_size (jlimits); json_array_foreach (jlimits, j, jlimit) @@ -233,7 +237,7 @@ parse_kyc (struct TALER_MERCHANT_KycGetHandle *kyc, NULL, NULL)) { GNUNET_break (0); - json_dumpf (json_array_get (pends, + json_dumpf (json_array_get (jkyc, i), stderr, JSON_INDENT (2)); @@ -251,7 +255,7 @@ parse_kyc (struct TALER_MERCHANT_KycGetHandle *kyc, if (NULL == payto_kycauths[pos_kycauths]) { GNUNET_break (0); - json_dumpf (json_array_get (pends, + json_dumpf (json_array_get (jkyc, i), stderr, JSON_INDENT (2)); @@ -260,38 +264,8 @@ parse_kyc (struct TALER_MERCHANT_KycGetHandle *kyc, pos_kycauths++; } } - for (unsigned int i = 0; i<num_touts; i++) - { - uint32_t hs; - struct GNUNET_JSON_Specification spec[] = { - TALER_JSON_spec_web_url ( - "exchange_url", - &timeout_kycs[i].exchange_url), - TALER_JSON_spec_ec ( - "exchange_code", - &timeout_kycs[i].exchange_code), - GNUNET_JSON_spec_uint32 ( - "exchange_http_status", - &hs), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (json_array_get (touts, - i), - spec, - NULL, NULL)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - timeout_kycs[i].exchange_http_status - = (unsigned int) hs; - } - kr->details.kyc_status.pending_kycs = pending_kycs; - kr->details.kyc_status.timeout_kycs = timeout_kycs; - kr->details.kyc_status.pending_kycs_length = num_pends; - kr->details.kyc_status.timeout_kycs_length = num_touts; + kr->details.ok.kycs = kycs; + kr->details.ok.kycs_length = num_kycs; kyc->cb (kyc->cb_cls, kr); } @@ -325,19 +299,12 @@ handle_get_kyc_finished (void *cls, (unsigned int) response_code); switch (response_code) { - case MHD_HTTP_NO_CONTENT: - break; - case MHD_HTTP_ACCEPTED: - case MHD_HTTP_BAD_GATEWAY: - case MHD_HTTP_GATEWAY_TIMEOUT: + case MHD_HTTP_OK: { - const json_t *pends; - const json_t *touts; + const json_t *jkyc; struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_array_const ("pending_kycs", - &pends), - GNUNET_JSON_spec_array_const ("timeout_kycs", - &touts), + GNUNET_JSON_spec_array_const ("kyc_data", + &jkyc), GNUNET_JSON_spec_end () }; @@ -353,8 +320,7 @@ handle_get_kyc_finished (void *cls, if (GNUNET_OK != parse_kyc (kyc, &kr, - pends, - touts)) + jkyc)) { kr.hr.http_status = 0; kr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; @@ -364,6 +330,8 @@ handle_get_kyc_finished (void *cls, TALER_MERCHANT_kyc_get_cancel (kyc); return; } + case MHD_HTTP_NO_CONTENT: + break; case MHD_HTTP_UNAUTHORIZED: kr.hr.ec = TALER_JSON_get_error_code (json); kr.hr.hint = TALER_JSON_get_error_hint (json); @@ -395,7 +363,8 @@ handle_get_kyc_finished (void *cls, * @param[in] url URL to use for the request, consumed! * @param h_wire which bank account to query, NULL for all * @param exchange_url which exchange to query, NULL for all - * @param timeout how long to wait for a (positive) reply + * @param lpt target for long polling + * @param timeout how long to wait for a reply * @param cb function to call with the result * @param cb_cls closure for @a cb * @return handle for this operation, NULL upon errors @@ -405,6 +374,7 @@ kyc_get (struct GNUNET_CURL_Context *ctx, char *url, const struct TALER_MerchantWireHashP *h_wire, const char *exchange_url, + enum TALER_EXCHANGE_KycLongPollTarget lpt, struct GNUNET_TIME_Relative timeout, TALER_MERCHANT_KycGetCallback cb, void *cb_cls) @@ -412,34 +382,44 @@ kyc_get (struct GNUNET_CURL_Context *ctx, struct TALER_MERCHANT_KycGetHandle *kyc; CURL *eh; char timeout_ms[32]; - unsigned int tms; + char lpt_str[32]; + unsigned long long tms; kyc = GNUNET_new (struct TALER_MERCHANT_KycGetHandle); kyc->ctx = ctx; kyc->cb = cb; kyc->cb_cls = cb_cls; - tms = (unsigned int) (timeout.rel_value_us - / GNUNET_TIME_UNIT_MILLISECONDS. - rel_value_us); + GNUNET_snprintf (lpt_str, + sizeof (lpt_str), + "%d", + (int) lpt); + tms = timeout.rel_value_us + / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us; GNUNET_snprintf (timeout_ms, sizeof (timeout_ms), - "%u", + "%llu", tms); - kyc->url = TALER_url_join (url, - "kyc", - "h_wire", - NULL == h_wire - ? NULL - : GNUNET_h2s_full (&h_wire->hash), - "exchange_url", - NULL == exchange_url - ? NULL - : exchange_url, - "timeout_ms", - GNUNET_TIME_relative_is_zero (timeout) - ? NULL - : timeout_ms, - NULL); + kyc->url + = TALER_url_join ( + url, + "kyc", + "h_wire", + NULL == h_wire + ? NULL + : GNUNET_h2s_full (&h_wire->hash), + "exchange_url", + NULL == exchange_url + ? NULL + : exchange_url, + "timeout_ms", + GNUNET_TIME_relative_is_zero (timeout) + ? NULL + : timeout_ms, + "lpt", + TALER_EXCHANGE_KLPT_NONE == lpt + ? NULL + : lpt_str, + NULL); GNUNET_free (url); if (NULL == kyc->url) { @@ -459,22 +439,25 @@ kyc_get (struct GNUNET_CURL_Context *ctx, CURLOPT_TIMEOUT_MS, (long) (tms + 100L))); } - kyc->job = GNUNET_CURL_job_add (ctx, - eh, - &handle_get_kyc_finished, - kyc); + kyc->job + = GNUNET_CURL_job_add (ctx, + eh, + &handle_get_kyc_finished, + kyc); return kyc; } struct TALER_MERCHANT_KycGetHandle * -TALER_MERCHANT_kyc_get (struct GNUNET_CURL_Context *ctx, - const char *backend_url, - const struct TALER_MerchantWireHashP *h_wire, - const char *exchange_url, - struct GNUNET_TIME_Relative timeout, - TALER_MERCHANT_KycGetCallback cb, - void *cb_cls) +TALER_MERCHANT_kyc_get ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const struct TALER_MerchantWireHashP *h_wire, + const char *exchange_url, + enum TALER_EXCHANGE_KycLongPollTarget lpt, + struct GNUNET_TIME_Relative timeout, + TALER_MERCHANT_KycGetCallback cb, + void *cb_cls) { char *url; @@ -485,6 +468,7 @@ TALER_MERCHANT_kyc_get (struct GNUNET_CURL_Context *ctx, url, /* consumed! */ h_wire, exchange_url, + lpt, timeout, cb, cb_cls); @@ -492,14 +476,16 @@ TALER_MERCHANT_kyc_get (struct GNUNET_CURL_Context *ctx, struct TALER_MERCHANT_KycGetHandle * -TALER_MERCHANT_management_kyc_get (struct GNUNET_CURL_Context *ctx, - const char *backend_url, - const char *instance_id, - const struct TALER_MerchantWireHashP *h_wire, - const char *exchange_url, - struct GNUNET_TIME_Relative timeout, - TALER_MERCHANT_KycGetCallback cb, - void *cb_cls) +TALER_MERCHANT_management_kyc_get ( + struct GNUNET_CURL_Context *ctx, + const char *backend_url, + const char *instance_id, + const struct TALER_MerchantWireHashP *h_wire, + const char *exchange_url, + enum TALER_EXCHANGE_KycLongPollTarget lpt, + struct GNUNET_TIME_Relative timeout, + TALER_MERCHANT_KycGetCallback cb, + void *cb_cls) { char *url; @@ -511,6 +497,7 @@ TALER_MERCHANT_management_kyc_get (struct GNUNET_CURL_Context *ctx, url, /* consumed! */ h_wire, exchange_url, + lpt, timeout, cb, cb_cls); diff --git a/src/testing/test_kyc_api.c b/src/testing/test_kyc_api.c @@ -240,8 +240,9 @@ run (void *cls, NULL, NULL, EXCHANGE_URL, - MHD_HTTP_NO_CONTENT, - false), + TALER_EXCHANGE_KLPT_NONE, + MHD_HTTP_OK, + true), /* now we get the legi UUID by running taler-merchant-depositcheck */ TALER_TESTING_cmd_depositcheck ( "deposit-check", @@ -260,16 +261,15 @@ run (void *cls, "get-default-instance"), CMD_EXEC_WIREWATCH ( "import-kyc-account-withdraw"), - - /* Now we should get a status of pending */ TALER_TESTING_cmd_merchant_kyc_get ( "kyc-pending", merchant_url, - NULL, /* instance */ - NULL, /* h_wire_ref: which account to query */ + NULL, /* default instance */ + "instance-create-default-account", /* h_wire_ref: which account to query */ EXCHANGE_URL, - MHD_HTTP_ACCEPTED, + TALER_EXCHANGE_KLPT_KYC_AUTH_TRANSFER, + MHD_HTTP_OK, true), TALER_TESTING_cmd_get_kyc_info ( "get-kyc-info-deposit", @@ -426,7 +426,8 @@ run (void *cls, NULL, /* no instance ID */ NULL, /* no wire ref */ EXCHANGE_URL, - MHD_HTTP_ACCEPTED, + TALER_EXCHANGE_KLPT_KYC_AUTH_TRANSFER, + MHD_HTTP_OK, true), TALER_TESTING_cmd_sleep ( "sleep to de-collide AML timestamps", @@ -454,6 +455,7 @@ run (void *cls, NULL, /* no instance ID */ NULL, /* no wire ref */ EXCHANGE_URL, + TALER_EXCHANGE_KLPT_KYC_OK, MHD_HTTP_OK, false), CMD_EXEC_AGGREGATOR ("run-aggregator-aml-normal"), @@ -521,10 +523,8 @@ run (void *cls, MHD_HTTP_OK), TALER_TESTING_cmd_batch ("pay", pay), -#if FIXME_FUTURE_WORK || 1 TALER_TESTING_cmd_batch ("aml", aml), -#endif TALER_TESTING_cmd_end () }; diff --git a/src/testing/test_merchant_api.c b/src/testing/test_merchant_api.c @@ -225,6 +225,7 @@ run (void *cls, NULL, NULL, EXCHANGE_URL, + TALER_EXCHANGE_KLPT_NONE, MHD_HTTP_NO_CONTENT, false), TALER_TESTING_cmd_merchant_post_orders_no_claim ( diff --git a/src/testing/testing_api_cmd_kyc_get.c b/src/testing/testing_api_cmd_kyc_get.c @@ -16,13 +16,11 @@ License along with TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ - /** * @file testing_api_cmd_kyc_get.c * @brief command to test kyc_get request * @author Christian Grothoff */ - #include "platform.h" #include <taler/taler_exchange_service.h> #include <taler/taler_testing_lib.h> @@ -78,11 +76,21 @@ struct KycGetState unsigned int expected_http_status; /** + * Target for long-polling. + */ + enum TALER_EXCHANGE_KycLongPollTarget lpt; + + /** * Expected KYC state. */ bool expected_kyc_state; /** + * Expected KYC state. + */ + bool have_access_token; + + /** * Interpreter state. */ struct TALER_TESTING_Interpreter *is; @@ -135,28 +143,32 @@ kyc_get_cb (void *cls, } switch (kr->hr.http_status) { - case MHD_HTTP_ACCEPTED: + case MHD_HTTP_OK: if (! cs->expected_kyc_state) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Expected KYC state %u, got %u\n", cs->expected_kyc_state, - kr->details.kyc_status.pending_kycs_length); + kr->details.ok.kycs_length); TALER_TESTING_FAIL (cs->is); } - for (unsigned int i = 0; i<kr->details.kyc_status.pending_kycs_length; i++) + for (unsigned int i = 0; i<kr->details.ok.kycs_length; i++) { const char *payto_uri; - payto_uri = kr->details.kyc_status.pending_kycs[i].payto_uri; + payto_uri = kr->details.ok.kycs[i].payto_uri; if (NULL == payto_uri) { continue; } TALER_payto_hash (payto_uri, &cs->h_payto); - cs->access_token - = kr->details.kyc_status.pending_kycs[i].access_token; + if (! kr->details.ok.kycs[i].no_access_token) + { + cs->access_token + = kr->details.ok.kycs[i].access_token; + cs->have_access_token = true; + } break; } break; @@ -190,7 +202,6 @@ kyc_get_run (void *cls, TALER_TESTING_interpreter_lookup_command (cs->is, cs->h_wire_ref))) { - GNUNET_break (0); TALER_TESTING_FAIL (cs->is); } /* Note: at the time of writing, no command offers an h_wire trait, @@ -199,7 +210,6 @@ kyc_get_run (void *cls, TALER_TESTING_get_trait_h_wire (wire_cmd, &h_wire)) { - GNUNET_break (0); TALER_TESTING_FAIL (cs->is); } } @@ -209,7 +219,10 @@ kyc_get_run (void *cls, cs->merchant_url, h_wire, cs->exchange_url, - GNUNET_TIME_UNIT_ZERO, + cs->lpt, + TALER_EXCHANGE_KLPT_NONE == cs->lpt + ? GNUNET_TIME_UNIT_ZERO + : GNUNET_TIME_UNIT_MINUTES, &kyc_get_cb, cs); else @@ -219,7 +232,10 @@ kyc_get_run (void *cls, cs->instance_id, h_wire, cs->exchange_url, - GNUNET_TIME_UNIT_ZERO, + cs->lpt, + TALER_EXCHANGE_KLPT_NONE == cs->lpt + ? GNUNET_TIME_UNIT_ZERO + : GNUNET_TIME_UNIT_MINUTES, &kyc_get_cb, cs); @@ -244,17 +260,21 @@ kyc_get_traits (void *cls, { struct KycGetState *cs = cls; struct TALER_TESTING_Trait traits[] = { - TALER_TESTING_make_trait_h_payto ( - &cs->h_payto), + /* Must be first, skipped if we have no token! */ TALER_TESTING_make_trait_account_access_token ( &cs->access_token), + TALER_TESTING_make_trait_h_payto ( + &cs->h_payto), TALER_TESTING_trait_end () }; - return TALER_TESTING_get_trait (traits, - ret, - trait, - index); + return TALER_TESTING_get_trait ( + &traits[cs->have_access_token + ? 0 + : 1], + ret, + trait, + index); } @@ -265,6 +285,7 @@ TALER_TESTING_cmd_merchant_kyc_get ( const char *instance_id, const char *h_wire_ref, const char *exchange_url, + enum TALER_EXCHANGE_KycLongPollTarget lpt, unsigned int expected_http_status, bool expected_kyc_state) { @@ -275,6 +296,7 @@ TALER_TESTING_cmd_merchant_kyc_get ( cs->instance_id = instance_id; cs->h_wire_ref = h_wire_ref; cs->exchange_url = exchange_url; + cs->lpt = lpt; cs->expected_http_status = expected_http_status; cs->expected_kyc_state = expected_kyc_state; { diff --git a/src/testing/testing_api_cmd_post_account.c b/src/testing/testing_api_cmd_post_account.c @@ -167,11 +167,13 @@ post_account_traits (void *cls, const char *trait, unsigned int index) { - struct PostAccountState *pps = cls; + struct PostAccountState *pps = cls; struct TALER_TESTING_Trait traits[] = { TALER_TESTING_make_trait_h_wires ( 0, &pps->h_wire), + TALER_TESTING_make_trait_h_wire ( + &pps->h_wire), TALER_TESTING_make_trait_payto_uris ( 0, pps->payto_uri),