gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-merchant] branch master updated: towards new /kyc API: test_kyc_a


From: gnunet
Subject: [taler-merchant] branch master updated: towards new /kyc API: test_kyc_api still fails, but getting close
Date: Sun, 08 Sep 2024 23:39:53 +0200

This is an automated email from the git hooks/post-receive script.

grothoff pushed a commit to branch master
in repository merchant.

The following commit(s) were added to refs/heads/master by this push:
     new fd6e459c towards new /kyc API: test_kyc_api still fails, but getting 
close
fd6e459c is described below

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

    towards new /kyc API: test_kyc_api still fails, but getting close
---
 ...r-merchant-httpd_private-get-instances-ID-kyc.c | 982 ++++++++-------------
 src/backend/taler-merchant-kyccheck.c              |  32 +-
 src/backenddb/pg_account_kyc_get_status.c          |  91 +-
 src/backenddb/pg_account_kyc_set_status.c          |  10 +-
 src/backenddb/pg_account_kyc_set_status.sql        |   5 +
 src/backenddb/pg_get_kyc_status.c                  |  28 +-
 src/backenddb/test_merchantdb.c                    |  38 +-
 src/include/taler_merchant_service.h               |  68 +-
 src/include/taler_merchant_testing_lib.h           |   2 +
 src/include/taler_merchantdb_plugin.h              |  13 +-
 src/lib/merchant_api_get_kyc.c                     | 237 +++--
 src/testing/test_kyc_api.c                         |  20 +-
 src/testing/test_merchant_api.c                    |   1 +
 src/testing/testing_api_cmd_kyc_get.c              |  58 +-
 src/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
index 69e962d0..7fb229b2 100644
--- 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.
@@ -111,11 +109,63 @@ 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;
+
+
 };
 
 
@@ -149,11 +199,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.
    */
@@ -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
@@ -195,6 +228,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.
@@ -221,6 +260,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.
    */
@@ -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 */
 }
 
 
@@ -471,107 +443,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
index 279be891..f001285c 100644
--- a/src/backend/taler-merchant-kyccheck.c
+++ b/src/backend/taler-merchant-kyccheck.c
@@ -38,6 +38,14 @@
           GNUNET_TIME_UNIT_MINUTES, \
           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.
  */
@@ -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
index 1eee0416..0a89189c 100644
--- a/src/backenddb/pg_account_kyc_get_status.c
+++ b/src/backenddb/pg_account_kyc_get_status.c
@@ -40,16 +40,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.
    */
@@ -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
index ecd63fb3..f2a3e51e 100644
--- 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
index 2d752167..dcb26a44 100644
--- 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
index 5c980bfd..3a216d83 100644
--- 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
index 392511e1..f1d6e392 100644
--- 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
index b1840ea8..05ea273b 100644
--- a/src/include/taler_merchant_service.h
+++ b/src/include/taler_merchant_service.h
@@ -4386,15 +4386,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.
    */
@@ -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
index 4833e4fe..c18274dc 100644
--- 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
index 5e50845c..41d7570d 100644
--- 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
index bf5d4300..8e441647 100644
--- 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
index 5f34995a..53e2818f 100644
--- 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
index f5cd61ab..72546e4c 100644
--- 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
index bca4fbd8..f58b091d 100644
--- 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>
@@ -77,11 +75,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.
    */
@@ -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
index 8ddad94c..057ab060 100644
--- 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),

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]